7

This code gives me EXC_BAD_ACCESS, why?

NSMutableDictionary  *d = [[NSMutableDictionary alloc] init];
IMP imp= [d methodForSelector:@selector(setObject:forKey:) ];
imp(d,  @selector( setObject:forKey:), @"obj", @"key");

I'm just starting using IMP, firs try.. no luck. Not sure why I get the error, also.. in the past, when I got EXC_BAD_ACCESS, the message was printed at the console, this time the error line is highlighted.

Some notes: ARC is enabled, XCode 4.3.2, the project uses Objective-C++ as de default language/compiler,this code is at the very beginning of the project

thanks guys

Lebyrt
  • 1,376
  • 1
  • 9
  • 18
subzero
  • 3,420
  • 5
  • 31
  • 40
  • I can't reproduce. If you put this in a fresh project, do you still get the error? – Chuck Jun 08 '12 at 17:18
  • Yep, what I have found... ARC is the problem. Try creating a new iOS project with ARC enabled. Then copy & paste the code somewhere (I put it in application didFinishLaunchingWithOptions:) – subzero Jun 08 '12 at 17:22

2 Answers2

21

You need to cast the function pointer properly or ARC doesn't know what it's supposed to be doing. IMP is a generic function pointer that takes an id, a selector and a variable number of other, undefined arguments and returns an id. The method implementation you're trying to call takes an id, a selector followed by exactly two id parameters and has a void return type. You can fix it by changing to the following code:

NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
void (*imp)(id, SEL, id, id) = (void(*)(id,SEL,id,id))[dict methodForSelector:@selector(setObject:forKey:)];
if( imp ) imp(dict, @selector(setObject:forKey:), @"obj", @"key");

You should always check that you actually got a function pointer back before you dereference it, as that would also crash. The code above will work even in an ARC environment. Also, even when you're not using ARC, you should always cast your function pointers to the actual prototype rather than IMP. You should never use IMP. Other places that would cause major issues are if the method returns a struct or if the method takes floating point parameters, etc.

Good habit: always cast your function pointers or make typedefs for them if you find the function pointer syntax jarring.

Jason Coco
  • 77,985
  • 20
  • 184
  • 180
  • Thanks Jason. It worked as a charm!. Question: why would it cause a major issue with structs or methods with floating point parameters?, if you could point me to some document I will be happier. I just found the "runtime" topic interesting. – subzero Jun 08 '12 at 17:38
  • 1
    It has to do with the calling conventions of functions in C and how parameters are passed. Since the compiler doesn't know anything about the parameters of IMP past the `self` and `_cmd` parameters, it will just push other parameters onto the stack and expect the called function to know what to do with them. However, if the called function expected a float, it will almost certainly look in the GPR (for ARM) or other fp-specific register and not on the stack, etc. It's the same for return types, structures are handled differently than pointers. – Jason Coco Jun 08 '12 at 18:35
1

The problem is that IMP has a return type of "id" which ARC will attempt to manage. You need to cast your function pointer to have a return type of void (consistent with the method you are calling):

    NSMutableDictionary  *d = [[NSMutableDictionary alloc] init];
    IMP imp= [d methodForSelector: @selector(setObject:forKey:)];
    void (*func)(__strong id,SEL,...) = (void (*)(__strong id, SEL, ...))imp;
    func( d,  @selector(setObject:forKey:), @"obj", @"key" );
Tom Pelaia
  • 1,275
  • 10
  • 9
  • `void (*)(id, SEL, ...)` is not the right type for the function pointer. It is `void (*)(id, SEL, id, id)`. Although it may work in this particular case on certain architectures with certain types (`id`), it is incorrect in general. – newacct May 08 '14 at 21:05