为什么这个Objective-C++的dynamic_castdebug成功,release失败?

Why does this dynamic_cast from Objective-C++ succeed in debug but fail in release?

我在最新版本的 Xcode(撰写本文时为 9.4.1)中构建了一个 C++ 框架,我正在使用 Objective-C++ 代码,再次在 Xcode。我需要执行从一种指针类型到另一种指针类型的 dynamic_cast。但是,dynamic_cast 仅适用于调试版本,不适用于发布版本。关于 dynamic_cast 如何在 Objective-C++ 中工作,我是否缺少或理解导致此示例失败的某些内容?

C++ 框架

TestClass.hpp

class Parent {
    public:
    // 
    // must have at least 1 virtual function for RTTI
    virtual ~Parent();

    Parent() {}
};

class Child : public Parent {
public:
    // if you put the implementation for this func 
    // in the header, everything works.
    static Child* createRawPtr(); 
};

TestClass.cpp

#include "TestClass.hpp"

Parent::~Parent() {}

Child* Child::createRawPtr() {
    return new Child;
}

Objective-C++ 命令行应用程序

main.mm

#import <Foundation/Foundation.h>
#import <TestCastCPP/TestClass.hpp>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Parent *parentPtr = Child::createRawPtr();
        Child *child = dynamic_cast<Child*>(parentPtr);
        NSLog(@"Was the cast successful? %s", child != nullptr ? "True" : "False");
    }
    return 0;
}

在 Debug 和 Release 中,我希望此代码打印 "True"。然而,实际上,Release 模式打印 "False"。作为冒烟测试,this SO post 处的 dynamic_cast 工作正常。

有趣的是,同样类型的代码在 C++ 命令行应用程序中运行,同样在 Xcode 中。我试过在 Release 模式下禁用优化器,但这似乎并没有解决问题。

我有一个示例项目up on GitHub here。记得在Release里编译一下看看我问题的原因。我已经包含了 Objective-C++ 的 TestCast 方案,以及直接 C++ 的 TestCastCPP 方案。

很难知道编译器的细节,因为编译器如何执行 RTTI 具有一定的灵活性(即,规范没有详细说明)。

在这种情况下,由于 Child class 没有定义任何虚函数,我怀疑编译器已经为 Child class.

的每个翻译单元发出了 RTTI

当框架被 linked 时,当可执行文件被 linked 时,每个都有自己的子 RTTI 信息,因为每个翻译单元发出自己的 RTTI。

一个的父 link 与另一个的父 link 不匹配,我怀疑,所以他们没有相同的父指针,那些东西不是 "fixed up" 由动态加载程序。 (dynamic_cast<Child*> 基本上遍历父指针链,直到它通过指针值而不是 RTTI 值找到匹配项。)

如果您查看了应用程序和框架的 nm -g TestCast | c++filt 转储,您可以看到 RTTI 块。拆开来看,我认为Child RTTI在这两种情况下都已经解析为它们自己的Parent RTTI。

为什么它对 DEBUG 有效,对 RELEASE 无效?发布优化之一可能是根据使用情况去除外部 linkage 符号的死代码。因此 DEBUG 的动态加载程序 (dyld) 能够解析符号,但是 RELEASE 构建一个或多个符号已经在内部解析。

可能有一种方法可以指示应保留和导出 RTTI 的 "unused" 符号,这将因 compiler/linker 而异。但这比提供避免问题的显式 "first virtual function"(例如虚拟 Child 析构函数)更麻烦。