作为计算机专业的来说,程序入门基本都是从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. python opencv cv2 imshow threading 多线程

    除了线程同步,还需要注意的是「窗口处理」要放在主线程 #!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys import threadin ...

  2. 源码编译安装nginx及设置开机启动项

    1.上传nginx文档:解压到/data目录下,并安装依赖包tar xf nginx-1.20.1.tar.gz -C /data/cd /data/nginx-1.20.1/ && ...

  3. python代码检查工具(静态代码审查)

    python静态代码检查 我们知道python是一门脚本语言,不像C#/Java等编译型语言可以在编译阶段就报出代码错误,脚本语言往往需要在运行期执行到这段代码时才会抛出代码错误. 那么在实际商业项目 ...

  4. NOIP模拟21:「Median·Game·Park」

    T1:Median   线性筛+桶+随机化(??什么鬼?).   首先,题解一句话秀到了我: 考虑输入如此诡异,其实可以看作随机数据   随机数据??   这就意味着分布均匀..   又考虑到w< ...

  5. rtl8188eu 驱动移植

    测试平台 宿主机平台:Ubuntu 16.04.6 目标机:iMX6ULL 目标机内核:Linux 4.1.15 rtl8188eu 驱动移植 在网上下载Linux版的驱动源码: wifi驱动的实现有 ...

  6. Python - 3.8 新特性之仅位置参数 & 仅关键字参数

    前置知识 Python 函数:https://www.cnblogs.com/poloyy/p/15092393.html 什么是仅限位置形参 仅限位置形参是 Python 3.8 才有的新特性 新增 ...

  7. 用 Java 写个塔防游戏「GitHub 热点速览 v.21.37」

    作者:HelloGitHub-小鱼干 本周 GitHub Trending 的主题词是:多语言.本周特推的 C 语言教程是大家都知道的阮一峰编写的,想必和他之前的技术文章类似,能起到科普作用.再来时 ...

  8. JS获取DOM元素的八种方法

    JS获取DOM元素的方法(8种) 通过ID获取(getElementById) 通过name属性(getElementsByName) 通过标签名(getElementsByTagName) 通过类名 ...

  9. private关键字理解

    private 意思: 私有的 私人的 不公开的 private 是一个修饰符可以用来修饰成员变量和方法 被private修饰的成员变量或成员方法,只能在本类中访问,针对private修饰的成员变量, ...

  10. .Net Core with 微服务 - 分布式事务 - 可靠消息最终一致性

    前面我们讲了分布式事务的2PC.3PC , TCC 的原理.这些事务其实都在尽力的模拟数据库的事务,我们可以简单的认为他们是一个同步行的事务.特别是 2PC,3PC 他们完全利用数据库的事务能力,在一 ...