ARC & Malloc: EXEC_BAD_ACCESS
Asked Answered
M

2

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.

Mckenney answered 2/2, 2012 at 20:32 Comment(7)
Why the typedef? Object pointers that are not otherwise-qualified are assumed to be __strong.Decrepitude
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'.Mckenney
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()).Decrepitude
Filed <rdar://problem/10798342> asking the compiler to warn/error on this pattern.Uterus
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.)Tinned
@robmayoff: True, although it's not really a struct at that point, it's a class (since ARC will synthesize a non-trivial destructor).Decrepitude
@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.Mckenney
D
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.

Decrepitude answered 2/2, 2012 at 20:37 Comment(5)
dang. subtle. you really have to know a lot about retain/release/C to use ARC in this instance.Rosenda
@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.Decrepitude
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*)?Rosenda
@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.Decrepitude
@Rosenda 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.).Mckenney
T
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.

Tinned answered 2/2, 2012 at 20:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.