使用objc_msgSendSuper_stret会引发`EXC_BAD_ACCESS(code = EXC_i386_GPFLT)`

时间:2014-09-07 12:02:20

标签: objective-c

我使用objc_msgSendSuperobjc_msgSendSuper_stret来调用超类的超类实现。

objc_msgSendSuper效果如下:

struct objc_super supersSuper;
supersSuper.receiver = self;
supersSuper.super_class = object_getClass(class_getSuperclass(class_getSuperclass(self)));  

return objc_msgSendSuper(&supersSuper, _cmd);

但使用objc_msgSendSuper_stret会引发运行时错误EXC_BAD_ACCESS(code=EXC_i386_GPFLT)

// In Tier3 class
+ (CGPoint) point{
    struct objc_super supersSuper;
    supersSuper.receiver = self;
    supersSuper.super_class = object_getClass(class_getSuperclass(class_getSuperclass(self)));  

    // raise EXC_BAD_ACCESS(code=EXC_i386_GPFLT) when invoking objc_msgSendSuper_stret
    return ((CGPoint(*)(struct objc_super *, SEL))objc_msgSendSuper_stret)(&supersSuper, _cmd); 
}

选择器确实存在于目标类中:

[Tier1 point]; // return {10, 10}

我看到很多objc_msgSendobjc_msgSendSuperobjc_msgSend_stret示例,但很少使用objc_msgSendSuper_stret找到示例。


更新

我发现objc_msgSend_stret的旧API如下:

void objc_msgSend_stret(void * stretAddr, id theReceiver, SEL theSelector,  ...)

但是当前的API是:

void objc_msgSend_stret(id self, SEL op, ...)

我认为Apple确实改变了objc_msgSend_stret的功能签名。不是吗?


更新-2

我确实尝试了以下内容:

CGPoint retVal = CGPointMake(0, 0);
// Cast to old API signature.
((void(*)(CGPoint *, id, SEL))objc_msgSend_stret)(&retVal, self, @selector(point));

return retVal;

此转换将使函数objc_msgSend_stret调用成功。确实调用了目标方法,目标方法返回{10,10}。但是我们得到的返回值不正确,它仍然是{0,0}。

顺便说一下,我发现代码无法在32位架构下工作(仍然会引发EXC_BAD_ACCESS(code=EXC_i386_GPFLT))。它只能在64位架构下运行。


更新-3

班级声明:

#import <objc/runtime.h>
#import <objc/message.h>

@interface TestClass : NSObject
+ (CGRect)rect;
+ (CGPoint)point;
@end
@implementation TestClass
+ (CGRect)rect;{
    NSLog(@"+[TestClass rect] is invoked");
    return CGRectMake(3, 3, 3, 3);
}
+ (CGPoint)point;{
    NSLog(@"+[TestClass point] is invoked");
    return CGPointMake(10, 10);
}

+ (CGRect)rectRuntimeTest;{
    CGRect retVal = CGRectMake(0, 0, 0, 0);
    ((void(*)(CGRect *, id, SEL))objc_msgSend_stret)(&retVal, self, @selector(rect));
    return retVal;
}

+ (CGPoint)pointRuntimeTest{
    CGPoint retVal = CGPointMake(0, 0);
    ((void(*)(CGPoint *, id, SEL))objc_msgSend_stret)(&retVal, self, @selector(point));
    return retVal;
}
@end

application:didFinishLaunchingWithOptions:中,测试以下内容:

CGRect retRect = [TestClass rectRuntimeTest];
NSLog(@"retVal: %@", NSStringFromCGRect(retRect));

CGPoint retPoint = [TestClass pointRuntimeTest];
NSLog(@"retPoint: %@", NSStringFromCGPoint(retPoint));

输出(在64位架构iOS模拟器下):

> +[TestClass rect] is invoked
> retVal: {{3, 3}, {3, 3}}
> +[TestClass point] is invoked
> retPoint: {0, 0}  // Should be {10, 10} which is returned from +[TestClass point]

输出(在32位架构iOS模拟器下):

`EXC_BAD_ACCESS (code=2, address=0xbfffca78)`

2 个答案:

答案 0 :(得分:0)

this问题看,CGPoint似乎是一个“简单”的返回值,因此在这种情况下您应该使用objc_msgSendSuper。返回CGRect时,您显然应该使用objc_msgSendSuper_stret

关于_stret变种的奇怪签名,不要让私人签名打扰你,依赖公共签名是正确的。详细展示了here

除此之外,你应该总是将objc_msg * Send *转换为非变量(即所有参数定义良好)的形式,并调用它(你在某些地方做,但你应该在所有地方)

答案 1 :(得分:0)

我可能错了,但我做了一些研究,当结构在寄存器中返回时,所谓的“简单”返回值是“简单的”。

如果根据这些ABI符合某些标准标准,POD(普通旧数据)结构将通过寄存器返回:

因此,为了总结一切,我们必须在以下时间使用objc_msgSend_stret函数:

  • x86和sizeof(struct) == 1 | 2 | 4 | 8
  • x86_64和sizeof(struct) > 16
  • armv7和sizeof(struct) > 4
  • arm64和sizeof(struct) > 64

否则只需使用objc_msgSend

我对arm64并不是100%肯定,因为标准太过抽象了:)

相关问题