作为计算机专业的来说,程序入门基本都是从C语言开始的,了解C程序中的内存布局,对我们了解整个程序运行,分析程序出错原因,会起到事半功倍的作用 。

C程序的内存布局包含五个段,分别是STACK(栈段),HEAP(堆段),BSS(以符号开头的块),DS(数据段)和TEXT(文本段)。

每个段都有自己的读取,写入和可执行权限。如果程序尝试以不允许的方式访问内存,则会发生段错误,也就是我们常说的coredump。

段错误是导致程序崩溃的常见问题。核心文件(核心转储文件)也与段错误相关联,开发人员使用该文件来查找崩溃的根本原因(段错误)。

下面我们将深入这五个段,更加详细的讲解每个段在程序开发或者运行中的作用。

High Addresses ---> .----------------------.
                    |      Environment     |
                    |----------------------|
                    |                      |   Functions and variable are declared
                    |         STACK        |   on the stack.
base pointer ->     | - - - - - - - - - - -|
                    |           |          |
                    |           v          |
                    :                      :
                    .                      .   The stack grows down into unused space
                    .         Empty        .   while the heap grows up. 
                    .                      .
                    .                      .   (other memory maps do occur here, such 
                    .                      .    as dynamic libraries, and different memory
                    :                      :    allocate)
                    |           ^          |
                    |           |          |
 brk point ->       | - - - - - - - - - - -|   Dynamic memory is declared on the heap
                    |          HEAP        |
                    |                      |
                    |----------------------|
                    |          BSS         |   Uninitialized data (BSS)
                    |----------------------|   
                    |          Data        |   Initialized data (DS)
                    |----------------------|
                    |          Text        |   Binary code
Low Addresses ----> '----------------------'

栈

  • 它位于较高的地址,与堆段的增长和收缩方向正好相反。
  • 函数的局部变量存在于栈上
  • 调用函数时,将在栈中创建一个栈帧。
  • 每个函数都有一个栈帧。
  • 栈帧包含函数的局部变量参数和返回值。
  • 栈包含一个LIFO结构。函数变量在调用时被压入栈,返回时将函数变量从栈弹出。
  • SP(栈指针)寄存器跟踪栈的顶部。
#include 
int main(void) {
    int data; // 局部变量,存储在栈上
    return 0;
}

堆

  • 用于在运行时分配内存。
  • 由内存管理函数(如malloc、calloc、free等)管理的堆区域,这些函数可以在内部使用brk和sbrk系统调用来调整其大小。
  • 堆区域由进程中的所有共享库和动态加载的模块共享。
  • 它在堆栈的相反方向上增长和收缩。
#include 
int main(void) {
    char *pStr = malloc(sizeof(char)*4); //pStr指向堆地址
    return 0;
}

BSS(未初始化的数据块)

  • 包含所有未初始化的全局和静态变量。
  • 此段中的所有变量都由零或者空指针初始化。
  • 程序加载器在加载程序时为BSS节分配内存。
#include 
int data1; // 未初始化的全局变量存储在BSS段
int main(void) {
    static int data2;  // 未初始化的静态变量存储在BSS段
    return 0;
}

DS(初始化的数据块)

  • 包含显式初始化的全局变量和静态变量。
  • 此段的大小由程序源代码中值的大小决定,在运行时不会更改。
  • 它具有读写权限,因此可以在运行时更改此段的变量值。
  • 该段可进一步分为初始化只读区和初始化读写区。
#include 
int data1 = 10 ; //初始化的全局变量存储在DS段
int main(void) {
    static int data2 = 3;  //初始化的静态变量存储在DS段
    return 0;
}

TEXT

  • 该段包含已编译程序的二进制文件。
  • 该段是一个只读段,用于防止程序被意外修改。
  • 该段是可共享的,因此对于文本编辑器等频繁执行的程序,内存中只需要一个副本。

深入

现在有一个简单的程序,代码如下:

#include  
  
int main(void) { 
    return 0; 
}

我们通过如下命令进行编译

gcc -g a.cc -o a

然后通过size命令,可以看到各个段的大小

[root@build src]# gcc a.c -o a
[root@build src]# size a
   text    data     bss     dec     hex filename
   1040     484      16    1540     604 a

其中前三列分别为可执行程序a的text、data以及bss段的大小,第四列为该三段大小之和,第四列为该大小的十六进制表示,最后一列是文件名。

增加一个未初始化的静态变量

#include  
    
int main(void) { 
    static int data;
    return 0; 
}

通过size命令

[root@build src]# size a
   text    data     bss     dec     hex filename
   1040     484      24    1548     60c a

从上面可以看出,bss段size变大

增加一个初始化的静态变量

#include  
    
int main(void) { 
    static int data = 10;
    return 0; 
}

通过size命令

[root@build src]# size a
   text    data     bss     dec     hex filename
   1040     488      16    1544     608 a

从上面可以看出,data段size变大

增加一个未初始化的全局变量

#include  
int data;
int main(void) { 
    return 0; 
}

通过size命令

[root@build src]# size a
   text    data     bss     dec     hex filename
   1040     484      24    1548     60c a

从上面可以看出,bss段size变大

数据段的只读区域和读写区域

#include 
char str[]= "Hello world";
int main(void) {
    printf("%s\n",str);
    str[0]='K';
    printf("%s\n",str);
    return 0;
}

输出

Hello world
Kello world

可以看到上面的示例str是一个全局数组,因此它将进入数据段。 还可以看到能够更改该值,因此它具有读取和写入权限。

现在查看其他示例代码

#include 
char *str= "Hello world";
int main(void) {
    str[0]='K';
    printf("%s\n",str);
    return 0;
}

在上面的示例中,我们无法更改数组字符是因为它是文字字符串。常量字符串不仅会出现在数据部分,而且所有类型的const全局数据都将进入该部分。

数据块只读部分,通常除了const变量和常量字符串外,程序的文本部分(通常是.rodata段)也存在于数据块的只读部分,因为通常无法通过程序进行修改。

C程序内存布局的更多相关文章

  1. C语言程序内存布局

    C语言程序内存布局 如有转载,请注明出处:http://blog.csdn.net/embedded_sky/article/details/44457453 作者:super_bert@csdn 一 ...

  2. 一起talk C栗子吧(第一百三十一回:C语言实例--C程序内存布局三)

    各位看官们,大家好.上一回中咱们说的是C程序内存布局的样例,这一回咱们继续说该样例.闲话休提,言归正转.让我们一起talk C栗子吧. 看官们,关于C程序内存布局的样例,我们在前面的两个章回都介绍过了 ...

  3. 用一个词(TASPK)牢记C程序内存布局

    一个典型的C程序内存布局,从低地址到高地址分别为: 1. text (正文段,即代码段 Code Segment) 2. data (已经初始化的数据段) 3. bss (未被初始化的数据段 Bloc ...

  4. C++程序内存布局

      代码区(code area) 程序内存空间 全局数据区(data area) 堆区(heap area) 栈区(stack area) 一个由C/C++编译的程序占用的内存分为以下几个部分, 1) ...

  5. linux C 程序内存布局

    参考: 1. http://www.cnblogs.com/clover-toeic/p/3754433.html 2. http://www.cnblogs.com/jacksu-tencent/p ...

  6. ESP32应用程序的内存布局

    应用程序内存布局 ESP32芯片具有灵活的内存映射功能.本节介绍ESP-IDF在默认情况下如何使用这些功能. ESP-IDF中的应用程序代码可以放置在以下内存区域之一中. IRAM(指令RAM) ES ...

  7. c++内存布局与c程序的内存布局

    c/c++的内存布局:堆,栈,自由存储区(与堆的区别),全局/静态存储区,常量存储区(字符串常量,const常量) http://www.cnblogs.com/QG-whz/p/5060894.ht ...

  8. UNIX高级环境编程(8)进程环境(Process Environment)- 进程的启动和退出、内存布局、环境变量列表

    在学习进程控制相关知识之前,我们需要了解一个单进程的运行环境. 本章我们将了解一下的内容: 程序运行时,main函数是如何被调用的: 命令行参数是如何被传入到程序中的: 一个典型的内存布局是怎样的: ...

  9. 探讨C++ 变量生命周期、栈分配方式、类内存布局、Debug和Release程序的区别

    探讨C++ 变量生命周期.栈分配方式.类内存布局.Debug和Release程序的区别(一) 今天看博客园的文章,发现博问栏目中有一个网友的问题挺有趣的,就点进去看了下,标题是“C++生存期问题”,给 ...

随机推荐

  1. C++模板使用

    C++模板 模板是一种对类型进行参数化的工具: 通常有两种形式:函数模板和类模板: 函数模板针对仅参数类型不同的函数: 例如:Max函数 :求两个数的最大值,我们需要对各种数据类型进行重载,如下 in ...

  2. vue ele 日期时间格式限制不能早于当天,时间转换成时间戳 进行比较

    <el-date-picker             value-format="yyyy-MM-dd HH:mm:ss"             v-model=&quo ...

  3. PyCharm——滚动鼠标调整字体大小

  4. HCNP Routing&Switching之IS-IS邻居建立、LSDB同步、拓扑计算和路由形成

    前文我们了解了IS-IS的报文结构和类型相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/15260670.html:今天我们来聊一聊IS-IS建立邻居. ...

  5. C# 中 AppDomain 的一些理解

    C# 中 AppDomain 的一些理解 前言 一直想写一个这样的程序:与其它的程序完全解耦,但可以动态的加载其它程序,并执行其中的特定方法,执行完后可以卸载,完全不影响该程序本身.最近无意间发现了 ...

  6. .Net core 的热插拔机制的深入探索,以及卸载问题求救指南.

    .Net core 的热插拔机制的深入探索,以及卸载问题求救指南. 一.依赖文件*.deps.json的读取. 依赖文件内容如下.一般位于编译生成目录中 { "runtimeTarget&q ...

  7. 在PHP中检测一个类是否可以被foreach遍历

    在PHP中,我们可以非常简单的判断一个变量是什么类型,也可以非常方便的确定一个数组的长度从而决定这个数组是否可以遍历.那么类呢?我们要如何知道这个类是否可以通过 foreach 来进行遍历呢?其实,P ...

  8. ecshop 首页调用指定分类下的销售排行

    /*首页调用指定分类下的销售排行*/ function get_cats_top10($cat = '') { $sql = 'SELECT cat_id, cat_name ' . 'FROM ' ...

  9. Linux系列(6) - 常见目录

    linux 一级目录有严格规定,脚本文件等放在root/home/tmp目录中,减少在根目录的操作 目录名称 作用 / 根目录 /bin 命令保存目录(普通用户就可以读取的命令); 根目录下的bin和 ...

  10. Web爬虫|入门实战之糗事百科(附源码)

    coding by real mind writing by genuine heart 解析 任务背景:https://www.qiushibaike.com/hot/   窥探网页细节:观察每一页 ...