为什么这段代码打印由 clang 和 gcc 编译的不同值?

0 c assembly x86-64 calling-convention

汇编:

.intel_syntax noprefix
.global Foo
Foo:
    mov ax, 146
    ret
Run Code Online (Sandbox Code Playgroud)

主要.c:

#include <stdio.h>

extern int Foo(void);

int main(int argc, char** args){
    printf("Asm returned %d\n", Foo());
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

现在我编译并链接:

(compiler name) -c asm.s -o asm.o
(compiler name) asm.o main.c -o  main
./main
Run Code Online (Sandbox Code Playgroud)

我正在使用 Windows 和 LLVM 的 x64 windows 二进制文件。

GCC 打印 146(如预期),但 clang 生成的代码打印随机值,为什么?在 Windows 上使用 gdb 调试 clang 二进制文件显然存在一些问题,因此我无法提供任何 gdb 日志。GCC 二进制文件正在执行我期望的操作,但不是 Clang。

Pet*_*des 5

您想要mov eax, 146匹配 32 位或更宽的返回类型。

请参阅AX、AH、AL 如何映射到 EAX?- 不幸的是,将 EAX 零扩展写入 RAX,但 8 位和 16 位部分寄存器保留了仅合并的传统 386 行为。

您告诉编译器它返回一个int(x86 和 x86-64 调用约定中的 4 个字节),但您只修改了 EAX/RAX 的低 16 位,将现有垃圾留在调用者的32 位返回值的高 16 中int在 EAX 中查找。

sizeof(int) == 4在 x86 的所有 32 位和 64 位调用约定中,因此它以 EAX 形式返回。
16 位 AX 是 C 语言中的“short或” unsigned short。(或int16_t/ uint16_t

如果将返回类型声明为short,则调用者只会在 AX 中查找,忽略窄返回值的调用约定所要求的高垃圾(与窄于 32 位的参数不同,作为至少 x86-64 系统的不成文扩展) V,也许还有 Windows x64,clang 依赖于此)。

另请参阅如何从 GCC/clang 程序集输出中删除“噪音”?了解如何查看编译器生成的代码以查看此类函数的正确 asm。


返回值的低 16 位是146。如果您以十六进制查看,您可以看到这一点,例如xxxx0092

也许 GCC 碰巧在 EAX 的高半部分已经为零的情况下调用它,但 clang 却没有。由于调用者中的代码不同,GCC 也可能使用 RAX 来获取非小值。对于 C 调用者和您碰巧测试的优化选项来说,这只是运气。

C 等效项int retval; memcpy(&retval, &tmp, 2)除了使用寄存器之外,不替换 的高半部分中未初始化的垃圾retval