【函数调用堆栈】

文章目录

  • 栈针的开辟
  • 函数返回值如何返回
    • 返回值 <= 4 字节
    • 4 字节 < 返回值 <= 8 字节
    • 8 字节 < 返回值
      • 总结
  • 栈针的回退
  • 问题
    • 返回值是结构体是值传递还是地址传递?

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;
}

栈针的开辟

在这里插入图片描述
https://img-blog.csdnimg.cn/img_convert/477868437ac05d0469093d50e9d9eb29.png#clientId=u78e4646c-f146-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=265&id=u63f0251d&margin=[object Object]&name=image.png&originHeight=265&originWidth=612&originalType=binary&ratio=1&rotation=0&showTitle=false&size=129717&status=done&style=none&taskId=u004ad1e5-1eaa-41bc-833f-823074eeca2&title=&width=612
【函数调用堆栈】_第1张图片
在这里插入图片描述
在这里插入图片描述

【函数调用堆栈】_第2张图片

函数返回值如何返回

在这里插入图片描述
在这里插入图片描述
由上图汇编可得知返回值是由eax寄存器返回的。
int temp = 0;

  • 此时temp的地址为 ebp-4
  • 指令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 字节

函数的返回值在 <= 4 字节的时候是通过 eax 寄存器返回的,没有产生任何的临时变量。

4 字节 < 返回值 <= 8 字节

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 两个寄存器共同带出来。

8 字节 < 返回值

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;
}

在这里插入图片描述
【函数调用堆栈】_第3张图片

  • 指令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 函数计算的结果拷贝至临时量内存中。
    在这里插入图片描述
    在这里插入图片描述
    【函数调用堆栈】_第4张图片
    指令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 函数中临时变量的地址。

栈针的回退

栈针回退并没有将栈空间内容清除,所以存在脏数据。

  1. 直接出栈 edi esi ebx 寄存器。
  2. 指令mov esp,ebp将esp 栈底指针移动到 ebp 栈顶指针的位置。
  3. 指令pop ebp出栈回到 ebp 此时指向的上一个栈的栈顶地址(此地址在函数开始被压入栈)。
  4. 指令ret与上面的return不同。这里的 ret 有两个操作,第一个操作是将 esp 此时指向的下一行指令地址(图一地址 0040109A 同样是在函数开始时入栈的)写入 PC 寄存器;第二个操作是将执行一次出栈操作,esp 指针下移。
  5. 指令add esp,8最后一步回退形参变量开辟的内存。(形参变量内存在sum函数分配)

问题

返回值是结构体是值传递还是地址传递?

小于等于 8 字节是值传递。
大于 8 字节是地址传递。但是在地址传递之前已经将函数运算的结果全部拷贝到 main 函数栈针的临时量内存中,返回的是临时量的地址。所以函数执行完毕,局部变量销毁不会影响 main 中临时量的值。

你可能感兴趣的:(书籍,c语言)