对于__stdcall调用方式,调用函数的逻辑一般如下
//Caller
;prolog
push xx
push ...
call callee
add esp x ; 恢复堆栈
;epilog
一般情况下,指令add esp x会恢复调用函数之前的栈顶指针。但并不是所有的情况都这样,例如,调用这个函数后,可能接着调用另外一个函数,这时候栈顶指针可能被修改为符合调用下个函数的esp值。例如主调函数会调用被调函数1及被调函数2,被调函数1的参数长度为8,被调函数2的参数长度为4,被调函数1执行完后,主调函数不会将esp的值加8(add esp 8),而是将esp值加4,使之成为适合调用被调函数2的栈结构。
以上我是通过自己写的某个测试代码发现的,其符合以下条件才会这样编译(未开启优化)
1. 两个被调用函数必须紧密连续被调用,之间没有其它的逻辑。
2. 存在浮点数参数。
例如以下代码
void loadfloat(int a,float f)
{
f+=a;
}
void loaddouble(int b, double d)
{
d-=b;
}
int _tmain(int argc, _TCHAR* argv[])
{
int a=3;
float f = 1.2f;
double de = 3.4;
<pre class="cpp" name="code"> loaddouble(a,de);
loadfloat(a,f);
return 0;}
反编译为
int _tmain(int argc, _TCHAR* argv[])
{
00211020 push ebp
00211021 mov ebp,esp
00211023 sub esp,10h
int a=3;
00211026 mov dword ptr [a],3
float f = 1.2f;
0021102D fld dword ptr [__real@3f99999a (21A818h)]
00211033 fstp dword ptr [f]
double de = 3.4;
00211036 fld qword ptr [__real@400b333333333333 (21A810h)]
0021103C fstp qword ptr [de]
loaddouble(a,de);
0021103F sub esp,8
00211042 fld qword ptr [de]
00211045 fstp qword ptr [esp]
00211048 mov eax,dword ptr [a]
0021104B push eax
0021104C call loaddouble (211010h)
loadfloat(a,f);
00211051 add esp,8
00211054 fld dword ptr [f]
00211057 fstp dword ptr [esp]
0021105A mov ecx,dword ptr [a]
0021105D push ecx
0021105E call loadfloat (211000h)
00211063 add esp,8
return 0;
00211066 xor eax,eax
}
请大家注意,loaddouble的参数总长度为12,但是调用完毕后确实是add esp 8,已满足下个被调函数的堆栈布局。若是在两个子函数之间随意插入一个逻辑,例如
loaddouble(a,de);
a++;
loadfloat(a,f);
则编译器不会执行这种优化。