我们本节来看看程序的结构体系。我们在平时所编写的程序中,其实它的结构是非常值得研究的,这样有助于我们编写出效率更高的代码,并且也有助于我们理解整个可执行文件的架构。好了,废话不多说。那么我们的程序究竟是由什么构成的呢?它是由不同的段所构成的,包括代码段、数据段等。
程序有两个特征,静态特征和动态特征。静态特征指的是指令和数据,而动态特征则指的是执行指令处理数据的动作。下来我们来看看程序源代码到可执行程序文件的对应关系,如下图所示
我们之前在 C 语言中曾经也说过这方面的知识,今天我们再次回顾下。由上图我们可以看到初始化过的全局变量和初始化过的 static 修饰的局部变量都存储在 .data 段,而没有初始化的变量则存储在 .bss 段,函数都存储在 .text 段。
下来我们来一一介绍下上面的几个段,首先是代码段(.text),代码段具有以下特征
1、源代码中的可执行语句编译后进入代码段;
2、代码段在有内存管理单元的系统中具有只读属性;
3、代码段的大小在编译结束后就已经固定(不能动态改变);
4、代码段中可以包含常量数据(如常量字符串)。
其次是数据段,它包括(.data, .bss, .rodata)。数据段用于处处源代码中具有全局生命期的变量,其中 .bss 段存储未初始化(或初始化为 0)的变量、.data 段存储具有非 0 的初始值的变量、.rodata 存储 const 关键字修饰的变量。那么我们思考下:我们在前面说初始化过的和未初始化的全局变量和静态局部变量是分开来存放的,为什么要搞的如此复杂呢?一般在程序加载后,.bss 段中的所有内存单元被初始化为 0,将程序文件中 .data 段相关的初始值写入对应的内存单元。因为 .bss 段中的变量不用在程序文件中保存初始值,从而减少可执行程序文件的体积,并且提高程序的加载效率。
下来我们还是以代码为例来进行分析说明
int g_no_var_v; int g_var_v = 1; int g_main() { static no_var_v1; static var_v2 = 2; return 0; }
我们编译来看看结果
我们看到在 .data 和 .bss 段中各占了8字节。接下来我们把全局变量 g_no_var_v 的类型改为 char,然后再看看结果
我们看到结果还是 8,因为它是四字节对齐的,因此结果还是 8,如果我们改为两个 char 类型的,那么结果便为 4 了。g_main() 的入口地址便是 .text 段的起始地址了,也再次证明了我们的程序是自己指定入口函数的了。
下来我们来看看栈(Stack)。栈在程序中的本质是一片连续存储的内存空间,SP 寄存器作为栈顶“指针”实现入栈操作和出栈操作。如下图所示
栈的作用大致有以下几方面:
1、中断发生时,栈用于保存寄存器的值;
2、函数调用时,栈用于保存函数的活动记录(栈帧信息);
3、并发编程时,每一个线程拥有自己独立的栈。
下来我们来看看堆(Heap),堆是一片“闲置”的内存空间,其目的是用于提供动态的内存分配;当然堆空间的分配是需要 malloc 函数的支持的,它在使用完成后也需要借助于 free 函数进行手动的释放空间。再来看看内存映射段(Memory Mapping Segment),在内核中,是直接将硬盘文件的内容直接映射到内存映射段(mmap),动态链接库在可执行程序加载时映射到内存映射段,以便程序在执行时能够创建匿名映射区存放程序数据。
我们简单来介绍下内存映射文件的原理:
1、将硬盘上的文件数据逻辑映射到内存中(零耗时);
2、通过缺页中断进行文件数据的实际载入(一次数据拷贝);
3、映射后的内存的读写就是对文件数据的读写;
映射关系如下:
在程序的结构体系中,它的整体分布如下图所示
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。