Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Objective-C blocks and C++ objects

I have a method that is being executed on a background thread. From that method I'm trying to dispatch_async a block on the main thread. The block uses a local C++ object which is supposed to be copy constructed according to the Apple reference. I'm getting a segmentation fault and from the trace I see that something very sketchy is going on. Here's the simplified version of my code.

struct A
{
    A() { printf("0x%08x: A::A()\n", this); }
    A(A const &that) { printf("0x%08x: A::A(A const &%p)\n", this, &that); }
    ~A() { printf("0x%08x: A::~A()\n", this); }
    void p() const { printf("0x%08x: A::p()\n", this); }
};

- (void)runs_on_a_background_thread
{
    A a;
    a.p();
    dispatch_async(dispatch_get_main_queue(), ^{
        printf("block begins\n");
        a.p();
        printf("block ends\n");
    });
}

And this is the output:

0xbfffc2af: A::A()
0xbfffc2af: A::p()
0xbfffc2a8: A::A(A const &0xbfffc2af)
0x057ae6b4: A::A(A const &0xbfffc2a8)
0xbfffc2a8: A::~A()
0xbfffc2af: A::~A()
0xbfffdfcf: A::A(A const &0x57ae6b4)
0xbfffdfcf: A::~A()
block begins
0xbfffdfcf: A::p()
block ends
0x057ae6b4: A::~A()

There are two things that I don't understand. The first one is why by the time it gets to 0xbfffdfcf: A::p() the destructor on that object has been called already.

The second thing I'm struggling with is why there are so many copy constructors being called. I expect one. That should happen when a copy of a is created to be captured by the block.

I'm using Xcode 3.2.5 with GCC. I experience the same behavior on the simulator and on the device.

like image 927
detunized Avatar asked Nov 13 '11 15:11

detunized


People also ask

Is Objective-C object-oriented?

Objective-C is the primary programming language you use when writing software for OS X and iOS. It's a superset of the C programming language and provides object-oriented capabilities and a dynamic runtime.

Why is Objective-C so different?

Objective-C uses the runtime code compilation Objective-C isn't a fast language. The main reason is that it uses the runtime code compilation, rather than the compile time. This means that when the Objective-C object calls for another object in the code, there is an extra level of indirection involved.

Can you still use Objective-C?

Furthermore, Objective-C is a piece of art, creators packed genius solutions and were constantly improving it, so us developers were able to use it at our advantage. There are a lot of indicators telling us there's still a ton of legacy Objective-C code, both from Apple and from other developers, that's still in use.


2 Answers

I just tested this on LLVM 3.0.

0xb024ee18: A::A()
0xb024ee18: A::p()
0xb024ee04: A::A(A const &0xb024ee18)
0x06869364: A::A(A const &0xb024ee04)
0xb024ee04: A::~A()
0xb024ee18: A::~A()
block begins
0x06869364: A::p()
block ends
0x06869364: A::~A()

As you can see the destructors get called appropriately in this case, I'd chalk this up to a complier bug in the extremely outdated compiler you're using.

The copies in this instance seem inline with what I'd expect. The block copies the stack based object into the block when it gets captured. And then again when the block gets copied from the stack to the heap.

like image 170
Joshua Weinberg Avatar answered Sep 28 '22 06:09

Joshua Weinberg


I guess that the multiple copies are caused by the block being copied implicitly a couple of times by the compiler, though I don't see why the block needs to be copied, you'd think that the one instance can be referenced directly when the block is sent to the main thread.

Ignoring the multiple copies, it seems the block should have used the 0x057ae6b4 instance of A since that is the one that survives all the copies and is freed after the block ends. Sounds like a compiler bug to me.

In any case, what you are doing is extremely anti-C++ and I recommend that you revise this code so that it has a more predictable behavior. The problem I have with your code is that you are using a stack allocated object in a block of code that will execute asynchronously at some undetermined time in the future, long after the function that owns that stack allocated object ended. To support this kind of thing the compiler has to generate a copy of your object under the covers, yet if you look at the code there is no indication that the a inside the block is a copy of the a declared outside. If you need to support this kind of thing I think you will be better off converting your C++ class to Objective-C, then you'll have a reference counted object that behaves in a more predictable way.

If this object needs to stay in the C++ domain, then I recommend that you allocate it on the heap and manage the destruction of it manually, as is standard for heap allocated objects in C++. For example, you could do something like this:

- (void)runs_on_a_background_thread
{
    A* a = new A();
    a->p();
    dispatch_async(dispatch_get_main_queue(), ^{
        printf("block begins\n");
        a->p();
        delete a;
        printf("block ends\n");
    });
}

If this sounds too raw to you, then you could see if using an auto_ptr or better yet, a shared_ptr works better. I suspect these two will have the same compiler issue seen when you work with A allocated on the stack, since these will also be allocated on the stack. But it might be worth a try.

like image 27
Miguel Avatar answered Sep 28 '22 05:09

Miguel