【问题标题】:Why is EAX being cleared before calling a function if I don't include the header?如果我不包含标头,为什么在调用函数之前清除 EAX?
【发布时间】:2021-06-08 20:06:06
【问题描述】:

在以下 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 指令:

这是编译器资源管理器:https://godbolt.org/z/3fTcss。这只是“未定义行为”的一部分,还是有特殊原因将call getchar 之前的指令添加到那里?

【问题讨论】:

  • 如果没有原型,编译器会生成代码来处理 varargs 函数以确保安全,这意味着将 al 归零。
  • @Jester:我认为这意味着我不能一次将 256 个数字传递给 snprintf。
  • 你可以。 al 仅表示使用的向量寄存器的数量,最大为 8 (xmm0-xmm7)。其余参数进入堆栈。
  • 我想知道为什么编译器不使用xor 来清除eax
  • @SimonRichter 确实如此,-O2 及以上。 mov其中的零只是未优化的、明显的、接近 C 的版本。

标签: c assembly x86-64 calling-convention


【解决方案1】:

如果没有标头,gcc 会在您使用时提供getchar隐式声明,就像您之前声明过一样

int getchar();

(此行为由旧版本的 C 标准保证。当前版本使使用以前未声明的函数成为未定义行为,但 gcc 仍将旧行为作为扩展提供。)

此声明未提供有关getchar 期望的参数类型的信息。 (请记住,与 C++ 不同,带有 () 的声明不会将函数声明为采用 no 参数,而是采用 unspecified 参数,由程序员决定知道函数期望什么并传递正确数量和类型的参数。)所有编译器都知道,它甚至可以是可变参数,并且根据 x86-64 SysV ABI,可变参数函数在 al 中期望向量寄存器的数量用于传递参数。这里没有使用向量寄存器,因此编译器在调用之前将al 设置为0。 (实际上将所有 rax 归零会稍微高效一些,所以它会这样做。)

【讨论】:

  • C 标准从一开始就说隐式声明的函数和没有原型声明的函数不能是可变参数。但无论如何,大多数编译器都支持它。
  • @prl:等等什么?最早的 C 在声明中没有参数类型。
  • @Joshua:第一个 C 标准 (C89) 做到了。你是对的,早期的准标准 C 没有。
  • 当前版本不使用未声明的函数 UB。实现必须发出诊断,并且可以停止编译,或者隐式定义函数并像在 C89 中一样继续。我没有查看标准本身,但至少引用了 C99 基本原理文档here
猜你喜欢
  • 2014-09-08
  • 1970-01-01
  • 2018-12-31
  • 1970-01-01
  • 2021-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多