闲着没事想研究一下gcc的函数调用方式和m$的__stdcall、__fastcall之类有何区别,本想是了解一下关于参数的入栈顺序和清理方,就随便写了个C函数,编译成.s文件,一看发现根本就没有push和pop之类的指令...两个int参数都是利用rsi和rdi传递!网上百度了一个关于m$平台x64的调用约定linux
看完ddk里相关的部分,总结下吧,规则却是不复杂,相对x86时代的stdcall cdecl fastcall 三分天下要简明的多。按ddk里的说法,m$就是要趁此次统一调用规则…… -__-算法
在x64下函数调用的前4个参数老是放在寄存器中传递,剩余的参数则压入堆栈中。而x86上则是所有压入堆栈中(除了fastcall方式)。这4个用于存放参数的寄存器分别是:存放整数参数的RCX,RDX,R8,R9;存放浮点数参数的XMM0,XMM1,XMM2,XMM3。函数
按照所传参数是整数仍是浮点数的不一样,寄存器的使用规则以下:优化
所有整数参数:指针
func1(int a, int b, int c, int d, int e);编译器
参数a放入RCX,参数b放入RDC,参数c放入R8,参数d放入R9,参数e么压栈。数学
参数传递规则:按照参数表声明的顺序,从左向右,前4个参数依次放入RCX,RDX,R8,R9中。it
所有浮点数参数:编译
func1(float a, float b, float c, double d, float e);ast
a放入XMM0,b放入XMM1,c放入XMM2,d放入XMM3,e压栈
参数传递规则:按照参数声明的顺序,从左向右,前4个参数依次放入XMM0,XMM1,XMM2,XMM3中
整数和浮点数参数混合出现:
func3(float a, int b, double c, int d)
a放入XMM0中,b放入RDX,c放入XMM2,d放入R9。
这里比较特殊,其实就是按照这个规则:
a b c d
RDX R9
XMM0 XMM2
也就是说4个整数寄存器严格的一一对应前4个参数,一样前4个XMM寄存器严格的一一应前4个参数,若是是整数浮点数间隔出现,那么就保持对应关系,选择对应的寄存器便可。
指针参数:
指针的传递遵循整数参数传递方式。
结构体参数:
结构体特殊一点,按照ddk的描述,若是结构体长度小于64bit,则使用整数参数的传递规则。但若是是一个很大的结构体,那么应该仍是要在堆栈中申请临时空间的(但ddk没有明说这一点,参考x86的规则应该如此)。
未声明函数的调用:
ddk里特别列举了这样一个例子:
func1();
func2(){
func1(2, 1.0, 7)
}
在这种状况下,func1()的参数表其实不明确,那么参数的传递要怎样进行?这里采用了一个比较保守的规则,就是:整数参数仍是按照寄存器映射关系放入对应的寄存器中,浮点数在按照映射关系放入XMM寄存器后,还须要按照整数参数的寄存器映射关系放入整数寄存器中一次,这就是为啥我说是“比较保守的规则”。就如今这个例子而言,结果以下:
2在RCX中,1.0在RDX和XMM1中,7在R8中。
然而gcc的用法却与此大相径庭:
版权为 win_hate 全部, 转载请保留做者名字
我这段时间要把之前的一个 x86_32 的 linux 程序移植到 x86_64(AMD) 的 linux 环境里. 因为写的是数学算法, 64 与 32 位有很大不一样, 代码实际上要重写. 看了点资料后, 以为 AMD64 的扩展于之前 16 到 32 位的扩展很相似, e**, 扩展为 r**, 此外还多了8个通用寄存器 r8~r15.指令格式与32位的极为类似. 我以为比较容易, 因此没再仔细看, 就开始动手写了.
个人程序由若干个汇编模块于与若干个c模块构成, 不少c模块要调用汇编模块. 做为试验, 我先写了个简单的汇编函数, 而后用c来调用. 结果算出来的值始终是错误的. 这令我很恼火, 由于函数很简单, 没有多少出错的余地. 后来我把程序反汇编出来, 错误立刻浮现出来了, 函数的参数竟然是经过寄存器来传递的. 我凭之前的经验, 从堆栈里取参数, 算出的结果固然不对了. 我之前不是没碰到过用寄存器传递参数的状况, 但所在的环境都不是 pc. 在 x86_32/linux 中, 即便用 -O3 优化选项, gcc 仍经过栈来传递参数的.
因此咱们如今知道, 在 x86_64/linux/gcc3.2 中, 即便不打开优化选项, 函数的参数也会经过寄存器来传递, 这确定是阔了的表现(通用寄存器多了).
我试验了多个参数的状况,发现通常规则为, 当参数少于7个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。当参数为 7 个以上时, 前 6 个与前面同样, 但后面的依次从 "右向左" 放入栈中。
例如:
CODE
(1) 参数个数少于7个:
f (a, b, c, d, e, f);
a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%r8, f->%r9
g (a, b)
a->%rdi, b->%rsi
有趣的是, 实际上将参数放入寄存器的语句是从右到左处理参数表的, 这点与32位的时候一致.
CODE
2) 参数个数大于 7 个的时候
H(a, b, c, d, e, f, g);
a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%rax
g->8(%esp)
f->(%esp)
call H
易失寄存器:
%rax, %rcx, %rdx, %rsi, %rdi, %r8, %r9 为易失寄存器, 被调用者没必要恢复它们的值。
显然,这里出现的寄存器大多用于参数传递了, 值被改掉也无妨。而 %rax, %rdx 经常使用于
数值计算, %rcx 经常使用于循环计数,它们的值是常常改变的。其它的寄存器为非易失的,也
就是 rbp, rbx, rsp, r10~r15 的值若是在汇编模块中被改变了,在退出该模块时,必须将
其恢复。
教训:
用汇编写模块, 而后与 c 整合, 必定要搞清楚编译器的行为, 特别是参数传递的方式. 此外, 我如今比较担忧的一点是, 未来若是要把程序移植到 WIN/VC 环境怎么办? 之前我用cygwin的gcc来处理汇编模块, 用vc来处理c模块, 只须要不多改动. 如今的问题是, 若是VC用不一样的参数传递方式, 那我不就麻烦了?
补充:
前面的参数 a, b, c, d 等, 都是整数, 长整数, 或指针, 也就是说, 能放到寄存器里头的. 若是你要传递一个很大的结构, 我估计编译器也只能经过栈来传递了.
环境为 AMD Athlon64, Mandrak linux 9.2, GCC3.3.1
对比上下两篇文章可知,gcc和vc在x64的函数调用方式彻底不一样,gcc和vc编译的模块想要互相调用貌似不太可能,除非其中的一种推出兼容模式