c - 可变参数变量如何在堆栈上表示?
问题描述
我试图使用这段代码实现我自己的可变参数函数。相反,我得到了 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
呢?是否不期望参数1
( of test()
) 将写入函数堆栈帧中的低地址 ex: 0000 0004
(因为0000 0000
将保留用于返回地址)然后arg_a
在第一个 arg 之后的较高地址中?
我猜这个结果是因为编译器优化,还是有别的原因?
解决方案
是否不期望参数1(test())将被写入函数堆栈帧中的低地址例如:0000 0004(因为0000 0000将保留用于返回地址)然后是arg_a在较高地址中紧随其后参数?
很久以前它就是这样工作的,但基本上所有用于全脂处理器(与微控制器相反)的ABI,自 1990 年代中期定义以来,都将前几个参数放在寄存器中,以使函数调用更快。无论被调用者是否可变参数,他们都会这样做。您无法使用指针算术访问寄存器,因此您尝试做的事情完全不可能。由于此更改,如果您查看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 是在 1980 年代后期定义的(与 80386 和 80486 是同时代的)并且它们确实将所有参数都放在堆栈上。我记得的唯一例外是Win32“fastcall”。然而,64 位 x86 ABI 是在 2000 年代初(与 AMD K8 同期)定义的,它们将参数放入寄存器中。
即使您为 32 位 x86(或任何其他将所有参数放在堆栈上的旧 ABI)编译它,您的代码也不会可靠地工作,因为它违反了 C 标准中关于偏移指针的规则。指针arg_b
不指向“内存中发生的任何事情a
”,它指向空。(正式地,它指向一个元素越过一个元素数组的末尾,因为所有非数组对象都被视为一个元素数组的唯一元素,用于指针算术目的。您可以执行计算这个的算术指针,但不能取消引用它。)取消引用arg_b
会给程序带来未定义的行为,这意味着允许编译器任意“错误编译”它。
推荐阅读
- php - "Error": "Unrecognized field: 0 when try to findBy ASC ORM/Doctrine
- google-people-api - 使用谷歌人员服务的人员 API 搜索联系人
- ios - xcode 9上的alamofire pod问题
- javascript - 正则表达式捕获超链接
- ethernet - 将 enc28j60 以太网模块与 esp32 连接
- email - 无法与主机 smtp.gmail.com 建立连接:stream_socket_client():
- react-native - 如何在本机反应中使用两个不同的 DateTimePicker 查找两个日期之间的差异
- python - Python Pandas Group by 并添加带有数字序列的新行
- c++ - dart 和 Qt/C++ 之间的 TCP 套接字连接关闭连接,删除旧的 QtcpSocket 对象
- javascript - Radio 组件内的 Select Dropdown 短暂出现后立即消失