3.函数的分类

85 阅读4分钟

在函数调用的时候一般开始的时候是在分配栈空间,结束的时候是在回收栈空间。

1.程序典型的内存分区

高地址
+------------------+ 0x7fffffff
|| ← 局部变量、函数参数、返回地址
||
+------------------+
|                  |
|   (动态分配)     |
|                  |
+------------------+
|| ← 动态分配的内存 (malloc/new)
||
+------------------+
|    BSS段         | ← 未初始化的全局/静态变量
+------------------+
|   数据段(.data)  | ← 已初始化的全局/静态变量
+------------------+
| 只读数据(.rodata)| ← 字符串常量、只读变量
+------------------+
|   代码段(.text)  | ← 程序指令
+------------------+ 0x00400000
低地址

2.各个分区的作用和特点

代码段 (.text)

作用和特点

代码段存储程序的可执行指令,是程序的核心部分。

主要特点:

  • 只读性:代码段通常是只读的,防止程序运行时意外修改指令
  • 可执行:CPU可以从这个区域取指令并执行
  • 共享性:多个进程可以共享同一个程序的代码段
  • 位置固定:在程序加载时确定位置,运行时不会改变

数据段 (.data)

作用和特点

数据段存储程序的已初始化的全局变量和静态变量。

主要特点:
  • 可读可写:程序运行时可以修改数据段中的值
  • 初始化:包含程序启动时就有确定值的变量
  • 持久性:在整个程序运行期间都存在
  • 固定大小:编译时就确定了大小

BSS段 (.bss)

BSS段存储未初始化的全局变量和静态变量。

特点:

  • 零初始化:程序启动时自动初始化为0
  • 不占文件空间:在可执行文件中不占实际空间
  • 运行时分配:程序加载时才分配内存

3.为什么说栈是向下生长的呢?

因为栈底是高地址,而栈顶是低地址,sp代表的是栈指针寄存器,指向帧顶,栈顶是小地址。 与此对应的还有一个栈底,bp,bp代表的是基地址寄存器,是指向栈底的,是高地址。而正常的压栈操作,是移动sp,实际上是让sp减去一个值,然后把这块空间放某个寄存器。所以栈他是向下生长的。

4.什么是大小端?

字节序(Byte Order):指多字节数据类型(如16位、32位、64位整数)在内存中字节的排列顺序。 可以简单的说大端序是高字节的数据放在地址比较低的位置,而小端序是高字节的地址放在地址比较低的位置。

  • 大端序(Big Endian):最高有效字节(MSB)存储在最低内存地址
  • 小端序(Little Endian):最低有效字节(LSB)存储在最低内存地址

image.png

4.函数的分类

  • 叶子函数:这个函数内部的实现没有调用其他函数
  • 非叶子函数:函数内部可能还调用其他函数

叶子函数

image.png
下面是对应的汇编
image.png

image.png 从上图可以看出只要移动sp就可以开辟一块新的栈空间给非叶子函数使用。

非叶子函数

下面的函数hehe就是非叶子函数

void haha()
{
    int a = 2;
    int b = 3;
}

void hehe()
{
    int a = 4;
    int b = 5;
    haha();
}

下面是对应的汇编代码

 sub sp, sp, #32             ; =32
 stp x29, x30, [sp, #16]     ; 8-byte Folded Spill
 add x29, sp, #16            ; fp = sp + 16

 mov w8, #5
 orr w9, wzr, #0x4
 stur 4, [x29, #-4]
 str 5, [sp, #8]
 bl _haha

 ldp x29, x30, [sp, #16]     ; 8-byte Folded Reload
 add sp, sp, #32             ; =32
 ret

image.png 简单分析下上图:

  • 首先sp 地址是在0x10021,然后sp-32,那么sp变成0x10001,
  • 后面近接着将sp的地址加上16,然后先存x29,x29其实就是fp寄存器,就是栈底指针。x30就是lr寄存器,其实就是当前的执行完返回后的指令地址,这一步其实是在保护现场,是在调用函数之前把栈底指针,返回地址,先存起来。一共用了16个字节,其实就是从0X10020到0x10010之间。
  • 后面再把sp 加16的地址赋值给x29,这是新的栈底位置。此时从sp到x29(fp)之间的就是当前调用函数可以使用的栈空间
  • 中间的5行代码就是在执行里面赋值和跳转操作
  • 最后三行 先是将 sp +16 位置开始取出16个字节的数据赋值给x29(fp),和lr(就是当前函数地址执行完后的返回地址)。然后再将sp + 32 调整sp回到开始的位置,最后返回