Mac M1平台gcc-11调用va_arg读取可变参数值异常问题
va_arg读取M1设备gcc-11编译程序可变参数异常问题
问题现象
在搭载M1芯片的Mac设备上使用va_arg读取函数可变参数时出现异常:使用Apple clang运行代码输出结果符合预期,同设备使用Homebrew安装的gcc-11运行相同代码时,会读取到无效乱码值。
测试代码如下:
#include <stdio.h> #include <stdarg.h> #define ND_indices size_t void llF(ND_indices rank, ...){ /* Get all the args of type ND_indices*/ ND_indices arg_val; va_list ap; va_start(ap, rank); for (ND_indices i=1; i<=rank; ++i){ arg_val = va_arg(ap, ND_indices); printf("%lu \n",arg_val); } va_end(ap); } int main() { llF(10,3,2,4,2,3,4,4,6,7,6); //printf("Hello World"); return 0; }
运行结果对比
- 使用Apple clang version 13.1.6 (clang-1316.0.21.2.5)编译运行,输出符合预期:
3 2 4 2 3 4 4 6 7 6
- 使用Homebrew安装的gcc-11在M1版Mac上编译运行,输出乱码值:
3 2 4 4294967298 4294967299 4294967300 4294967300 9331036219741569030 4294967303 4294967302
复现条件说明
- 编译选项:
gcc-11 test.c -Wall - 特殊规律:即使用gcc-11编译,当传入3个可变参数时代码可正常运行
- 交叉验证结果:在x86架构设备上分别使用clang、gcc-10、icc编译运行该代码,以及多个在线C编译器测试,均未复现该问题。
问题根因
该问题本质是未定义行为触发的编译器实现差异:
- 代码中传入可变参数的字面量
3,2,4...默认是int类型(32位宽度),但va_arg读取时指定的类型是size_t(ARM64平台下为64位无符号整型)。C标准明确要求va_arg读取的类型必须和实际传入参数的类型(经过默认参数提升后)严格匹配,否则行为未定义。注意默认参数提升仅会将宽度小于int的类型转换为int、float转换为double,int类型本身不会自动扩展到64位。 - 不同平台/编译器的ABI实现差异导致表现不同:
- x86_64平台的System V ABI要求所有整数参数传递时都扩展到64位宽度,高32位自动清零,因此误读64位时不会出现乱码
- Apple clang遵循Darwin ARM64 ABI规范,对传入可变参数列表的
int类型值会自动零扩展到64位,因此读取64位值结果正常 - M1平台上Homebrew安装的gcc-11对可变参数的实现没有做上述自动扩展,
int类型参数仅占32位宽度,剩余高32位为寄存器/栈上的残留垃圾值,按64位读取时就会把垃圾值一并读出,出现乱码
- 传入3个可变参数时运行正常属于巧合:ARM64架构下函数参数优先通过X0-X7寄存器传递,前几个参数所在寄存器的高32位刚好为0,没有触发垃圾值读取。
修复方案
传入可变参数时,显式将参数转换为和va_arg读取一致的size_t类型,避免类型不匹配:
// 方式1:强转类型 llF(10, (size_t)3, (size_t)2, (size_t)4, (size_t)2, (size_t)3, (size_t)4, (size_t)4, (size_t)6, (size_t)7, (size_t)6); // 方式2:直接使用匹配宽度的字面量后缀 // llF(10, 3UL, 2UL, 4UL, 2UL, 3UL, 4UL, 4UL, 6UL, 7UL, 6UL);
内容的提问来源于stack exchange,提问作者Aditya Kurrodu
相关产品推荐
相关产品推荐

