当前位置:   article > 正文

内存五大模型与elf文件_计算机程序的本质、内存组成与elf格式深度解析

计算机程序的本质、内存组成与elf格式深度解析

一、 在c中分为这几个存储区

内存区域存放内容(elf存放区域)释放时间
在函数体中定义的变量通常是在栈上(栈)由编译器自动分配释放
用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上(堆)一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收
全局区(静态区)全局变量和静态变量的存储是放在一块的。初始化的全局变量和静态变量在一块区域(data段),未初始化的全局变量和未初始化的静态变量在相邻的另一块区域(bss段)。程序结束释放
常量区域函数中的"adgfdf"这样的字符串存放在常量区(text段)程序结束释放
程序代码区存放二进制代码(text段)/

注意:在所有函数体外定义的是全局量,加了static修饰符后不管在哪里都存放在全局区(静态区),在所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用,在函数体内定义的static表示只在该函数体内有效。

int a = 0; //全局初始化区
char *p1; //全局未初始化区

void main()  {
int b; //栈
char s[] = "abc"; //栈
char *p2;  //栈
char *p3 = "123456";   / /123456{post.content}在常量区,p3在栈上
static int c = 0;  //全局(静态)初始化区

p1 = (char *)malloc(10);  //分配得来得10字节的区域在堆区
p2 = (char *)malloc(20); //分配得来得20字节的区域在堆区

strcpy(p1, "123456");   //123456{post.content}放在常量区,编译器可能会将它与p3所指向的"123456"优化成一块
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

堆栈基本对比

是系统提供的功能,特点是快速高效,缺点是有限制,数据不灵活是函数库提供的功能,特点是灵活方便,数据适应面广泛,但是效率有一定降低
栈是系统数据结构,对于进程/线程是唯一的堆是函数库内部数据结构,不一定唯一;不同堆分配的内存无法互相操作;
栈空间分静态分配和动态分配两种。静态分配是编译器完成的,比如自动变量(auto)的分配。动态分配由alloca函数完成。栈的动态分配无需释放(是自动的),也就没有释放函数。为可移植的程序起见,栈的动态分配操作是不被鼓励的堆空间的分配总是动态的,虽然程序结束时所有的数据空间都会被释放回系统,但是精确的申请内存
  • 堆栈 - 在释放内存方面的对比
碎片问题对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出
生长方向生长方向是向上的,也就是向着内存地址增加的方向生长方向是向下的,是向着内存地址减小的方向增长
分配方式堆都是动态分配的,没有静态分配的堆栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现
分配效率栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多

bss段

bss段(bss segment) 用于存放程序中 未初始化的全局变量和静态局部变量 。在目标文件中,这个段并不占据实际空间,它仅是个占位符。

bss段属于 静态内存分配

data段

数据段(data segment) 通常指存放程序中 已始化的全局变量和静态局部变量 的一块内存区域,读写属性

数据段属于 静态内存分配

text段

代码段(code segment/text segment) 通常指存放程序 执行代码的一块内存区域 ,也可能包含些只读的 常数变量(如字符串常量等)只读属性

这部分区域的大小在程序运行前就已确定,且内存区域通常属于只读(某些架构也允许代码段为可写,即允许修改程序)。

堆(heap)

堆是用于存放进程运行中被动态分配的内存段,大小不固定,可动态扩张或缩减。

当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);

当用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。

栈(stack)

栈又称堆栈,是用户存放程序临时创建的局部变量,

是函数括弧“{}”中定义的变量(不包括static声明的变量,static意味着在数据段中存放变量)。

此外,在函数被调用时,其参数也会被压入发起调用的进程栈中,且待到调用结束后,函数的返回值也会被存放回栈中。

由于栈的先进先出(FIFO)特点,所以栈特别方便用来保存/恢复调用现场。

从这个意义上讲,可把堆栈看成一个寄存、交换临时数据的内存区。

解剖工具、命令

## 解剖 Linux C 程序.
file	#查看文件属性
xxd		#二进制格式文件转hex格式 例: xxd t1.o > t1.hex
#ELF 文件基本介绍
readelf
objdump
# 如何精简 Linux C 小程序
stdlibc #介绍 (gnulibc), 如何去掉链接stdlibc
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

注:macOs下无这两个命令,可用brew安装,brew update && brew install binutils,然后用greadelf和gobjdump。

附:一个程序本质上都是由 bss段、data段、text段三个组成。

这样的概念,不知道最初来源于哪里的规定,但在当前的计算机程序设计中是很重要的一个基本概念。

且在嵌入式系统的设计中也非常重要,牵涉到嵌入式系统运行时的内存大小分配,存储单元占用空间大小的问题。

在采用段式内存管理的架构中(比如intel的80x86系统),bss段通常指用来存放程序中未初始化的全局变量的一块内存区域,

一般在初始化时bss 段部分将会清零。bss段属于静态内存分配,即程序一开始就将其清零了。

如,在C语言之类的程序编译完成后,已初始化的全局变量存在.data 段中,未初始化的全局变量保存在.bss 段中。

text和data段都在可执行文件中(在嵌入式系统里一般是固化在镜像文件中),由系统从可执行文件中加载;

而bss段不在可执行文件中,由系统初始化。

全局的未初始化变量存在于.bss段中,具体体现为一个占位符;
全局的已初始化变量存于.data段中;
而函数内的自动变量都在栈上分配空间;
.bss是不占用文件空间的,其内容由操作系统初始化(清零);
.data却需要占用,其内容由程序初始化。因此造成了上述情况。
bss段(未手动初始化的数据)并不给该段的数据分配空间,只是记录数据所需空间的大小;
bss段的大小从可执行文件中得到 ,然后链接器得到这个大小的内存块,紧跟在数据段后面。
data段(已手动初始化的数据)则为数据分配空间,数据保存在目标文件中;
data段包含经过初始化的全局变量以及它们的值。当这个内存区进入程序的地址空间后全部清零。
包含data段和bss段的整个区段此时通常称为数据区。

【例】

// t1.c
int a = 1;
static int b = 4;
int c;
static int d;
char *s1 = "1234";
int main() {
    int e = 2;
    char *s2 = "3456";
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

编译调试文件

gcc test1.c -o test1.o 		//编译

objdump -sx t1.o			//调试
  • 1
  • 2
  • 3

摘取相关信息如下:

Sections:
Idx Name Size VMA LMA File off Algn
……
15 .rodata 0000001a 0000000000400570 0000000000400570 00000570 23
CONTENTS, ALLOC, LOAD, READONLY, DATA
……
24 .data 00000018 0000000000601020 0000000000601020 00001020 2
3
CONTENTS, ALLOC, LOAD, DATA
25 .bss 00000010 0000000000601038 0000000000601038 00001038 2**2
ALLOC

Sections展示了不同的段的大小。

SYMBOL TABLE:
000000000060102c l O .data 0000000000000004 b
000000000060103c l O .bss 0000000000000004 d
……
0000000000601030 g O .data 0000000000000008 s1
0000000000601040 g O .bss 0000000000000004 c
0000000000601028 g O .data 0000000000000004 a

SYMBOL TABLE展示了不同的变量是存在哪的。这里就可以看到
data段有:

  • 初始化了的全局变量a
  • 初始化了的静态变量b
  • 常量字符串s1

bss段有:

  • 未初始化的全局变量c
  • 未初始化的静态变量d
Contents of section .rodata:
 400550 01000200 31323334 00333435 3600      ....1234.3456. 

Contents of section .data:
 6008a8 00000000 00000000 00000000 00000000  ................
 6008b8 01000000 04000000 54054000 00000000  ........T.@.....
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

可以看到rodata段中有我们定义的1234和3456
而data段里有1和4(小端)分别对应a和b

还可以读取elf文件,命令是readelf

参考文档:
一、基础
二、编译和链接
三、目标文件解析
四、静态链接
使用readelf和objdump解析目标文件

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/849309
推荐阅读
相关标签
  

闽ICP备14008679号