C++ 是否使用Visual C++;为DWORD推送8个字节?(若然,原因为何?)

C++ 是否使用Visual C++;为DWORD推送8个字节?(若然,原因为何?),c++,stack,64-bit,C++,Stack,64 Bit,在不知情的情况下,我首先编写了一些涉及long变量的代码。我错误地将其视为long,将其传递给printf,后者实际打印了它包含的值(因为高位都是零) 最后,我发现我使用的是long,这让我对为什么printf对我所犯的错误如此有弹性产生了兴趣。最后我写了这样一篇文章: intmain() { 长a=1; 长b=2; 长c=3; 长d=4; 长e=5; 长f=6; 长g=7; 长h=8; 长i=9; 长j=10; printf(“%d%I64d%d%d%I64d%d%d%I64d%d%I64d\

在不知情的情况下,我首先编写了一些涉及
long
变量的代码。我错误地将其视为
long
,将其传递给
printf
,后者实际打印了它包含的值(因为高位都是零)

最后,我发现我使用的是
long
,这让我对为什么
printf
对我所犯的错误如此有弹性产生了兴趣。最后我写了这样一篇文章:

intmain()
{
长a=1;
长b=2;
长c=3;
长d=4;
长e=5;
长f=6;
长g=7;
长h=8;
长i=9;
长j=10;
printf(“%d%I64d%d%d%I64d%d%d%I64d%d%I64d\n”,a、b、c、d、e、f、g、h、i、j);
printf(“%I64d%I64d%I64d%I64d%I64d%I64d%I64d%I64d%I64d%I64d%I64d\n”,a、b、c、d、e、f、g、h、i、j);
printf(“%d%d%d%d%d%d%d%d%d%d%d\n”,a、b、c、d、e、f、g、h、i、j);
getchar();
返回0;
}
第一个
printf
s将格式字符串后面的每个后续参数类型的正确格式说明符关联起来。第二个为
long
应用说明符,第三个为普通的
long
(准确地说,是
int
,对吧?)

以下是输出:

1 2 3 4 5 6 7 8 9 10
1 2 3 -3689348818177884156 5 -3689348818177884154 -3689348818177884153 8 -3689348818177884151 10
1 2 3 4 5 6 7 8 9 10
现在这让我感到困惑,因为我希望调用
printf
的编译代码将
long
s的参数推到堆栈上四个字节,将
long
s的参数推到堆栈上八个字节。但是,如果是这样的话,
printf
代码在处理第一个不正确的格式说明符之后,可能会在错误的位置查找。然而,正如我的输出所示,
printf
从未丢失,在每种情况下都将匹配的说明符与其参数相关联。只有第二行无法打印正确的值,但这是一个非常有意义的原因,但也引出了我的问题

为了找出发生了什么,我让编译器(VS2015的VC++,x64)生成了一个程序集列表。下面是对
printf
的第一个调用:

; 20   :    printf("%d %I64d %d %d %I64d %d %d %I64d %d %I64d\n", a, b, c, d, e, f, g, h, i, j);

    mov rax, QWORD PTR j$[rbp]
    mov QWORD PTR [rsp+80], rax
    mov eax, DWORD PTR i$[rbp]
    mov DWORD PTR [rsp+72], eax
    mov rax, QWORD PTR h$[rbp]
    mov QWORD PTR [rsp+64], rax
    mov eax, DWORD PTR g$[rbp]
    mov DWORD PTR [rsp+56], eax
    mov eax, DWORD PTR f$[rbp]
    mov DWORD PTR [rsp+48], eax
    mov rax, QWORD PTR e$[rbp]
    mov QWORD PTR [rsp+40], rax
    mov eax, DWORD PTR d$[rbp]
    mov DWORD PTR [rsp+32], eax
    mov r9d, DWORD PTR c$[rbp]
    mov r8, QWORD PTR b$[rbp]
    mov edx, DWORD PTR a$[rbp]
    lea rcx, OFFSET FLAT:??_C@_0CL@GHCEKCAD@?$CFd?5?$CFI64d?5?$CFd?5?$CFd?5?$CFI64d?5?$CFd?5?$CFd?5?$CFI64d@
    call    printf
现在,我已经很长时间没有做汇编编程了,但是,如果我读得正确的话,从右到左的每个参数似乎都被放进了内存位置,相对于
rsp
寄存器,每个寄存器之间相隔八个字节,无论值是否需要8个字节来表示。(我注意到这句话实际上不适用于保存在寄存器而不是内存中的值
a
b
c
;可能是对短参数列表的优化?)

因此,这一切都是有意义的:
printf
永远不会丢失,因为它知道(或者编写它的编码人员知道)每个参数都会在距离它的邻居8个字节的地方找到,而不管该参数的大小如何。(我认为,少数打印错误的值可以解释为编译后的代码为这些值存储了四字节
DWORD
s,并且在
QWORD
的高位四个字节中可能有非零字节,
printf
预计会在这些位置找到。)

因此,在我看来,编译器有效地维护了一个堆栈(这里的“stack”是正确的字吗?),其中包含八个字节的条目,而不管是否总是需要八个字节

为什么?

更新:

我问的问题(Hans Passant已经回答了)并不依赖于
printf
甚至变量参数列表。对于x64体系结构,函数的第五个和更高参数是如何处理的

例如,调用此函数:

void sub(长a、长b、长c、长d、长e、长f)
{
}
获取此汇编程序代码:

; 28   :    sub(a, c, d, f, g, i);

    mov eax, DWORD PTR i$[rbp]
    mov DWORD PTR [rsp+40], eax
    mov eax, DWORD PTR g$[rbp]
    mov DWORD PTR [rsp+32], eax
    mov r9d, DWORD PTR f$[rbp]
    mov r8d, DWORD PTR d$[rbp]
    mov edx, DWORD PTR c$[rbp]
    mov ecx, DWORD PTR a$[rbp]
    call    ?sub@@YAXJJJJJJ@Z           ; sub

在这里,我们再次看到,
长的
s参数被传递到相隔八个字节的内存位置中的函数。

我猜这与64位处理器有关,它可能喜欢在八字节边界上对齐。但我讨厌猜测,希望有人能给我提供可靠的信息。谢谢未定义的行为就是-未定义。在你的
%d
周围加入一些
%s
,看看会发生什么。。。。而且,尽管有时可以在特定的运行中分析特定系统上的UB症状,但这样做几乎毫无用处。(这就是为什么您将得到一个巴西式的“stahp;it's UB”注释和答案。)它是x64 abi所要求的,需要通过堆栈(第5个及以上)传递的参数与8对齐。编译器执行它所执行的操作,因为这是您让它执行的操作。这个问题部分是关于
printf
的,因为它使用了可变参数列表,允许您轻松地告诉运行时做错误的事情。