多次调用可变参数Obj-C方法会导致出现无效参数

时间:2019-02-28 22:04:31

标签: objective-c variadic-functions

我有一个可变参数方法,我想将多个枚举值传递给:

typedef NS_ENUM(NSInteger, Enum) {
    Enum0 = 0,
    Enum1 = 1,
    Enum2 = 2,
    Enum3 = 3,
    Enum4 = 4,
    Enum5 = 5,
    Enum6 = 6,
};

@interface Variadics : NSObject
- (void)test:(Enum)enm, ...;
@end

这是实现

@implementation Variadics
- (void)test:(Enum)enm, ... {
    va_list args;
    va_start(args, enm);
    for (; enm != 0; enm = va_arg(args, Enum))
    {
        NSAssert(enm <= Enum6, @"invalid enum");
    }
    va_end(args);
}
@end

这就是我的称呼:

Variadics* var = [[Variadics alloc] init];
[var test:Enum1, Enum2, Enum3, Enum4, Enum5, Enum6, 0];

效果很好!但是,如果我将其更改为:

Variadics* var = [[Variadics alloc] init];
[var test:Enum1, Enum2, Enum3, Enum4, Enum5, Enum6, 0];
[var test:Enum1, Enum2, Enum3, Enum4, Enum5, Enum6, 0];
[var test:Enum1, Enum2, Enum3, Enum4, Enum5, Enum6, 0];
[var test:Enum1, Enum2, Enum3, Enum4, Enum5, Enum6, 0];

我突然打断了那个断言和enum == 4294967296。甚至更奇怪,我在第一个电话上遇到了断言 。之后的三个呼叫甚至都无法运行。

这是怎么回事?

1 个答案:

答案 0 :(得分:3)

您在参数列表末尾的零终止符将作为int(32位)值传递给您的函数,但是您的va_arg正在提取NSInteger( aka long)值。因此,您要从堆栈中吸收另外的32位,并将其全部视为一个64位值,其中一半是您想要的零,而另一半则是存储器中与它相邻的所有内容。那个时候。

您必须执行此操作才能获得所需的行为:

[var test:Enum1, Enum2, Enum3, Enum4, Enum5, Enum6, (NSInteger)0];

正如Rob Mayoff在下面的评论中阐明的那样,未铸造的零文字(是整数值)被视为int。普通C整数提升规则适用*;在varargs列表中,因为参数没有声明的类型,所以较小的整数类型将提升为int并作为int传递。由于编译器无法查看您希望在运行时看到的实际类型,因此不会long提升为您的int,因此会以printf的形式出现在堆栈中。

通常,varargs终止要么隐式完成(nil样式表示的arg数量和类型),要么使用int这样的常数,它将是正确的宽度,这些方法可以避免此问题。在旧的32位世界中,枚举值是NSInteger s, intint,默认的整数提升也是 enum == 4294967296,这些区别被隐藏了。

实际上,这对您的代码设计可能意味着您可能会保留一个特殊的哨兵枚举值(不一定为零)以用作列表终止符,以确保其类型正确。或者,您可能需要修改以在函数调用之前添加参数数量。

*请参阅C18标准§6.3.1.1;-)


奖励说明:您为什么看到值0x0000000100000000

十进制数4294967296等于1。其中的第二个(下半部分)是您放置的32位零。上半部分看起来只是数字-test:...。最初,我认为这将是当前堆栈帧的其他(有效)部分,但是一些调查(在使用当前llvm和Xcode等的64位Mac上)表明编译器设置了对{{1 }},方法是使用movq推送枚举args(移动四边= 64位),最后使用movl推送最后的零arg(移动long = 32位)。由于堆栈是64位对齐的,因此在倒数第二个和终端args之间的堆栈存储器中会留下32位的“空位”(即,不会被覆盖)。这就是包含0x1的位置。因此,您不会读取相邻的参数或其他有效但用于其他用途的值。您正在从某个较早的函数调用的工作空间中读取幻影值。在我的调试中,它似乎不是来自Variable的alloc / init -它是在先的,与手头的测试代码无关。