我们有3个文件:main.c、lib.h和lib.c
C.主要:
#include <stdio.h>
#include <stdlib.h>
/* #include "lib.h" */
int main(void)
{
printf("sizeof unsigned long long: %zu\n", sizeof(unsigned long long));
printf("sizeof int: %zu\n", sizeof(int));
unsigned long long slot = 0;
int pon_off = 1;
lib_fn(slot, pon_off);
return EXIT_SUCCESS;
}lib.h:
void lib_fn(unsigned slot, int pon_off);lib.c:
#include <stdio.h>
#include <stdlib.h>
void lib_fn(unsigned slot, int pon_off)
{
printf("slot: %d\n", slot);
printf("pon_off: %d\n", pon_off);
return;
}汇编:
gcc -O2 -Wall -Wextra main.c lib.c在手臂上奔跑:
$ ./a.out
sizeof unsigned long long: 8
sizeof int: 4
slot: 0
pon_off: 0在x86-64上运行:
$ ./a.out
sizeof unsigned long long: 8
sizeof int: 4
slot: 0
pon_off: 1如您所见,pon_off在ARM上为0,而在x86-64上为1。我猜这与参数大小有关,因为lib_fn()需要两个it,它们在一起有8个字节长,一个long long是8个字节长。
pon_off在ARM和x86-64上的打印方式不同?发布于 2017-11-08 01:21:42
这和电话会议有关吗?
是的,它与呼叫约定/ ABI有关。
在x86-64上,函数参数的“自然”宽度为64位,更窄的整数args仍然使用整个“槽”。(首先是寄存器中的6个整数/指针args和前8个FP args (SysV)或前4个args (Windows),然后是堆栈)。
在ARM上,寄存器宽度(堆栈上的"arg槽“最小宽度)为32位,64位整数args采用两个寄存器。
在32位x86 (gcc -m32)上,您将看到与32位ARM相同的行为.在AArch64上,您将看到与x86-64相同的行为,因为它们的调用约定都是“正常的”,并且不会将单独的窄args打包到单个寄存器中。(x86-64系统确实将结构成员打包到最多2个寄存器中,而不是每个成员使用单独的寄存器!)
具有与寄存器大小相等的最小"arg槽“宽度几乎是通用的,无论arg是在寄存器中传递还是在堆栈上传递。不过,这不一定是int的宽度:args可以在一个寄存器中传递。
对于原型,根据原型中的类型,将更宽/更窄的类型转换为被调用方所期望的类型。所以很明显一切都正常。
在没有原型的情况下,调用中表达式的类型决定了arg是如何传递的。unsigned long long slot 在ARM的调用约定中接受第一个2 arg传递寄存器, lib_fn 希望在其中查找其2整型args.。
(声称所有东西都在没有原型的情况下转换为int的答案是错误的。没有一个原型等同于int lib_fn(...);,但是printf仍然可以与double和int64_t一起工作。请注意,当传递到变量函数时,float被隐式转换为double,就像较窄的整数类型被上转换为int一样,这就是为什么%f是double格式的原因,而float没有格式,这与您传递指针的扫描格式不同。这正是C的设计方式,没有理由这样做。但是无论如何,C要求更广泛的类型能够像对各种函数一样被传递,并且所有的调用约定都包含这一点。)
顺便说一句,其他的破坏是可能的,:有些实现对各种函数(因此是非原型的)使用了与普通函数不同的调用约定。
例如,在Windows上,您可以将一些编译器设置为默认为呼叫约定,其中被调用者从堆栈中弹出args。(即在弹出返回地址后使用ret 8来执行esp+=8。)但是很明显,这个调用约定不能用于各种函数,因此默认值不适用于它们,它们将使用_cdecl或调用方负责清理堆栈args的东西,因为只有调用方知道它们传递了多少个arg。希望在这种模式下,编译器至少会警告隐式声明函数的错误,因为出错会导致崩溃(调用后堆栈指向错误的位置)。
让我们来看看这个案例的asm
有关读取编译器asm输出的介绍,请参见如何消除GCC/clang组件输出中的“噪音”?,特别是马特·戈德波特的CppCon2017 talk “我的编译器最近为我做了什么?”打开编译器的盖子“。
为了使asm尽可能简单,我删除了打印,并将代码放入一个返回void的函数中。(这允许在尾叫优化中跳转到函数并将其返回给调用方。)编译器输出中唯一的指令是arg设置和跳转到lib_fn。
#ifdef USE_PROTO
void lib_fn(unsigned slot, int pon_off);
#endif
void foo(void) {
unsigned long long slot = 0;
int pon_off = 1;
lib_fn(slot, pon_off);
}(**-m32**),参见戈德波特编译器资源管理器上的source+asm,ARM,x86-64,x86-32,gcc 6.3**。(我实际上复制了foo并将其重命名为lib_fn,这样它就不会在调用者的一个版本中有原型,而不是为每个体系结构设置两个单独的编译器窗口。在更复杂的情况下,这会很方便,因为您可以区分编译器窗格)。
对于x86-64,输出与没有原型的输出基本相同。如果没有,调用方必须将al (使整个拉克斯为零)为零,以指示此变量函数调用在XMM寄存器中没有传递任何FP args。(在Windows调用约定中,您不会有这种情况,因为Windows约定是针对各种函数以及在牺牲正常函数的情况下实现它们的简单性而优化的。)
ARM的:
foo: @ no prototype
mov r2, #1 @ pon_off
mov r0, #0 @ slot low half
mov r1, #0 @ slot high half
b lib_fn_noproto
bar: @ with proto, u long long is converted to unsigned according to C rules, like the callee expects
mov r1, #1
mov r0, #0
b lib_fnlib_fn期待slot在R0,pon_off在R1。
打破x86-64
如果使用unsigned __int128,在x86-64上也会遇到同样的问题。
lib_fn_noproto((unsigned __int128)slot, pon_off);汇编成:
mov edx, 1 # pon_off = EDX = 1
xor edi, edi # low half of slot = RDI = 0
xor esi, esi # high half of slot = RSI = 0
xor eax, eax # number of xmm register args = 0
jmp lib_fn_noproto这与32位ARM完全相同的方式打破了呼叫约定,64位arg占据了前两个时隙。
发布于 2017-11-07 22:45:36
发布于 2017-11-07 22:11:27
如果没有函数原型并使用隐式声明,编译器假定所有参数都是int类型。
看起来int在arm和x64-86架构上是不同的。
注意,修饰符%d只能与int参数一起使用,对于未签名的一个使用%u
这就是为什么有警告给你。
https://stackoverflow.com/questions/47168239
复制相似问题