问题描述
根据 this answer 传递给可变参数函数的数字常量总是被视为 int
如果它们合二为一.这让我想知道为什么下面的代码同时适用于 int
和 long long
.考虑以下函数调用:
According to this answer numeric constants passed to variadic functions are always treated as int
if they fit in one. This makes me wonder why the following code works with both, int
and long long
. Consider the following function call:
testfunc(4, 1000, 1001, 1002, 1003);
testfunc
看起来像这样:
void testfunc(int n, ...)
{
int k;
va_list marker;
va_start(marker, n);
for(k = 0; k < n; k++) {
int x = va_arg(marker, int);
printf("%d\n", x);
}
va_end(marker);
}
这很好用.它打印 1000、1001、1002、1003.但令我惊讶的是,以下代码也能正常工作:
This works fine. It prints 1000, 1001, 1002, 1003. But to my surprise, the following code works as well:
void testfunc(int n, ...)
{
int k;
va_list marker;
va_start(marker, n);
for(k = 0; k < n; k++) {
long long x = va_arg(marker, long long);
printf("%lld\n", x);
}
va_end(marker);
}
这是为什么?为什么它也适用于 long long
?我认为数字整数常量是作为 int
传递的,如果它们合二为一?(参见上面的链接)那么它怎么可能也适用于 long long
呢?
Why is that? Why does it work with long long
too? I thought that numeric integer constants were passed as int
if they fit in one? (cf. link above) So how can it be that it works with long long
too?
哎呀,它甚至在 int
和 long long
之间交替时也能工作.这让我很困惑:
Heck, it's even working when alternating between int
and long long
. This is confusing the heck out of me:
void testfunc(int n, ...)
{
int k;
va_list marker;
va_start(marker, n);
for(k = 0; k < n; k++) {
if(k & 1) {
long long x = va_arg(marker, long long);
printf("B: %lld\n", x);
} else {
int x = va_arg(marker, int);
printf("A: %d\n", x);
}
}
va_end(marker);
}
这怎么可能?我以为我所有的参数都是作为 int
传递的...为什么我可以在 int
和 long long
之间任意来回切换,没有问题全部?我现在真的很困惑...
How can this be? I thought all my parameters were passed as int
... why can I arbitrarily switch back and forth between int
and long long
with no trouble at all? I'm really confused now...
感谢您对此有所了解!
推荐答案
这与 C 无关.只是你使用的系统 (x86-64) 在 64 位寄存器中传递前几个参数,即使用于可变参数.
That has nothing to do with C. It is just that the system you used (x86-64) passes the first few arguments in 64-bit registers, even for variadic arguments.
本质上,在您使用的架构上,编译器生成的代码为每个参数(包括可变参数)使用完整的 64 位寄存器.这是 ABI 商定的架构,与 C 本身无关;所有程序,无论如何生成,都应该遵循它应该运行的架构上的 ABI.
Essentially, on the architecture you used, the compiler produces code that uses a full 64-bit register for each argument, including variadic arguments. This is the ABI agreed upon the architecture, and has nothing to do with C per se; all programs, no matter how produced, are supposed to follow the ABI on the architecture it is supposed to run.
如果您使用 Windows,x86-64 使用 rcx
、rdx
、r8
和 r9
作为四个第一个(整数或指针)参数,按顺序排列,其余的堆栈.在 Linux、BSD、Mac OS X 和 Solaris 中,x86-64 使用 rdi
、rsi
、rdx
、rcx
、r8
和 r9
用于前六个(整数或指针)参数,按此顺序,其余为堆栈.
If you use Windows, x86-64 uses rcx
, rdx
, r8
, and r9
for the four first (integer or pointer) arguments, in that order, and stack for the rest. In Linux, BSD's, Mac OS X, and Solaris, x86-64 uses rdi
, rsi
, rdx
, rcx
, r8
, and r9
for the first six (integer or pointer) arguments, in that order, and stack for the rest.
您可以使用一个简单的示例程序来验证这一点:
You can verify this with a trivial example program:
extern void func(int n, ...);
void test_int(void)
{
func(0, 1, 2);
}
void test_long_long(void)
{
func(0, 1LL, 2LL);
}
如果您在 Linux、BSD、Solaris 或 Mac OS 中将上述代码编译为 x86-64 程序集(例如 gcc -Wall -O2 -march=x86-64 -mtune=generic -S
)(X 或更高版本),你得到大约(AT&T 语法,source,target 操作数顺序)
If you compile the above to x86-64 assembly (e.g. gcc -Wall -O2 -march=x86-64 -mtune=generic -S
) in Linux, BSDs, Solaris, or Mac OS (X or later), you get approximately (AT&T syntax, source,target operand order)
test_int:
movl $2, %edx
movl $1, %esi
xorl %edi, %edi
xorl %eax, %eax
jmp func
test_long_long:
movl $2, %edx
movl $1, %esi
xorl %edi, %edi
xorl %eax, %eax
jmp func
即函数是相同的,并且不将参数压入堆栈.注意jmp func
等价于call func;ret
,更简单.
i.e. the functions are identical, and do not push the arguments to the stack. Note that jmp func
is equivalent to call func; ret
, just simpler.
但是,如果您针对 x86 进行编译 (-m32 -march=i686 -mtune=generic
),您将获得大约
However, if you compile for x86 (-m32 -march=i686 -mtune=generic
), you get approximately
test_int:
subl $16, %esp
pushl $2
pushl $1
pushl $0
call func
addl $28, %esp
ret
test_long_long:
subl $24, %esp
pushl $0
pushl $2
pushl $0
pushl $1
pushl $0
call func
addl $44, %esp
ret
这表明 Linux/BSDs/etc. 中的 x86 调用约定.涉及在堆栈上传递可变参数,并且 int
变体将 32 位常量推送到堆栈(pushl $x
推送一个 32 位常量 x
到堆栈),并且 long long
变体将 64 位常量压入堆栈.
which shows that the x86 calling conventions in Linux/BSDs/etc. involve passing the variadic arguments on stack, and that the int
variant pushes 32-bit constants to the stack (pushl $x
pushes a 32-bit constant x
to the stack), and the long long
variant pushes 64-bit constants to the stack.
因此,由于您使用的操作系统和架构的底层 ABI,您的可变参数函数显示了您观察到的异常".要单独从 C 标准中看到您期望的行为,您需要解决底层的 ABI 怪癖——例如,通过使用至少六个参数启动可变参数函数,以占用 x86-64 架构上的寄存器,以便rest,您真正的可变参数,在堆栈上传递.
Therefore, because of the underlying ABI of the operating system and architecture you use, your variadic function shows the "anomaly" you observed. To see the behaviour you expect from the C standard alone, you need to work around the underlying ABI quirk -- for example, by starting your variadic functions with at least six arguments, to occupy the registers on x86-64 architectures, so that the rest, your truly variadic arguments, are passed on the stack.
这篇关于为什么我的可变参数函数可以同时使用 int 和 long long?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!