int sum(int a, int b)
{
int temp;
temp = a + b;
return temp;
}
int main(void)
{
int a = 10; // __asm{mov dword ptr[ebp-4], 0Ah}
int b = 20;
int ret = 0;
ret = sum(a, b);
printf("ret = %d\n", ret);
return 0;
}
由上图汇编可得知返回值是由eax寄存器返回的。
int temp = 0;
mov dword ptr [ebp-4],0
是将 ebp-4 地址开始两个字节赋值为0。temp = a + b;
mov eax.dword ptr [ebp+4]
是把 ebp+4 地址的双字节数据赋值给 eax。add eax.dword ptr [ebp+0Ch]
是把 ebp+0Ch 地址的双字节地址加到 eax 寄存器。mov eax,dword ptr [ebp-4],eax
是将 eax 寄存器中的值赋值给 ebp-4 开始的两个字节。因为 ebp-4 是 temp 的地址,所以此条指令就是将 eax 寄存器的值赋值给 temp。return temp;
mov eax,dword ptr [ebp-4]
将 ebp-4 地址开始的两个字节再次赋值给 eax 寄存器,return 的结果是通过 eax 寄存器返回的。函数的返回值在 <= 4 字节的时候是通过 eax 寄存器返回的,没有产生任何的临时变量。
typedef struct {
int a;
int b;
}Data_t;
Data_t sum(Data_t a, Data_t b)
{
Data_t temp = {0};
temp.a = a.a + b.a;
return temp;
}
int main(void)
{
Data_t a = {10};
Data_t b = {20};
Data_t ret = {0};
ret = sum(a, b);
return 0;
}
由汇编文件可见,8 字节的返回值 eax edx 两个寄存器共同带出来。
typedef struct {
int a[20];
}Data_t;
Data_t sum(Data_t a, Data_t b)
{
Data_t temp = {0};
temp.a[0] = a.a[0] + b.a[0];
return temp;
}
int main(void)
{
Data_t a = {10};
Data_t b = {20};
Data_t ret = {0};
ret = sum(a, b);
return 0;
}
sub esp, 50
是为形参 b 开辟地址。mov ecx,14h
在 ecx 寄存器中记录了形参 b 的字节偏移量。lea esi,[ebp-0A0h]
。ebp 指针 - 50h(也就是20 * sizeof(int) = 80)为 实参 a 的地址;ebp 指针 - 0A0h(也就是2 * 20 * sizeof(int) = 160)为实参 b 的地址。此指令将实参 b 的地址写入寄存器 esi。则 esi 指向实参 b 的地址。mov edi,esp
将 esp 寄存器的值写入 edi 寄存器中。也就是 edi 指向 esp 指向的地址。rep movs dword ptr [edi],dword ptr [esi]
是将 esi 指向的地址内容循环拷贝到 edi 指向的地址中。循环次数为 ecx 寄存器中的 14h 次。lea eax,[ebp-190h]
开辟了临时量内存,这个内存的位置在 main 的栈针上,此内存非常重要,对于 sum 返回大于 8 字节的内容来说。这里就可以解释返回结构体实际上是将结构体内容拷贝至 main 栈针的临时量内存上。即便 sum 函数销毁或修改也不会影响 main 中临时量的数据。push eax
是将临时量内存的地址入栈,以便于将 sum 函数计算的结果拷贝至临时量内存中。mov eax,dword ptr [ebp+0Ch]
是将形参 a.a[0] 的两个字节拷贝至 eax 寄存器。因为 ebp 偏移 12 字节就是形参 a 的首地址。add eax,dword ptr [ebp+5Ch]
是将形参 b.a[0] 的两个字节累加至 eax 寄存器。mov dword ptr [ebp-50h],eax
是将 eax 寄存器的值拷贝至 temp 内存中。lea esi,[ebp-50h]
将 temp 的地址传给 esi 寄存器。mov edi,dword ptr [ebp+8]
将临时量内存地址放到 edi。因为 ebp + 8 的内存中存放的是 main 栈上临时量内存的地址。rep movs dword ptr [edi],dword ptr [esi]
循环执行将 esi 地址的值两个字节两个字节的拷贝至 edi。mov eax,dword ptr [ebp+8]
因为 ebp + 8 的地址存放的 main 上临时量的地址。 所以最后 eax 寄存器返回的是 main 栈上临时量的地址。在调用函数之前会创建临时变量。并将其临时量地址压栈入 sum 函数的栈中。栈中位置处于形参变量后。
return temp
将 temp 的内存拷贝到 main 栈针上临时变量的空间上。到此就将 sum 局部变量的值带出到 main 函数中。然后返回了 main 函数中临时变量的地址。
栈针回退并没有将栈空间内容清除,所以存在脏数据。
mov esp,ebp
将esp 栈底指针移动到 ebp 栈顶指针的位置。pop ebp
出栈回到 ebp 此时指向的上一个栈的栈顶地址(此地址在函数开始被压入栈)。ret
与上面的return不同。这里的 ret 有两个操作,第一个操作是将 esp 此时指向的下一行指令地址(图一地址 0040109A 同样是在函数开始时入栈的)写入 PC 寄存器;第二个操作是将执行一次出栈操作,esp 指针下移。add esp,8
最后一步回退形参变量开辟的内存。(形参变量内存在sum函数分配)小于等于 8 字节是值传递。
大于 8 字节是地址传递。但是在地址传递之前已经将函数运算的结果全部拷贝到 main 函数栈针的临时量内存中,返回的是临时量的地址。所以函数执行完毕,局部变量销毁不会影响 main 中临时量的值。