可以安全地将 objc_msgsend 转换为可变长度参数函数

could cast objc_msgsend to variable-length arguments function safely

假设我在 运行 时得到了一个对象和它的选择器之一,我打算安全地调用它,所以我定义了

#define objc_msgsend_va ((void (*)(id, SEL, ...))objc_msgsend)

#define Call_object(obj, sel, ...) objc_msgsend_va(obj, sel, ## __VA_ARGS__)

我可以安全地用 Call_object 宏替换任何方法调用吗,否则它可能会导致某种崩溃。

这是为了在 iOS 设备上为 运行 任意 运行time 方法定义一个宏,我无法显式转换为每个方法正确的函数类型,所以我使用了变量-长度参数。

主要是考虑这样使用宏的安全性。

底线:

不,这绝对不安全。它适用于很多常见情况,但 va_args 与直接将参数传递给函数不同。

请,请,请,请使用 NSInvocation 它为您解决了大约 99% 的问题,同时更加安全。可能存在它无法解决的边缘情况(例如,参数列表中的 SSE/AVX 个向量),但它会比您可能一起破解的内容有突飞猛进的发展。

让我们按体系结构对其进行分解,看看这种可能 可能发挥作用的地方。

通用

这些问题会到处出现,没有真正的解决方案。

  1. 当作为 va_args 传递时,浮点数总是放大为双精度数。参见 the C standard,第 6.5.2.2 节

  2. shorts,chars也是如此。它们也会变大,通过时会造成很大的痛苦和痛苦。从技术上讲(与浮点数不同,因为它们有时会在特殊的 FPU 寄存器中传递),对于整数类型,您可以通过将它们包装在单个元素结构中来解决这个问题。这将阻止他们得到晋升。不过请不要这样做。

  3. 联合体,或复杂的结构体。需要额外的对齐和填充以及注意事项。如果你想要一个稳定的解决方案,你将不得不深入研究编译器代码。

  4. 如果您开始接触 return 值,事情会变得更加复杂,并且您需要了解架构何时将结构打包到 return 值的寄存器中(请参阅a lot of 之前的讨论)。

PowerPC

我不在意这个。如果您仍然需要定位这些 macOS 设备,请注意以下两点:

  1. 我为你感到难过

  2. 在这方面你可能比我有更多的背景知识。祝你好运,万事如意。

x86

32 位 x86 相当正常。没有复杂的寄存器参数传递,在我的脑海中,我觉得这可能是相当理智的。除了前面提到的问题之外,最大的问题是由 x87 FPU 引起的。避免花车,你可能不会在这里燃烧世界。

x86-64

64 位 x86 完全是另一种野兽。您将不得不学习很多有关编译器如何将寄存器分配给参数(和 return 值!)的知识,当然还有用于浮点的新 SSE 寄存器。

ARMv6/ARMv7/ARMv7s(32 位 ARM)

我把它们放在一起,从这个角度来看没有什么大的区别。

与 x86-64 一样,如果您想 100% 安全,您必须了解一些寄存器在堆栈中的位置,但如果记忆恢复的话,这里的浮点传递更简单。

Apple 的 ABI guide 是你的朋友。

ARM64

这是我的知识最不稳定的地方,如果这些信息有任何不正确的地方,我深表歉意。

大部分函数调用都是完全通过寄存器来完成的,所以我们之前的担心都还在。现在有新的 FPU 寄存器,你必须为你的浮点数处理,但如果你已经到了这一点,没有什么是不可逾越的。