我试图用这段代码实现我自己的variadic函数。相反,我得到了UB。
#include <stdio.h>
void test(int a, ...)
{
char* arg_a = (char*)&a;
char* arg_b = arg_a + sizeof(int);
printf("%c", *arg_b);
}
int main(){
test(1, 'a');
}
那么,为什么这个程序不打印字母a
呢?难道不希望(test()
的(参数1
将被写入函数堆栈帧中的低地址(例如:0000 0004
(因为0000 0000
将保留为返回地址(,然后在第一个参数后面的较高地址中写入arg_a
吗?
我想这个结果是因为编译器优化的原因,还是其他原因?
是否预期参数1(test(((将写入函数堆栈帧中的低地址ex:00000 0004(因为0000 0000将保留为返回地址(,然后在第一个arg之后的较高地址中后跟arg_a?
这是很久以前的工作方式,但从本质上讲,自20世纪90年代中期以来定义的所有用于全速处理器(与微控制器相反(的ABI都将前几个参数放在寄存器中,以使函数调用更快。他们这样做无论被调用者是否可变。你不能用指针算术访问寄存器,所以你想做的事情完全不可能。由于这个变化,如果您查看当前一代编译器提供的stdarg.h
的内容,您会发现va_start
、va_arg
和va_end
是使用编译器内部函数定义的,类似
#define va_start(ap, last_named_arg) __builtin_va_start(ap, last_named_arg)
// etc
您可能会混淆,以为自变量仍然存在于堆栈中,因为大多数32位x86 ABI是在20世纪80年代末定义的(与80386和80486同期(,并且它们确实将所有自变量都放在堆栈中。我现在唯一记得的例外是Win32";快速呼叫";。然而,64位x86 ABI是在21世纪初定义的(与AMD K8同期(,它们将参数放入寄存器中。
即使为32位x86(或任何其他将所有参数都放在堆栈上的旧ABI(编译代码,您的代码也不会可靠地运行,因为它违反了C标准中关于偏移指针的规则。指针CCD_ 11不指向"0";不管发生在CCD_ 12"之后的存储器中的是什么;,它指向nothing。(从形式上讲,它将一个元素指向单元素数组的末尾,因为出于指针算术的目的,所有非数组对象都被视为单元素数组中的唯一元素。您可以进行计算该指针的算术运算,但不能取消引用它。(取消引用arg_b
会给程序带来未定义的行为,这意味着编译器被允许任意地";错误编译";