这是对问题 1 的更完整解释。与 Java、C#、Python、Ruby 等语言相比,C 和 Objective-C 是相当低级的语言。在 C 和 Objective-C 中,允许具有可变数量参数的函数(所谓的 varargs 或 variadic 函数),但该语言没有提供知道数量的机制实际传入的参数。高级语言通常通过将额外的参数公开为对象列表或数组来提供此功能; C 和 Objective-C 使用一组更有限的宏来一次访问一个额外的参数。
因此,任何可变参数函数都需要其他方法来确定传递的实际参数数量。 printf 和 scanf 系列函数及其同类使用格式字符串作为固定参数之一,格式字符串中的格式说明符指示传入的额外参数的数量和类型。-initWithObjects: 消息另一方面,Objective-C 类的NSMutableArray 要求额外参数列表以nil 结尾。因此,您不能将nil 作为参数传入(因为它会标记参数列表的结尾),但在这种情况下没关系,因为NSMutableArrays 指定它们不能包含nil 对象。
在 x86 架构上,可变参数函数是使用 C 调用约定 (cdecl) 实现的,即参数从右到左压入堆栈。调用者负责在调用后清理堆栈。因此,对printf() 的调用可能会被编译成如下内容:
// ignore the fact that these are invalid calls to printf
printf("format string 1", a1, a2);
printf("format string 2", b1, b2, b3);
// The above might get compiled into this:
push a2
push a2
push <address of "format string 1">
call printf
add $12. %esp // restore stack pointer to offset the pushes
push b3
push b2
push b1
push <address of "format string 2">
call printf
add $16, %esp // restore stack pointer
参数被从右到左而不是从左到右推送的原因是堆栈增长向下 -- x86 push 指令将堆栈指针 esp 递减4字节。因此,当被调用函数检查堆栈时,它将以正确的顺序查看参数。在第一次调用上面的printf 之后,堆栈看起来像这样:
%esp+0x00:返回地址给调用函数
%esp+0x04: "格式字符串1">的地址
%esp+0x08: a1
%esp+0x0c: a2
%esp+0x10 及以上:来自调用函数的局部变量,堆栈的其余部分
当被调用函数检查它的参数时,它只是遍历堆栈,但它不知道在哪里停止——调用约定没有指定有多少个参数。该功能只需要通过其他方式知道。大多数函数都有固定数量的参数,这对它们来说很容易,但可变参数函数需要其他方法来确定参数的确切数量。
如果函数认为传递的参数数量与实际传递的参数数量不匹配(例如,将错误的格式字符串传递给printf),则会导致错误。最可能的情况是该函数将继续向下读取堆栈,并且您将在某处获得垃圾值。更糟糕的是,您的程序可能会崩溃。更糟糕的是,您的程序可能看起来运行正常,但您可能无意中打开了一个安全漏洞。