Linux 环境中,虚拟地址空间即用户程序可以看到的地址空间分为以下几个段,从上到下依次是栈,堆,bss,data,text

以下内容只适用于 32 位系统,64 位系统略微不同。

1、栈

1.1 栈的作用

在 c/c++ 中函数调用很常见,那么在底层函数调用是怎么实现的呢?c/c++ 中的函数调用,在会汇编指令中通过call 指令实现,当执行到 call 指令的时候,① 将函数所需参数从右到左依次入栈,例如调用 printf("a+b=%d\n",sum);,sum 入栈,字符串"a+b=%d\n"的地址入栈,② CPU 首先将call 指令之后指令地址入栈,当函数 return 的时候可以继续往下执行。当进入函数内部的时候,首先执行:

push   %ebp
mov    %esp,%ebp

此时 ebp 代表该函数的栈顶地址,一个栈帧表示一个函数的调用过程,此时在该函数内部可以自由使用 ebp 以下的内存空间,可以通过将 sp 寄存器减去特定的值,来为函数的局部变量预留空间,变量地址可以通过 bp 指针获取。如下图所示:

当函数返回的时候,要确保栈的内容和调用之前一模一样。

1.2 栈的工作过程

现在通过代码来观察栈的工作过程。以下代码定义了一个求和函数,在 main 函数中将m,n,x,y 的和 保存在sum 变量中。 其中 sum 是未初始化变量,这主要为了测试,记得不要使用未初始化的变量。

int getSum(int a,int b,int c,int d){return a+b+c+d;
}
int main(){int m = 8;int n = 9;int x = 10;int y = 11;int sum;sum = getSum(m,n,x,y);return 0;
}

gcc 使用 -g 添加调试选项,-m32 编译32位应用程序:使用 objdump 得到反汇编得到机器代码:

$ gcc -m32 -g dis.c -o dis
$ objdump -d dis

main 函数的机器码如下:

0000050a <main>:50a:   55                      push   %ebp50b:   89 e5                   mov    %esp,%ebp50d:   83 ec 20                sub    $0x20,%esp510:   e8 3f 00 00 00          call   554 <__x86.get_pc_thunk.ax>515:   05 c7 1a 00 00          add    $0x1ac7,%eax51a:   c7 45 ec 08 00 00 00    movl   $0x8,-0x14(%ebp)521:   c7 45 f0 09 00 00 00    movl   $0x9,-0x10(%ebp)528:   c7 45 f4 0a 00 00 00    movl   $0xa,-0xc(%ebp)52f:   c7 45 f8 0b 00 00 00    movl   $0xb,-0x8(%ebp)536:   ff 75 f8                pushl  -0x8(%ebp)539:   ff 75 f4                pushl  -0xc(%ebp)53c:   ff 75 f0                pushl  -0x10(%ebp)53f:   ff 75 ec                pushl  -0x14(%ebp)542:   e8 a2 ff ff ff          call   4e9 <getSum>547:   83 c4 10                add    $0x10,%esp54a:   89 45 fc                mov    %eax,-0x4(%ebp)54d:   b8 00 00 00 00          mov    $0x0,%eax552:   c9                      leave  553:   c3                      ret

sub $0x20,%esp,将 esp 减去 32,代表当前栈帧中为局部变量预留的空间为 32 字节,为什么是 32 字节?我们总共定义了 5 个int 型变量,每个变量占 4 个字节,总共 20 字节。其实为了为了方便计算,esp 每次都减去 16 的整数倍,当局部变量所占空间小于等于 16 时,就预留16字节,即减去 0x10;当大于 16,小于等于 32时,预留 32 字节,依次类推。

ebp 表示当前函数栈帧地址,通过将 ebp 减去一定的偏移量就可以定位到这些局部变量。ebp-0x4 表示 sum,ebp-0x8 表示 y,ebp-0xc 表示 x,ebp-0x10 表示 n,ebp-0x14 表示 m。

在调用 getSum 之前,y,x,n,m 依次压入堆栈,之后把 add $0x10,%esp的地址压入堆栈。函数的返回值存储在 eax 中。

函数调用之后:add $0x10,%esp,为了清理函数调用压入的 4 个参数,每个参数 4 字节,总共 16 字节,等于 0x10,因为 c 语言规定,谁调用,谁负责清理堆栈。

leave指令在32位汇编下相当于: :

mov %ebp,%esp;
pop %ebp

相当于清理了堆栈中的局部变量,此时栈顶的内容为add $0x10,%esp的地址,执行 ret 指令,从栈中弹出函数返回地址,main 继续执行。

1.3 栈工作总结

总的来说,栈实现了函数调用,在栈中记录函数的返回地址,这样当函数执行完成后,调用者继续向下执行。栈用来保存函数形参,通过 ebp 加上一定的偏移就可以获得这些参数值。栈还用来存储局部变量,当函数执行完成后,这些局部变量自动被系统回收。

2、堆

堆位于栈的下方,与栈相反,堆从低地址往高地址增长。代码段和数据段(data,bss)的大小在编译时就固定了,确定了的。除了代码段,数据段,栈段,剩余的空间都可以作为堆,供程序动态使用。每一个 malloc或new 都应该有 free 和 delete 配对,否则容易造成内存泄漏,这对于像服务器端程序这样需要持续运行很久,就很关键。程序结束后,系统会回收所有资源,包括栈,和堆中已经分配但还没释放的空间。

#include <stdlib.h>
int main(){int *p = malloc(sizeof(int));*p = 12;free(p);      // 每一个 malloc 都应该和一个 free 配对return 0;
}

上述代码中,指针 p 属于局部变量,保存在栈上,p指向的内存存储在堆中。

栈和堆一起构成堆栈段

3、 数据段

数据段的大小在编译的时候就已经确定,在运行过程中也可以通过 sbrk() 来调整数据段的大小。

3.1 bss 段

一般存放未初始化的全局数据和未初始化的静态数据,bss 段变量都初始化为0。

例如下列代码中:count,sum,grade 存储于bss 段

#include <stdio.h>int count;
static int sum;
int main(){static int grade;return 0;
}

3.2 data 段

一般存放非0全局变量,非0静态变量(staic 修饰)。例如以下代码中 global, count, size 存储在该区域。

#include <stdio.h>int global = 100;
static int count = 80;
int main()
{static int size = 2;return 0;
}

4、代码段

代码段的页表决定它只可读,任何尝试写的操作都会造成页错误。所以程序的机器指令和只读数据存储于代码段。

只读数据:

  • 字符串常量。
  • const 修饰的全局变量。

const 修饰的局部变量存储在栈上,它不可以直接修改,但可以间接修改:

void foo(){const int sum = 80;      // 局部 const 变量,存储在栈上sum = 99;                            // 错误,不可以直接修改int *p = (int*)&sum;*p = 99;                 // 正确,可以间接修改
}

在以下代码中, 指针s保存在栈中,它保存了字符串"Hello world" 的地址,“Hello world” 本身保存在代码段中。

const 修饰的全局变量保存在代码段中,例如:con,*p1, p2, p3,*p3,但是 p1,*p2 在数据段中。

#include <stdio.h>void print(char* str){printf("%s\n",str);
}const int con = 18;
int m = 100;
int n=99;
int x = 1024;
const int *p1 = &m;            //  p 指向代码段中的一个地址,此时 *p 修改了,就等于修改代码段,导致错误
int *const p2 = &n;            //  p 本身是代码段中的一个地址,此时 p 被修改了,就等于修改代码段,导致错误
cont int* const p3 = &x;       //  p 本身和 p 执行的地址都位于代码段,此时,修改 p 还是修改 *p 都会段错误
int main(){char *s = "Hello world";print(s);return 0;
}

现在回答以下问题:

*为什么 p1 在代码段,p1 在data?

根据const 的位置,当const 在 类型前时,p1 指向的内存为常量,因而位于代码段中,p1 本身不是常量,所以存在于data段中。

*为什么 p2 在代码段,p2 在data 段?

const 在指针名前,表示 p2 本身为常量,因而位于代码段,但该指针指向的内存地址不为常量,因而位于data段中。

同理可解释为什么 p3 和 *p3 都位于代码段。

如有遗漏,欢迎指正。

Linux c 地址空间 堆栈 数据段 代码段 变量存储位置相关推荐

  1. linux进程的堆栈空间_代码段(指令,只读)、数据段(静态变量,全局变量)、堆栈段(局部变量)、栈【转】...

    转自:http://blog.csdn.net/gongweijiao/article/details/8207333 原文参见:http://blog.163.com/xychenbaihu@yea ...

  2. 16位汇编 数据段 栈段 代码段

    使用数据段,栈段,代码段 实现置换功能 0123  0456  0789  0abc  0def  0fed    0cba  0987 0987  0cba  0fed   0def   0abc ...

  3. 【C 语言】变量本质 ( 变量概念 | 变量本质 - 内存空间别名 | 变量存储位置 - 代码区 | 变量三要素 )

    文章目录 一.变量概念 二.变量本质 1.变量本质 - 内存别名 2.变量存储位置 - 代码区 3.变量三要素 一.变量概念 变量概念 : 变量 是 既能读 , 又能写 的 内存对象 ; 与 变量 相 ...

  4. java数据段 静态区_linux进程的堆栈空间_代码段(指令,只读)、数据段(静态变量,全局变量)、堆栈段(局部变量)、栈【转】...

    一)概述 .堆栈是一个用户空间的内存区域,进程使用堆栈作为临时存储. .堆栈中存放的是函数中的局部变量,在函数的生命周期中可以将变量压入堆栈,编译器需要确保堆栈指针在函数退出前恢复到初始位置,也就是说 ...

  5. 14. OD-inline patch入门,将一段代码和变量分别注入一个程序中

    分析程序情况 第一次进入call为neg 第二次进入call为主程序 第三次退出程序进入call为neg 这个跳转如果改为jmp,那么主程序也没有了 那么我们可以试想写一段C程序做判断 int i = ...

  6. linux内存分配堆栈数据段代码段,linux – LD_PRELOAD堆栈和数据段内存分配

    你好, 我正在编写一个Linux模块(基于名为"Ccontrol"的GitHub项目)来创建缓存分区(a.k.a页面着色),以减轻定时侧通道攻击(用于防止Prime Probe等攻 ...

  7. 西部数据硬盘支持linux,技术|对西部数据 My Passport Wireless 移动存储进行 Linux 魔改...

    虽然 WD My Passport Wireless 本身就是一个相当有用的设备,但它有一个轻量级但完整的 Linux 发行版提供支持的事实意味着其功能可以进一步扩展.例如,在设备上部署 rclone ...

  8. c语言代码存放的区域 堆栈,C语言中内存分布及程序运行中(BSS段、数据段、代码段、堆栈)...

    BSS段:(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域.BSS是英文Block Started by Symbol的简称.BSS段属于静态内存分配. 数据段 :数据 ...

  9. 代码段+数据段+bss段+stack+heap

    在学习之前我们先看看ELF文件. ELF分为三种类型:.o 可重定位文件(relocalble file),可执行文件以及共享库(shared library),三种格式基本上从结构上是一样的,只是具 ...

  10. c语言堆、栈、数据段、代码段、bss段的疑惑

    程序的内存分配 一个由c/C++编译的程序占用的内存分为以下几个部分 1.栈区(stack)- 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等.其操作方式类似于数据结构中的栈. 2.堆区(h ...

最新文章

  1. Spring Cloud Finchley版中Consul多实例注册的问题处理
  2. python手机版怎么运行项目或脚本-python可以在手机上运行吗
  3. 关于VS2012连接MySql数据库时无法选择数据源
  4. 这个为生信学习打造的开源Linux/Bash教程真香!!!
  5. python中else在循环中的使用(一分钟读懂)
  6. XNA实现不停循环的路的效果
  7. 六石管理学:好大喜功,头目们是否相信
  8. 技巧:Eclipse阿里代码规范插件
  9. Spring Boot 实现在线Web SSH( Java Web版本的Xsehll)
  10. 联想售后服务偷换主板
  11. 自学软件测试怎么样,有前景吗?
  12. v.douyin.com/xxx抖音网址官方生成制作抖音缩短口令网址php接口方法
  13. 在线英文广播电视资源
  14. Docker 容器安装监控软件 cAdvisor
  15. mysql 连接慢安全狗_服务器安全狗端口安全策略导致微信小程序慢解决办法
  16. 数据库的列类型与字段属性
  17. 123数字黑洞-第11届蓝桥杯Scratch选拔赛真题精选
  18. Jetson Nano B01 无界面初始化安装系统+飞桨(Paddle)v2.0
  19. win10 神州网信政府版 (V0-G.1014.000) 关闭屏幕保护
  20. euclidea4攻略_Euclidea几何构建11.4通关攻略

热门文章

  1. Python+Turtle 魔法阵效果(简陋)
  2. 记录异或门原理图和版图设计
  3. 极小化极大;292Nim 游戏;bitset容器;464我能赢吗;486预测赢家
  4. java实现三进制转十进制
  5. 计算机电子表格课程导入,又到了每年此刻,教你把课程表导入日历
  6. 学霸题 - 数正方形
  7. QQ推广,QQ在线代码
  8. linux桌面lxde 安装_在Ubuntu上,如何安装轻量的LXDE桌面
  9. python中正则的使用
  10. java边界布局东南西北_第58节:Java中的图形界面编程-GUI