照例,先贴代码,再说明,再总结!随便起个例子的.
- #include <stdio.h>
- int & kiss(int&,int);
- double kiss2(char *o,int *t,int &s);
- int main(){
- printf("dddd\n");
- int k=10;
- int wy = kiss(k,20);
- printf("%d\n",wy);
- kiss2(0,0,wy);
- return 0;
- }
- int& kiss(int& i,int j){
- i++;
- return i;
- }
- double kiss2(char *o,int *t,int &s){
- double res = 12.332;
- return res;
- }
test.cpp
----------------------------------------------------------------------
附上 objdump 出来的汇编
两头省掉一万行代码...直接进主题
----------------------------------------------------------------------
080484f4 :
/
80484f4: 55 push %ebp
80484f5: 89 e5 mov %esp,%ebp
\_上面两句分别为保存调用者的栈帧指针和激活本函数的栈帧指针.
/
80484f7: 83 e4 f0 and $0xfffffff0,%esp
\_栈帧对齐.
/
80484fa: 83 ec 20 sub $0x20,%esp
\_申请本函数的局部变量栈空间,包括(局部变量int k,int wy和本函
数调用的子函数中参数个数或长度最大的一个所需的参数压栈空间),
在本例为kiss2函数.这个跟调用约定也有关系,C/C++默认均为_cdecl,
规则为调用子程序的参数栈由调用者自行平衡,子程序只需平衡子程序
内用到的寄存器和局部变量,调用子子程序的最长参数压栈空间(以此
规则递归...),其他的例如参数压栈顺序从右到左的就不多说了.
/
80484fd: c7 04 24 50 86 04 08 movl $0x8048650,(%esp)
\_printf("dddd\n");这句话的第一个参数,在C++里变成了puts,
$0x8048650为只读字符串首地址.在这里顺便说下,如上所说,本函数已
经申请了函数内调用到的参数长度最长的子函数参数栈空间,所以可以用
自esp打上的栈空间作为参数调用函数内的任何子函数.
/
8048504: e8 17 ff ff ff call 8048420 <>
\_调用之...
/
8048509: c7 44 24 1c 0a 00 00 movl $0xa,0x1c(%esp)
8048510: 00
\_0x1c(%esp)为局部变量int k=10;,并进行初始化.
/
8048511: c7 44 24 04 14 00 00 movl $0x14,0x4(%esp)
8048518: 00
\_kiss(k,20);函数调用从右至左的第一个参数.
/
8048519: 8d 44 24 1c lea 0x1c(%esp),%eax
804851d: 89 04 24 mov %eax,(%esp)
\__kiss(k,20);函数调用从右至左的第二个参数,因为是引用传递,所以
实质上还是传递局部变量int k的地址.
/
8048520: e8 3f 00 00 00 call 8048564 <_Z4kissRii>
\_调用之...
/
8048525: 8b 00 mov (%eax),%eax ;这里留个疑问,首先结果是正确的,lea 0x1c(%esp),%eax这句拿的是SS段的偏移,mov (%eax),%eax这句操作的是ds段,用同一个偏移...望高手指点.
8048527: 89 44 24 18 mov %eax,0x18(%esp)
\_因为kiss返回一个引用值,所以实质还是返回一个地址,并访问这个
地址,取值赋给局部变量int wy(0x18(%esp)),随便说下,函数一般式
用eax作为返回值的.
/
804852b: 8b 44 24 18 mov 0x18(%esp),%eax
804852f: 89 44 24 04 mov %eax,0x4(%esp)
8048533: c7 04 24 55 86 04 08 movl $0x8048655,(%esp)
\_分别初始化printf("%d\n",wy);函数调用的第一个和第二个形参,
把int wy赋给第二个参数,把字符串首地址$0x8048655赋给第一个参数.
/
804853a: e8 d1 fe ff ff call 8048410 <>
\_调用之...
/
804853f: 8d 44 24 18 lea 0x18(%esp),%eax
8048543: 89 44 24 08 mov %eax,0x8(%esp)
8048547: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp)
804854e: 00
804854f: c7 04 24 00 00 00 00 movl $0x0,(%esp)
\_真是太忙了,printf刚调用完,马上又为调用kiss2作准备...继续说明之,
kiss2右边第一个参数为引用形参,地址传之...右起第一个和第二个都为0,
直接赋值之!
/
8048556: e8 1e 00 00 00 call 8048579 <_Z5kiss2PcPiRi>
\_调用之...
/
804855b: dd d8 fstp %st(0)
\_浮点型的返回值,把结果放到浮点寄存器中...
/
804855d: b8 00 00 00 00 mov $0x0,%eax
\_上面说过了,一般情况下eax是作为函数返回值.
/
8048562: c9 leave
8048563: c3 ret
\_这两个...厄...先贴下原意,leave 和 ret -> mov esp,ebp
pop ebp
sub esp, -4
jmp dword [esp-4]
至此,又一胎...
下面的两个子函数就不再继续说明了,也是按照如上规则递归之...
总结下:cdecl的风格是自己搞大别人肚子的,就要自己负责,如果不
是你的(传进来的参数),打死也不要揽这个担子上身!stdcall则
比较多情,不管是自己的还是别人的,照揽(一般形式为在最后的ret
后面加上本函数参数所需的栈空间大小,例如 ret 8.),对了顺便
提下,C++里的非静态成员函数调用规则大致如上,唯独this指针是通
过ecx传进函数的.
08048564 <_Z4kissRii>:
8048564: 55 push %ebp
8048565: 89 e5 mov %esp,%ebp
8048567: 8b 45 08 mov 0x8(%ebp),%eax
804856a: 8b 00 mov (%eax),%eax
804856c: 8d 50 01 lea 0x1(%eax),%edx
804856f: 8b 45 08 mov 0x8(%ebp),%eax
8048572: 89 10 mov %edx,(%eax)
8048574: 8b 45 08 mov 0x8(%ebp),%eax
8048577: 5d pop %ebp
8048578: c3 ret
08048579 <_Z5kiss2PcPiRi>:
8048579: 55 push %ebp
804857a: 89 e5 mov %esp,%ebp
804857c: 83 ec 10 sub $0x10,%esp
804857f: dd 05 60 86 04 08 fldl 0x8048660
8048585: dd 5d f8 fstpl -0x8(%ebp)
8048588: dd 45 f8 fldl -0x8(%ebp)
804858b: c9 leave
----------------------------------------------------------------------
另外,下面是在网上找到的一点资料,让自己长点记性.
Enter -> push ebp
mov ebp,esp
Leave -> mov esp,ebp
pop ebp
ret指令宏
macro ret AddEspNum
{
if ~ AddEspNum eq 0 ; 如果无参数,则恢复EIP所占栈,然后跳.否则先恢复传进来大小+EIP占大小,再跳.有参的时候貌似还判断了CPU是多少位的.
if (AddEspNum mod 4)<>0 ; 32-bit cpu
.err
end if
lea esp, [esp+(AddEspNum+4)]
jmp dword [esp-(AddEspNum+4)] ; skip AddEspNum bits stack value, and jump to next instruction
else
sub esp, -4
jmp dword [esp-4] ; no return value, jump to next instruction
end if
}
针对上面的一个疑问,经高人指点,疑惑解开.顺便贴下过程:
- #include <stdio.h>
- int main(char argc,char **argv){
- int cs=0,ds=0,ss=0;
- __asm__ __volatile__("movl $0,%%eax;\
- movw %%cs,%%ax;\
- movl %%eax,%0;\
- movl $0,%%eax;\
- movw %%ds,%%ax;\
- movl %%eax,%1;\
- movl $0,%%eax;\
- movw %%ss,%%ax;\
- movl %%eax,%2;"
- :"=r"(cs),"=r"(ds),"=r"(ss)
- :);
- printf("CS:%d DS:%d SS:%d\n",cs,ds,ss);
- return 0;
- }
打印结果为: CS:115 DS:123 SS:123 这就能解释上面用同一偏移地址,在不同段选择子的同一结果.