0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

详解C语言程序内存分区

STM32嵌入式开发 来源:STM32嵌入式开发 2023-06-11 17:29 次阅读

每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。目标文件由段组成。通常一个目标文件中至少有两个段(segment):

代码段:该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。

数据段:主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读,可写,可执行的。

1 目标文件结构

目标文件是源代码编译但未链接的中间文件(Windows的.obj和Linux的.o),Windows的.obj采用 PE 格式,Linux 采用 ELF 格式,两种格式均是基于通用目标文件格式(COFF,Common Object File Format)变化而来,所以二者大致相同。

目标文件一般包含编译后的机器指令代码、数据、调试信息,还有链接时所需要的一些信息,比如重定位信息和符号表等,而且一般目标文件会将这些不同的信息按照不同的属性,以“节(section)”也叫“段(segment)”的形式进行存储。


#include 
#include 


int gInitVar = 1;           // .data   加载阶段加载
int gUninitVar;             // .bss    加载阶段加载
const int gConstVar = 2;    // .rdata  加载阶段加载
// extern可以修饰const用于扩展文件链接性,const默认是文件内链接的
// static修饰全局变量可以限制其文件链接性,其存储属性不变
void foo(int i)             // .text   加载阶段加载
{
    static int staticLocalInitVar = 3;  // .data  加载阶段加载
    static int staticLocalUninitVar;    // .bss   加载阶段加载
    int stack_localVar = 4;             // 栈帧(每个程序运行时会加载1-2M栈空间)
    const int LocalConstVar = 5;        // 栈帧(运行时自动分配)
    staticLocalUninitVar = LocalConstVar + gConstVar;
    gUninitVar = staticLocalInitVar + gInitVar;
    int *dynamic_heapData = (int*)malloc(sizeof(int)*10000000);
    dynamic_heapData[10000000-1] = 9;   // 堆区(运行时动态申请)
    i += stack_localVar + staticLocalUninitVar + gUninitVar; 
    printf("%d
",i+dynamic_heapData[10000000-1]);// .rdata,加载阶段加载"%d
"
    free(dynamic_heapData);             // 堆内存需要显式释放
}                                       // 栈内存在超出作用域后自动释放
int main()
{
    foo(6);
    getchar();
    return 0;
}                                       // 加载阶段加载的内存要等到程序结束才释放


文件的内容分割为不同的区块(Setion,又称区段,节等),区段中包含代码数据,各个区块按照页边界来对齐,区块没有限制大小,是一个连续的结构。每块都有他自己在内存中的属性,比如:这个块是否可读可写,或者只读等等。

①.text代码段

代码段存放程序的机器指令;

② .data已初始化数据段

初始化数据段存放已初始化的全局变量与局部静态变量;

③ .bss未初始化数据段

未初始化局部静态变量(或初始化为0的局部静态变量)放到.bss段,对于未初始化全局变量(或初始化为0的全局变量),不同语言与编译器的实现有不同的处理,有的只是在.baa段预留一个未定义的全局变量符号,等到最终链接成可执行文件的时候再在 .bss 段分配空间。编译器会把未初始化的全局变量标记为一个 COMMON 符号,不为其在 .bss 段分配空间的原因是现在的编译器和链接器支持弱符号机制,即允许同一个弱符号定义在多个目标文件中,因为未初始化的全局变量属于弱符号,编译时无法确定符号大小,所以此时无法在 .bss 段为未初始化的全局变量分配空间。

④ .rdata或.rodata只读数据段

只读数据段存放程序中只读变量,如const修饰的常量和字符串常量;

单独设立.rodata段的好处有很多,比如语义上支持了C的const常量,而且操作系统在加载的时候可以将.rodata段的内容映射为只读区,这样对于这个段的任何修改都会被判为非法,保证了程序的安全性。

⑤ .symtab符号表段

.symtab段用于存符号表。每个目标文件都有一个相应的符号表(Symbol Table),记录了目标文件中用到的所有符号。每个符号都有一个对应的值,叫做符号值(Symbol Value),符号值可以是符号所对应的数据在段中的偏移量,也可以是该符号的对齐属性。

链接过程的本质就是要把多个不同的目标文件之间像拼图一样拼起来,相互拼合实际上是目标文件之间对地址的引用,即对函数和变量的地址的引用。比如目标文件B用到了目标文件A中的函数foo,那么称目标文件A定义了函数foo,目标文件B引用了函数foo。定义与引用这两个概念同样适用于变量。每个函数和变量都有自己独一的名字,才能避免链接过程中不同变量和函数之间的混淆。在链接中,我们将函数和变量统称为符号(Symbol),函数或变量名就是符号名(Symbol Name)。

符号是链接的粘合剂,没有符号就无法完成链接。每一个目标文件都会有一个相应的符号表(Symbol Table),这个表里记录了目标文件中所用到的所有符号。每个定义的符号有一个对应的值叫做符号值(Symbol Value),对于变量和函数来说,符号值就是它们的地址。

除了函数和变量之外,还存在其它几种不常用到的符号。符号表中的符号可分为全局符号、局部符号、段名、行号等,对于链接而言,只关心全局符号。

⑥.strtab字符串表

因为字符串的长度往往是不定的,所以用固定的结构来表示它比较困难。一种很常见的做法是把字符串集中起来存放到一个表,然后使用字符串在表中的偏移来引用字符串(在汇编中使用offset)。


7:        char * stringLiteral = "stringLiteral";
00401038   mov         dword ptr [ebp-4],offset string "stringLiteral" (004230c0)


⑦.rela.text代码段重定位表

重定位表,也叫作重定位段,用于链接器在处理目标文件时,重定位代码段中那些对绝对地址的引用的位置。比如 .text 段中对外部 printf() 函数的调用。每个要被重定位的地方叫重定位入口(Relocation Entry),OFFSET 表示该入口在所在段中的偏移位置,TYPE 表示重定位入口的类型,VALUE 表示重定位入口的符号名称。

2 加载与执行

项目全部相关文件最终会由链接器链接到一起形成一个可执行文件,Linux 系统中的每个可执行文件都运行在一个进程上下文中,有自己的虚拟地址空间。当shell 运行一个程序时,父shell 进程生成一个子进程,它是父进程的一个复制。

子进程通过系统调用启动加载器。加载器删除子进程现有的虚拟内存段,并创建一组新的代码、数据、堆和栈段。新的栈和堆段被初始化为零。通过将虚拟地址空间中的页映射到可执行文件的页大小的片(chunk), 新的代码和数据段被初始化为可执行文件的内容。最后,加载器跳转到_start地址,它最终会调用应用程序的main 函数。

1a43e22c-0837-11ee-962d-dac502259ad0.png

一个系统中的进程是与其他进程共享CPU和主存资源的。然而,共享主存会形成一些特殊的挑战。如果太多的进程需要太多的内存,那么它们中的一些就根本无法运行。当一个程序没有空间可用时,那就是它运气不好了。内存还很容易被破坏。如果某个进程不小心写了另一个进程使用的内存,它就可能以某种完全和程序逻辑无关的令人迷惑的方式失败。

为了更加有效地管理内存并且少出错,现代系统提供了一种对主存的抽象概念,叫做虚拟内存(VM)。虚拟内存是硬件异常、硬件地址翻译、主存、磁盘文件和内核软件的完美交互,它为每个进程提供了一个大的、一致的和私有的地址空间。通过一个很清晰的机制,虚拟内存提供了三个重要的能力:

1) 它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,它高效地使用了主存。

2) 它为每个进程提供了一致的地址空间,从而简化了内存管理。

3) 它保护了每个进程的地址空间不被其他进程破坏。

虚拟内存是计算机系统最重要的概念之一。它成功的一个主要原因就是因为它是沉默地、自动地工作的,不需要应用程序员的任何干涉。

1a6a8ee0-0837-11ee-962d-dac502259ad0.png

3 汇编代码分析

看以下源代码与汇编代码的对应,以及数据(变量)对应的地址值:


1:    #include 
2:    #include 
3:
4:    int gInitVar = 1;           // .data   加载阶段加载,所以这里无汇编对应
5:    int gUninitVar;             // .bss    加载阶段加载
6:    const int gConstVar = 2;    // .rdata  加载阶段加载
7:
8:    void foo(int i)             // .text   加载阶段加载
9:    {
00401020   push        ebp
00401021   mov         ebp,esp
00401023   sub         esp,4Ch
00401026   push        ebx
00401027   push        esi
00401028   push        edi
00401029   lea         edi,[ebp-4Ch]
0040102C   mov         ecx,13h
00401031   mov         eax,0CCCCCCCCh
00401036   rep stos    dword ptr [edi]
10:       static int staticLocalInitVar = 3;  // .data  加载阶段加载,所以这里无汇编对应
11:       static int staticLocalUninitVar;    // .bss   加载阶段加载
12:       int stack_localVar = 4;             // 栈帧(每个程序运行时会加载若干M栈空间)
00401038   mov         dword ptr [ebp-4],4 // 局部变量保存在栈上,由ebp及其偏移表示
13:       const int LocalConstVar = 5;        // 栈帧(运行时自动分配)
0040103F   mov         dword ptr [ebp-8],5 // 局部常量放在栈区
14:       staticLocalUninitVar = LocalConstVar + gConstVar;
00401046   mov         dword ptr [gUninitVar+4 (00428e40)],7
15:       gUninitVar = staticLocalInitVar + gInitVar;
00401050   mov         eax,[global_data+4 (00425a34)]
00401055   add         eax,dword ptr [gInitVar (00425a30)]
0040105B   mov         [gUninitVar (00428e3c)],eax
16:       int *dynamic_heapData = (int*)malloc(sizeof(int)*10000000);
00401060   push        2625A00h
00401065   call        malloc (00401190)
0040106A   add         esp,4
0040106D   mov         dword ptr [ebp-0Ch],eax
17:       dynamic_heapData[10000000-1] = 9;   // 堆区(运行时动态申请)
00401070   mov         ecx,dword ptr [ebp-0Ch]
00401073   mov         dword ptr [ecx+26259FCh],9
18:       i += stack_localVar + staticLocalUninitVar + gUninitVar;
0040107D   mov         edx,dword ptr [ebp-4]
00401080   add         edx,dword ptr [gUninitVar+4 (00428e40)]
00401086   add         edx,dword ptr [gUninitVar (00428e3c)]
0040108C   mov         eax,dword ptr [ebp+8]
0040108F   add         eax,edx
00401091   mov         dword ptr [ebp+8],eax
19:       printf("%d
",i+dynamic_heapData[10000000-1]);// .rdata,加载阶段加载"%d
"
00401094   mov         ecx,dword ptr [ebp-0Ch]
00401097   mov         edx,dword ptr [ebp+8]
0040109A   add         edx,dword ptr [ecx+26259FCh]
004010A0   push        edx
004010A1   push        offset string "xd4xcbxd0xd0xbdxd7xb6xcexa3xbaxb6xafxccxacxc9xeaxc7xeb
004010A6   call        printf (00403110)
004010AB   add         esp,8
20:       free(dynamic_heapData);             // 堆内存需要显式释放
004010AE   mov         eax,dword ptr [ebp-0Ch]
004010B1   push        eax
004010B2   call        free (00401c10)
004010B7   add         esp,4
21:   }                                       // 栈内存在超出作用域后自动释放





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux系统
    +关注

    关注

    4

    文章

    574

    浏览量

    27035
  • C语言
    +关注

    关注

    180

    文章

    7551

    浏览量

    131889
  • 虚拟机
    +关注

    关注

    1

    文章

    870

    浏览量

    27576
  • 类加载器
    +关注

    关注

    0

    文章

    6

    浏览量

    907

原文标题:理解C语言程序内存分区

文章出处:【微信号:c-stm32,微信公众号:STM32嵌入式开发】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    一文详解C语言内存管理

    C语言内存管理指对系统内存的分配、创建、使用这一系列操作。
    发表于 07-26 16:04 548次阅读
    一文<b class='flag-5'>详解</b>C<b class='flag-5'>语言</b><b class='flag-5'>内存</b>管理

    C语言内存管理详解

    C语言内存管理详解,很不错的一份资料.
    发表于 08-06 23:14

    C语言编程程序内存如何布局

    重点关注以下内容:  C语言程序内存中各个段的组成  C语言
    发表于 08-20 17:03

    C语言编程程序内存如何布局

    C语言编程程序内存如何布局重点关注以下内容:  C语言程序
    发表于 08-29 11:17

    C语言编程程序内存如何布局

    重点关注以下内容:  C语言程序内存中各个段的组成  C语言
    发表于 10-25 09:31

    C语言编程程序内存如何布局

    重点关注以下内容:  C语言程序内存中各个段的组成  C语言
    发表于 10-27 09:24

    C语言编程程序内存如何布局

    C语言编程程序内存如何布局重点关注以下内容:  C语言程序
    发表于 11-05 11:35

    C语言指针详解

    ];//指针的类型是 int(*)[3] (5)int*(*ptr)[4];//指针的类型是int*(*)[4] 怎么样?找出指针的类型的方法是不是很简单? 完整的C语言指针详解pdf格式文档电子发烧友下载地址(共12
    发表于 07-04 03:34

    c语言指针详解

    被回收了,则这个数据就“消亡了”。C语言中的程序数据会按照他们定义的位置,数据的种类,修饰的关键字等因素,决定他们的生命周期特性。实质上我们程序使用的
    发表于 03-26 09:51

    C语言编程程序内存如何布局

    重点关注以下内容:  C语言程序内存中各个段的组成  C语言
    发表于 09-13 15:04

    单片机C语言程序与数据存储的相关资料分享

    目录:一、五大内存分区二、C语言程序的存储区域三、C语言
    发表于 11-30 06:48

    ARM_C语言程序设计详解

    ARM_C语言程序设计详解
    发表于 10-27 15:39 32次下载
    ARM_C<b class='flag-5'>语言</b><b class='flag-5'>程序</b>设计<b class='flag-5'>详解</b>

    存储器的分区内存管理与分区存储管理

    内存固定地划分为若干个大小不等的分区供各个程序使用,每个分区的大小和位置都固定,系统运行期间不再重新划分。
    发表于 05-26 10:28 2973次阅读
    存储器的<b class='flag-5'>分区内存</b>管理与<b class='flag-5'>分区</b>存储管理

    单片机C语言程序与数据存储

    目录:一、五大内存分区二、C语言程序的存储区域三、C语言程序的段四、在C
    发表于 11-20 20:36 12次下载
    单片机C<b class='flag-5'>语言</b><b class='flag-5'>程序</b>与数据存储

    程序内存分区中的堆与栈

    下,堆与栈表示两种内存管理方式; (2)数据结构场景下,堆与栈表示两种常用的数据结构。 1.程序内存分区中的堆与栈 1.1 栈简介 栈由操作系统自动分配释放 ,用于存放函数的参数值、局
    的头像 发表于 11-11 16:21 565次阅读
    <b class='flag-5'>程序</b><b class='flag-5'>内存</b><b class='flag-5'>分区</b>中的堆与栈