5

I have been working on a project for some time now, and I decided to make the jump to ARC. I came across some code that was bombing out every time, and I would like to know why. I have managed to simplify it down to this snippet:

typedef __strong id MYID;

int main(int argc, char *argv[])
{ 
    MYID *arr = (MYID *) malloc(sizeof(MYID) * 4);

    arr[0] = @"A";     // always get an EXEC_BAD ACCESS HERE
    arr[1] = @"Test";
    arr[2] = @"Array";
    arr[3] = @"For";

    // uh oh, we need more memory
    MYID *tmpArray = (MYID *) realloc(arr, sizeof(MYID) * 8);
    assert(tmpArray != NULL);

    arr = tmpArray;

    arr[4] = @"StackOverflow";  // in my actual project, the EXEC_BAD_ACCESS occurs here
    arr[5] = @"Is";
    arr[6] = @"This";
    arr[7] = @"Working?";

    for (int i = 0; i < 8; i++) {
        NSLog(@"%@", arr[i]);
    }

    return 0;
}

I'm not quite sure what is happening here, tired this in 4 different projects, and they all fail. Is there something wrong with my malloc call? Sometimes it returns null, and other times it returns a pointer that I can't access.

Brad Larson
  • 170,088
  • 45
  • 397
  • 571
Richard J. Ross III
  • 55,009
  • 24
  • 135
  • 201
  • Why the typedef? Object pointers that are not otherwise-qualified are assumed to be `__strong`. – Lily Ballard Feb 02 '12 at 20:36
  • Because in the actual project, `MYID` is a part of a struct (cocos2d's ccCArray). Also, the code will not compile without a qualifier for ownership, because it isn't part of a selector where the owner could be 'self'. – Richard J. Ross III Feb 02 '12 at 20:37
  • I'm not sure what you mean by "because it isn't part of a selector where the owner could be 'self'". The "owner" of an object allocated in a method is the stack itself, not the value of `self`. Also note that you cannot place `__strong` (or `__weak`) object pointers into a C struct under ARC, you must use `__unsafe_unretained` and manage the memory explicitly (e.g. with some non-ARC code or with `CFRetain()`/`CFRelease()`). – Lily Ballard Feb 02 '12 at 20:40
  • 2
    Filed asking the compiler to warn/error on this pattern. – bbum Feb 02 '12 at 20:41
  • 1
    Or you can use Objective-C++, which supports ARC pointers in structs. (They will be non-POD structs if you put ARC pointers in them.) – rob mayoff Feb 02 '12 at 20:43
  • @robmayoff: True, although it's not really a struct at that point, it's a class (since ARC will synthesize a non-trivial destructor). – Lily Ballard Feb 02 '12 at 20:45
  • @robmayoff good point, but the cocos2d code would have to fundamentally change if I used ObjC++, and I don't have time to deal with that. – Richard J. Ross III Feb 02 '12 at 20:46

2 Answers2

13

The crash is because you're casting malloc'd memory to a C array of objects. The moment you try to assign to one of the slots, ARC will release the previous value, which will be garbage memory. Try using calloc() instead of malloc() to get zeroed memory and it should work.

Note that your realloc() call will also not zero-fill any new memory that's allocated, so if you need the realloc() then you may want to be using a temporary void* pointer that you then zero-fill manually before assigning to your object array.

Lily Ballard
  • 182,031
  • 33
  • 381
  • 347
  • 3
    dang. subtle. you really have to know a lot about retain/release/C to use ARC in this instance. – nielsbot Feb 02 '12 at 20:39
  • @nielsbot: As long as you stick to Obj-C you shouldn't have a problem. This sort of thing only crops up when trying to combine basic C stuff (like `malloc()`) with ARC. – Lily Ballard Feb 02 '12 at 20:41
  • also, if the malloc'd array is just for temporary storage of `id`'s for the scope of the function couldn't you use `(__unsafe_unretained MYID*)` instead of `(MYID*)`? – nielsbot Feb 02 '12 at 20:42
  • 1
    @nielsbot: I admit I'm not quite sure what ARC does if you alloc/init an object and assign it to an `__unsafe_unretained` slot. I think it ignores those references entirely, so it'll probably release the object. But using `__autoreleasing` would probably work. Although if you're using ARC then you might want to preserve the explicit lifetime semantics. I know I personally would just make sure the memory is zero-filled if I needed to use this idiom. – Lily Ballard Feb 02 '12 at 20:45
  • @nielsbot the problem is, it isn't temporary storage, it is using strong objects for a reason, it is an array which manages things about nodes (children, layers, etc.). – Richard J. Ross III Feb 02 '12 at 20:47
8

The malloc function does not zero the memory it allocates. The memory can contain random garbage.

From the Clang Automatic Reference Counting guide, section 4.2:

For __strong objects, the new pointee is first retained; second, the lvalue is loaded with primitive semantics; third, the new pointee is stored into the lvalue with primitive semantics; and finally, the old pointee is released.

So what's probably happening here is malloc is returning memory that contains random non-zero values. ARC tries to use that random value as a pointer to an object and release it, but it's not a valid object pointer. Crash.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848