如果我没有包含头文件,为什么在调用函数之前会清除EAX?

26
以下是需要翻译的内容:

在下面的C代码中:

#include <stdio.h>
int main(void){getchar();}

它生成以下汇编代码:

main:
        push    rbp
        mov     rbp, rsp
                 # no extra instruction here when header is included
        call    getchar
        mov     eax, 0
        pop     rbp
        ret

然而,如果我在文件中不包含stdio.h,那么它仍然可以编译,但会添加一个看起来像是随机的mov eax, 0指令:

enter image description here

这里是Compiler Explorer:https://godbolt.org/z/3fTcss。这只是"未定义行为"的一部分,还是在call getchar之前添加该指令有特殊原因?


14
没有原型,编译器会生成处理可变参数函数的代码以确保安全性,这意味着需要将 al 清零。 - Jester
1
@Jester:我理解为我不能一次传递256个数字给snprintf。 - Joshua
2
你可以。al 只是表示使用的向量寄存器数量,最多为8个(xmm0-xmm7)。其余的参数放在堆栈上。 - Jester
我想知道为什么编译器没有使用 xor 来清除 eax - Simon Richter
1
@SimonRichter,在-O2及以上优化级别下会有影响。在此之中插入一个零的mov指令只是未经优化、明显且接近C语言版本的代码。 - TooTea
1个回答

38

没有头文件时,gcc在使用getchar函数时会提供一个隐式声明,就好像你之前已经声明了它一样。

int getchar();

这种行为曾经由旧版本的C标准保证。当前版本将使用先前未声明的函数视为未定义的行为,但是gcc仍然作为扩展提供旧的行为。

这个声明没有提供关于getchar需要的参数类型的信息。(请记住,与C++不同,()声明的函数不表示不带参数,而是表示参数是未指定的,程序员需要知道函数需要哪些参数并传递正确数量和类型的参数)。对于编译器来说,它甚至可以是可变参数的,并且根据x86-64 SysV ABI,可变参数函数期望在al中传递使用的向量寄存器的数量。这里没有使用向量寄存器,因此编译器在调用之前将al设置为0。(实际上将所有rax置零更有效率,所以编译器会这样做。)


7
C语言标准一开始就规定,隐式声明的函数和没有原型声明的函数不能是可变参数函数。但大多数编译器仍然支持此功能。 - prl
@prl:等等,什么?最早的C语言在声明中没有参数类型。 - Joshua
5
@Joshua:第一个C标准(C89)确实如此。您说得对,早期的非标准C没有这样做。 - Nate Eldredge
3
当前版本不使用未声明函数UB。实现必须发出诊断,可以停止编译,或者隐式定义该函数并像在C89中一样继续编译。我没有查看标准本身,但至少引用了C99理由文件此处 - Ruslan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接