指针变量
指针变量也是变量,读写操作和普通变量完全一致
普通变量的值–读写运算;
指针变量的值—定位内存可以是变量、常量,cpu寄存器,受保护的值,
指针变量做加减运算也是在做地址偏移
域名:百度,ip:202.108.22.5(数据包的导航方式);
域名是ip的别名
变量是内存地址的别名
第一句:定义一个int变量a并都把1赋值给a;即把1 写入到地址为0x1234的内存里面,
第二句:定义一个指针变量p,并且赋值为变量a的地址,即把0x1234写入到 地址为:0x1238的内存里面
第三句:对P进行 * 操作,这个分两步完成,
- 取出指针变量p的值–0x1234;并且作为目标地址,即读取目标地址为0x1238里面的内存值:0x1234,
- 把2写入到地址为0x1234的 内存里面;这个是*的特有操作
指针变量的初值十分重要,会以这个初始值作为,目标内存的地址,并且往里面读写数据
指针变量的初值十分重要,会以这个初始值作为,目标内存的地址,并且往里面读写数据
普通变量也能做指针操作,只要知道变量a的地址,就可以 用*操作
变量是内存地址的别名,
指针可以无代价的变换成任意数据类型
精确定位能力,可以避免数据拷贝工作,可以访问硬件相关的寄存器,相对于面向对象的多态,指针对各种数据类型兼容性很好
数组越界
越界的两个数组变量,相当于拓展的 c,d
所以为毛安全呢?
因为越界元素所在的内存是一块无主之地,此时的读写操作不会影响到任何人
定义变量的过程,就是向下(低端地址方向)和拓展函数堆栈(rbp)的过程,不过不会马上崩溃,一旦与其他函数变量冲突时,就很诡异了
函数1 堆栈的上方(高地址方向)存放了引导cpu跳转的地址信息,当函数1执行完以后,cpu靠这个信息,回调回main函数那边,所以如果让cpu跳转到a[3]=0显然会导致异常,想修正就恢复到原来地址,因为main调用完函数1时候,只有返回到401185 这个地方,才能继续执行main函数的其他操作
把这个地址赋值给当时越界的那个地方,就好了
数组越界的特点:
一般数组越界比较隐蔽,比如拷贝字符串,用全局变量所引数组元素
用汇编语言看函数调用的轨迹
完整的函数调用轨迹如下:
- Push rbp,把rbp寄存器的值 存入内存
- Mov指令对内存没有影响,可以略过
- Call指令 会把下一条指令的地址存入内存—cpu跳转到func1继续执行
- 函数1的push指令,会把把rbp寄存器的值 存入内存
- Mov指令对内存没有影响,可以略过
- Call指令 会把下一条指令的地址存入内存—cpu跳转到func2继续执行
- 函数2 执行完以后,会通过40111a返回到fun1;函数1执行完,通过4.1125返回到main函数
![在这里插入图片描述](https://img-blog.csdnimg.cn/593059fd8e4d47ae9bcd8c3259b184ed.pn
所有函数调用结束,水位线回到初始位置
函数的返回
8. 函数的pop指令,会把水位线上的值,赋值给寄存器rbp,
Ret指令会把水位线上的值,赋值给寄存器rip,从而,让rip引导cpu返回到func_1
9. 返回到函数1后执行pop指令,把水位线上的值赋值给,寄存器rbp,
10. ret指令则把水位线上的值,赋值给寄存器rip,从而,让rip引导cpu返回到main
11. Main函数pop指令,会把水位线上的值,赋值给寄存器rbp,
所有函数调用结束,水位线(栈顶)回到原点,内存一点没多,一点没少
过程:通过一个栈变量获得栈顶位置,使用指针操作,遍历一段堆栈数据,再根据代码段的地址特性做一下过滤,配合map表,完整的函数调用轨迹就出来了
总结
- 栈顶其实也是一个内存地址,保存在rsp寄存器里面
- 堆栈是一段普通的内存,每次函数调用需要一定数量的内存,用来存放,返回地址和其他信息
- 每次函数返回都会如数的返回刚才占用的内存,但是不会清理数据
- 如果函数嵌套调用过深,函数没有机会返回,并释放占用的内存,就可能出现水位线超标的现象----堆栈溢出,最典型的例子,递归函数
参考:
https://www.bilibili.com/video/BV1544y177yw/?spm_id_from=333.788