Home > Articles > Programming > C/C++

  • Print
  • + Share This
From the author of

Interoperating with C

One of the advantages of Objective-C over other dynamic languages is the ease of interoperability with C. You can think of Objective-C as a Smalltalk dialect that allows inline C, just as C allows inline assembly. Or you can think of it as a C dialect that allows inline Smalltalk. Either way, it's trivial to use C (and C++) libraries from Objective-C(++).

The downside of this is that the memory model is a mess. You can store object pointers in C pointers and vice versa. Object pointers may contain valid objects, nil, or some other value.

With ARC, you now have two memory models: one for objects, and one for C. These are distinct: It’s possible to move values between them, but only via an explicit cast. Object pointers now always contain either a valid object or nil, unless explicitly marked as __unsafe_unretained. When you declare an object pointer on the stack, it will be initialized to nil if it is not assigned to before its first use.

This is part of the reason why strong object pointers are not allowed in C structures. These structures can be allocated with malloc(), and it's therefore not possible for ARC to guarantee that they are initialized with nil. It's also trivial to cast them, which is problematic because it makes it easy to hide the object pointers from ARC, and it's hard for ARC to ensure that the pointers are released before the structure is freed.

It is quite common to want to store Objective-C objects in C pointers, so ARC makes some allowances for this. For example, if you want to create a POSIX thread (rather than an NSThread) then you call pthread_create(). This takes a void* as the last argument. In non-ARC mode, you'd typically do something like this:

    pthread_create(&thread, NULL, startThread, anObject);

If you try that with ARC, then you will get an error like this:

error: implicit conversion of an Objective-C pointer to 'void *' is disallowed with ARC
        pthread_create(&thread, NULL, startThread, anObject);
                                                   ^~~~~~~~
1 error generated.

You need to use a bridged cast to move between the two memory models. The simplest form would be something like this:

pthread_create(&thread, NULL, startThread, (__bridge  void*)anObject);

This is equivalent to the original, but now that you're in ARC mode there's an additional caveat. As I discussed last weak, the ARC optimizer uses local balancing, and will assume that it can release anObject if this is the last reference to it. Unfortunately, pthread_create() knows nothing of objects, so won't retain the argument. By the time the thread has launched, the object may have been deallocated. The correct solution is this:

pthread_create(&thread, NULL, startThread, (__bridge_retained  void*)anObject);

This will call objc_retain() before passing the argument. Later, the optimizer will run and will probably note an unpaired retain and remove it and an objc_release() call, so the net result of this cast is to remove an objc_release() at the end of the scope. Now the object will leak, which is closer to what you wanted, but not quite right. The other part is the cast in the opposite direction. The startThread() function should start something like this:

    void* startThread(void *arg)
    {
        id anObject = (__bridge_transfer id)arg;
        arg = NULL;

This will place the object back under ARC's control. This form of bridged cast assumes that the object is already retained, and so will release it at the end of the scope. The arg = NULL line is not really required, but it's good style. You have placed the reference that arg held under ARC's control, so zeroing the pointer is clear. In this specific case, it doesn't matter because the object won't be deallocated until arg goes out of scope, but it does in pointers in structures.

ARC also respects the static analyzer annotations related to retain counts. If you declare a parameter with the ns_consumed attribute, then the function that clang emits in ARC mode will call objc_release() on that parameter. If you declare a function with the ns_returns_retained attribute, then the return value will not be autoreleased and will be an owning reference. It's important that both the declaration and definition have the same attributes. If you declare a function with this attribute then code using it will assume that it has to delete the reference. If the function then doesn't return an owning reference, then you will have memory corruption.

  • + Share This
  • 🔖 Save To Your Account