从汇编角度理解C

本文从函数调用、变量的本质、数组与指针等多个方面,深入剖析了C语言的底层运作机制。通过对比C代码和汇编代码,详细解释了参数传递、返回值、堆栈变化的过程,并探讨了变量的内存分配、类型转换和结构体的存储方式。此外,还介绍了指针的五大特点,包括其加减运算、取地址和取值的规则,以及指针与数组之间的关系。
摘要由CSDN通过智能技术生成


c只要会 数组指针 就没什么太大问题

一、函数&变量

1. 参数传递与返回值

1.1 c代码

我们来分析下面这一份c代码:

int plus(int x, int y){
   
    return x + y;
}

int main(void){
   
    plus(1, 2);  // 调用函数
    return 0;    
}

2.2 汇编代码

调用函数部分:
plus(1, 2);

(1)    push 2  ;保存线程
       push 1
(2)    call plus()函数地址  ; 跳转
(3)    add esp, 8  ; 外平栈顶

函数部分:
int plus(int x, int y){

<1>    push ebp  ; 提升堆栈
       mov ebp, esp
       sub esp, 40H
<2>    push ebx  ; 保存线程
       push esi
       push edi
<3>    lea edi, [ebp - 40H]  ; 用CC填充缓冲区
       mov ecx, 10H
       mov eax, 0CC CC CC CCH
       rep stos dword ptr [edi]

return x + y;

<4>    mov eax, dword ptr [ebp + 8]  ; 代码主体
       add eax, dword ptr [ebp + 0CH]

}

<5>    pop edi  ; 释放线程
       pop esi
       pop ebx
<6>    mov esp, ebp  ;还原堆栈 
<7>    pop ebp  ; 释放ebp
<8>    ret  ; 返回调用处

2.3 堆栈情况

在这里插入图片描述


调用函数:

  • 步骤1: (1),把参数压入栈(从右往左压)在这里插入图片描述

  • 步骤2: (2),把调用处下一行代码的地址压入栈。跳转到plus()在这里插入图片描述

  • 步骤3: <1>,提升堆栈,保存esp的值在ebp在这里插入图片描述

  • 步骤4: <2>,保存线程 在这里插入图片描述

  • 步骤5: <3>,用CC填充缓冲区 在这里插入图片描述

补充:

  1. 提升堆栈和原始堆栈之间的地方叫做缓冲区
  2. 为了防止缓冲区溢出 错误,需要让指令不能读到缓冲区,而CC的作用就是断点。
  3. CC是中文ASCII码
  • 步骤6: <4>,把xeax,再把y加进去 在这里插入图片描述

  • 步骤7: <5>,释放线程 在这里插入图片描述

  • 步骤6: <6>,还原堆栈 在这里插入图片描述

  • 步骤7: <7>,还原ebp的值 在这里插入图片描述

  • 步骤8: <8>,返回调用处 在这里插入图片描述

  • 步骤9: (3),执行此步前,堆栈没有还原,所以手动还原(叫外平栈在这里插入图片描述

2. 变量

2.1 变量的本质

  • 声明变量: int a
    ``
  • 给变量赋值: a = 7
    mov dword ptr [a], 1

全局变量:

  1. 编译时就确认内存地址和宽度,变量名就是内存地址别名(数据标号)
  2. 如果不重写编译,全局变量的内存地址不变。(找基址 的意思就是找全局变量

局部变量:

  1. 局部变量是函数内部申请的,只有当函数执行时,它才有内存空间
  2. 局部变量的内存是在堆栈中分配的,我们无法确定局部变量的内存地址

2.2 变量与参数的内存分配

继续回顾一下这张图: 在这里插入图片描述

可以发现:

  1. [EBP + 8]就是第一个参数,再+4就是第二个参数。。。
  2. [EBP + 4]是返回地址
  3. EAX用来存储计算过程,也用来存储返回值(约定俗成的)

局部变量在缓冲区中: int z = x + y

mov eax, dword ptr [ebp + 8]  ; x
add eax, dword ptr [ebp + 0CH]  ; x + y
mov dword ptr [ebp - 4], eax  ; 往缓冲区写入局部变量

返回值通过eax传递: return z

mov eax, dword ptr [ebp - 4]

int a = plus(1, 2);

mov dword ptr [ebp - 4], eax  ; 【注意】这个ebp是调用者main()的ebp,不是plus()的ebp

总结一下:
在这里插入图片描述

2.3 嵌套调用

还记得plus()调用的堆栈下面是啥嘛?是main()的堆栈!

一个函数调用另一个函数,新的函数会 在调用函数的上面
在这里插入图片描述

  • 问题: 为什么全局变量 可以不赋值,定义完直接使用(有默认值,比如int类型,默认值为0),而局部变量 必须要赋值才能使用?
  • 回答: 一个函数执行完毕后,会在堆栈留下一堆垃圾数据。这时,如果调用了一个新的函数,分配给局部变量的那部分内存,里面是别的函数留下的垃圾,如果不赋值使用,必然出错。

补充: win32 Debug版编译器有一个命令call __chkesp来检查函数执行前后,堆栈是否平衡。不平衡就报错

2.4 printf

printf("%d", r);

mov eax, dword ptr [ebp - 4]  ; 把r的值给eax
push eax  ; 形参入栈
push offset String "%d"  ;"%d"的偏移地址
call printf  
add esp, 8  ; 外平栈

3. 变量类型

3.1 分类&定义

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值