24

ScreenShot : running mode of xCode 5.1

Why is it an exc_bad_access and not a run-time or compile-time error?

By mistake I wrote "@age" instead of @"age", and it sparked my curiosity.

What I understand of exc_bad_access is that : Bad-Access is caused by a pointer (okay reference) that is dereferenced to a memory location which is either not allocated yet or deallocated or unauthorized to access (const or something).

But in this case I am only writing data onto memory and the syntax doesn't match the NS Objective-c format. Hence it should be a run-time error instead of a Bad-Access.

Where am I missing the concept?

Neeku
  • 3,646
  • 8
  • 33
  • 43
  • My guess is this: the compiler expects an NSString at index 1, but it instead gets a c-string, so when it tries to access the data, it reads expecting there to be more data where there is none. BTW, compile-time safety for this could be enforced if you used `NSArray *arr = @[@"name", "@age", nil];`. Although, we know this is more about what's actually going on under the hood. – michaelsnowden Jul 04 '14 at 09:26
  • `exc_bad_access` is a run-time error. Do you mean run-time exception? – Bryan Chen Jul 06 '14 at 22:51
  • Well, it's not a "compile time" error because it didn't occur during compilation. It *is* a "runtime" error because it occurs while the app is running. It's just a particular type of runtime error. – Hot Licks Jul 20 '14 at 21:30
  • @doctordoder -- Actually, that NSArray constructor would throw an error due to the presence of the `nil` operand, even if `@"age"` were correct. – Hot Licks Jul 20 '14 at 21:31
  • @HotLicks You're right, sorry. `NSArray *arr = @[@"name", "@age"];` wold be fine though. – michaelsnowden Jul 20 '14 at 21:47
  • @doctordoder - And of course that would either produce a compile-time error or throw a runtime access error, due to the non-object in the list. I'm not sure which since I don't know how "smart" they've made the compiler. – Hot Licks Jul 20 '14 at 21:49

4 Answers4

24

The reason you get EXC_BAD_ACCESS is that the -initWithObjects: method expects all of its arguments to be valid Objective-C objects. Each Objective-C object starts with a small header; this used to be a straightforward pointer, called isa, to its class object (it isn't necessarily quite this simple any more, and these days you shouldn't poke about yourself; there are Objective-C runtime APIs you can use instead if necessary).

The reason you don't get a compiler error here is that there is no way in C/C++/Objective-C to specify the correct types for a "varargs" method or function. As a result, the compiler allows you to pass arguments of any type, assuming you know what you’re doing.

Anyway, within the implementation of -initWithObjects:, it’s going to try to send a -retain message to each of the objects you pass in. When it does that, it's going to try to dereference the isa pointer. In the case of your C string, that means it's going to treat the first four or eight bytes of the string as a pointer. This is very unlikely to have a good outcome, and very likely you'll get EXC_BAD_ACCESS straight away. Even if you were lucky and they do happen to point to valid memory, the Objective-C runtime is going to expect them to point to a valid Class structure, which is tremendously unlikely, and the result of that is also very probably going to be an EXC_BAD_ACCESS.

al45tair
  • 4,405
  • 23
  • 30
3

In fact, "@age" is a const char* so it seems to match your description of what is a exc_bad_access.

KIDdAe
  • 2,714
  • 2
  • 22
  • 29
3

As mentioned @"age" is a shortcut for creating an NSString * - which again is a subclass of NSObject. The @ in front of the string tells the compiler to create and return an NSString *.

"@age" on the other hand does not have the @ prefix, and therefore the compiler creates a const char * which is what C uses to represent strings. Remember that Objective-C is a strict superset of C, which means that any C code will compile on an Objective-C compiler. This also means that the compiler needs to maintain backwards compatibility for C - which again means that "@age" is a C string while @"age" is an Objective-C Foundation Kit NSString object.

In regards to your question: The reason why this is not a compiler error, is because the initWithObjects: initializer of NSArray does not specify the required type. It simply says a list of pointers. And because both @"age" (NSString *) is a pointer and the "@age" (const char *) is also a pointer, the compiler won't complain. Both are by the way the initWithObjects: is defined allowed by the compiler.

Now you say why it is not a run-time error. EXC_BAD_ACCESS IS a run-time error. You might mean why there is not an exception thrown, as there is a difference in exceptions and run-time errors. Exceptions are run-time errors also, though, but they can be detected and acted upon. Signals (which a EXC_BAD_ACCESS is) can typically not be acted upon and the application is killed by the operating system.

The reason why it is a run-time error, is because the initWithObject: are expecting a list of NSObjects and the first thing this function does is to do some inspection or work on the objects provided. Now internally in the Objective-C run-time it is basically a C struct with information about the objects methods and variables. These structs typically contains as an example a pointer to the super class of the object. What you are giving this method is basically garbage to it. When it tries to examine what it believes is a struct with pointers and data - all it gets is a four byte buffer ("age\0"). So when it tries to — as an example dereference a super class pointer in what the method believes is a struct – it will read outside of that four byte buffer and therefore your application will receive a EXC_BAD_ACCESS.

So there you have it. From a compiler stand point you are doing nothing wrong. From the runtime standpoint you are feeding it garbage, which it has no means of detecting. It just goes about working on the data as if it is what it expects. And when it does that, it goes outside the boundaries of the buffer you have provided and therefore gets a EXC_BAD_ACCESS.

Hope this clarified your curiosity.

Trenskow
  • 3,783
  • 1
  • 29
  • 35
1

The syntax @"name" is a string literal and is the same as saying [[NSString alloc] initWithUTF8String:"name\0"]; which makes it an object

"@age" on the other hand, is a const char*. NSArray can only handle objects, so giving it a const char* will cause your app to crash. I'm not sure why the static analyzer doesn't catch this issue in the first place, I'm surprised there isn't at least a warning telling you to use a literal, such as with NSLog();

Literphor
  • 498
  • 4
  • 16