Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I have a C++ framework being built in the latest version of Xcode (9.4.1 at the time of writing) that I am using from Objective-C++ code, again within Xcode. I need to perform a dynamic_cast from one pointer type to another. However, the dynamic_cast is only working from a Debug build and is not working from a Release build. Is there something I'm missing or understanding about how dynamic_cast works here within Objective-C++ that makes this sample fail?

C++ Framework

TestClass.hpp

class Parent {
    public:
    // https://stackoverflow.com/a/8470002/3938401
    // 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++ Command Line App

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;
}

In both Debug and Release, I expect this code to print "True". However, in reality, Release mode prints "False". As a smoke test, the dynamic_cast at this SO post works just fine.

Interestingly, the same sort of code works from a C++ command line application, again within Xcode. I have tried disabling the optimizer in Release mode, but this did not seem to fix the problem.

I have a sample project up on GitHub here. Remember to compile it in Release to see the reason for my question. I've included the TestCast scheme for Objective-C++, and the TestCastCPP scheme for the straight C++.

like image 441
Deadpikle Avatar asked Jun 19 '18 13:06

Deadpikle


People also ask

What does dynamic_cast return if fails?

If the dynamic_cast operator succeeds, it returns a pointer that points to the object denoted by arg . If dynamic_cast fails, it returns 0 . You may perform downcasts with the dynamic_cast operator only on polymorphic classes.

Why does dynamic_cast return null?

If the cast is successful, dynamic_cast returns a value of type new-type. If the cast fails and new-type is a pointer type, it returns a null pointer of that type. If the cast fails and new-type is a reference type, it throws an exception that matches a handler of type std::bad_cast.

What is the difference between Static_cast and dynamic_cast?

static_cast − This is used for the normal/ordinary type conversion. This is also the cast responsible for implicit type coersion and can also be called explicitly. You should use it in cases like converting float to int, char to int, etc. dynamic_cast −This cast is used for handling polymorphism.

Is Static_cast faster than dynamic_cast?

While typeid + static_cast is faster than dynamic_cast , not having to switch on the runtime type of the object is faster than any of them. Save this answer.


1 Answers

It is hard to know the compiler specifics, since how a compiler does RTTI has some flexibility (i.e., not specified in great detail by the specification).

In this case, since Child class did not have any virtual functions defined, I suspect the compiler had emitted RTTI with every translation unit for the Child class.

When the framework was linked, and when the executable was linked, each had their own Child RTTI information, since each translation unit emitted its own RTTI.

The Parent link of the one did not match the Parent link of the other, I suspect, so they did not have the same Parent pointer and those things were not "fixed up" by the dynamic loader. (The dynamic_cast<Child*> basically walks the parent pointer chain until it finds a match by pointer value, not by RTTI value.)

If you looked at nm -g TestCast | c++filt dumps of the app and of the framework, you can see the RTTI blocks. Disassembling them, I think the Child RTTI was already resolved in both situations to their own Parent RTTI.

Why did it work for DEBUG, but not RELEASE? Probably, one of the release optimizations was dead-code stripping of external linkage symbols based on usage. So the dynamic loader (dyld) for DEBUG was able to resolve the symbols, but the RELEASE build one-or-more symbols was already internally resolved.

There's probably a way to indicate an "unused" symbol for the RTTI should be retained and exported, which will vary by compiler/linker. But that's more bother than providing an explicit "first virtual function" (such as the virtual Child destructor) which avoids the problem.

like image 117
Eljay Avatar answered Sep 29 '22 15:09

Eljay