程序或-内存区域分配(五个段)--终于搞明白了

2023-11-07

一.

在学习之前我们先看看ELF文件。

ELF分为三种类型:.o 可重定位文件(relocalble file),可执行文件以及共享库(shared library),三种格式基本上从结构上是一样的,只是具体到每一个结构不同。下面我们就从整体上看看这3种格式从文件内容上存储的方式,spec上有张图是比较经典的:如上图:
 其实从文件存储的格式来说,上面的两种view实际上是一样的,Segment实际上就是由section组成的,将相应的一些section映射到一起就叫segment了,就是说segment是由0个或多个section组成的,实际上本质都是section。在这里我们首先来仔细了解一下section和segment的概念:section就是相同或者相似信息的集合,比如我们比较熟悉的.text .data  .bss section,.text是可执行指令的集合,.data是初始化后数据的集合,.bss是未初始化数据的集合。实际上我们也可以将一个程序的所有内容都放在一起,就像dos一样,但是将可执行程序分成多个section是很有好处的,比如说我们可以将.text section放在memory的只读空间内,将可变的.data section放在memory的可写空间内。

从可执行文件的角度来讲,如果一个数据未被初始化那就不需要为其分配空间,所以.data和.bss一个重要的区别就是.bss并不占用可执行文件的大小,它只是记载需要多少空间来存储这些未初始化数据,而不分配实际的空间。

可以通过命令 $ readelf -l a.out 查看文件的格式和组成。

二.

 

站在汇编语言的角度,一个程序分为:
数据段 -- DS
堆栈段 -- SS
代码段 -- CS
扩展段 -- ES

站在高级语言的角度,根据APUE,一个程序分为如下段:
text
data (initialized)
bss
stack
heap

        1.一般情况下,一个可执行二进制程序(更确切的说,在Linux操作系统下为一个进程单元,在UC/OSII中被称为任务)在存储(没有调入到内存运行)时拥有3个部分,分别是代码段(text)、数据段(data)和BSS段。这3个部分一起组成了该可执行程序的文件

可执行二进制程序 = 代码段(text)+数据段(data)+BSS段

        2.而当程序被加载到内存单元时,则需要另外两个域:堆域和栈域。图1-1所示为可执行代码存储态运行态的结构对照图。一个正在运行的C程序占用的内存区域分为代码段、初始化数据段、未初始化数据段(BSS)、堆、栈5个部分。

正在运行的C程序 = 代码段+初始化数据段(data)+未初始化数据段(BSS)+堆+栈

       3.在将应用程序加载到内存空间执行时,操作系统负责代码段、数据段和BSS段的加载,并将在内存中为这些段分配空间。栈亦由操作系统分配和管理,而不需要程序员显示地管理;堆段由程序员自己管理,即显示地申请和释放空间。

          4.动态分配与静态分配,二者最大的区别在于:1. 直到Run-Time的时候,执行动态分配,而在compile-time的时候,就已经决定好了分配多少Text+Data+BSS+Stack。2.通过malloc()动态分配的内存,需要程序员手工调用free()释放内存,否则容易导致内存泄露,而静态分配的内存则在进程执行结束后系统释放(Text, Data), 但Stack段中的数据很短暂,函数退出立即被销毁。

 

 图1-1(从可执行文件a.out的角度来讲,如果一个数据未被初始化那就不需要为其分配空间,所以.data和.bss一个重要的区别就是.bss并不占用可执行文件的大小,它只是记载需要多少空间来存储这些未初始化数据,而不分配实际的空间)

三.

代码段 --text(code segment/text segment)
text段在内存中被映射为只读,但.data和.bss是可写的。
text段是程序代码段,在AT91库中是表示程序段的大小,它是由编译器在编译连接时自动计算的,当你在链接定位文件中将该符号放置在代码段后,那么该符号表示的值就是代码段大小,编译连接时,该符号所代表的值会自动代入到源程序中。

数据段 -- data
data包含静态初始化的数据,所以有初值的全局变量和static变量在data区。段的起始位置也是由连接定位文件所确定,大小在编译连接时自动分配,它和你的程序大小没有关系,但和程序使用到的全局变量,常量数量相关。数据段属于静态内存分配。 

bss段--bss
bss是英文Block Started by Symbol的简称,通常是指用来存放程序中未初始化的全局变量的一块内存区域,在程序载入时由内核清0。BSS段属于静态内存分配。它的初始值也是由用户自己定义的连接定位文件所确定,用户应该将它定义在可读写的RAM区内,源程序中使用malloc分配的内存就是这一块,它不是根据data大小确定,主要由程序中同时分配内存最大值所确定,不过如果超出了范围,也就是分配失败,可以等空间释放之后再分配。BSS段属于静态内存分配。

stack:
栈(stack)保存函数的局部变量(但不包括static声明的变量, static 意味着 在数据段中 存放变量),参数以及返回值。是一种“后进先出”(Last In First Out,LIFO)的数据结构,这意味着最后放到栈上的数据,将会是第一个从栈上移走的数据。对于哪些暂时存贮的信息,和不需要长时间保存的信息来说,LIFO这种数据结构非常理想。在调用函数或过程后,系统通常会清除栈上保存的局部变量、函数调用信息及其它的信息。栈另外一个重要的特征是,它的地址空间“向下减少”,即当栈上保存的数据越多,栈的地址就越低。栈(stack)的顶部在可读写的RAM区的最后。

heap:
堆(heap)保存函数内部动态分配内存,是另外一种用来保存程序信息的数据结构,更准确的说是保存程序的动态变量。堆是“先进先出”(First In first Out,FIFO)数据结构。它只允许在堆的一端插入数据,在另一端移走数据。堆的地址空间“向上增加”,即当堆上保存的数据越多,堆的地址就越高。

下图是APUE中的一个典型C内存空间分布图:

所以可以知道传入的参数,局部变量,都是在栈顶分布,随着子函数的增多而向下增长.
函数的调用地址(函数运行代码),全局变量,静态变量都是在分配内存的低部存在,而malloc分配的堆则存在于这些内存之上,并向上生长.

举例1:

#include <stdio h=""> const int g_A = 10; //代码段 int g_B = 20; //数据段 static int g_C = 30; //数据段 static int g_D; //BSS段 int g_E; //BSS段 char *p1; //BSS段 void main( ) { int local_A; //栈 int local_B; //栈 static int local_C = 0; //数据段 static int local_D; //数据段 char *p3 = "123456"; //123456在代码段,p3在栈上 p1 = (char *)malloc( 10 ); //堆,分配得来得10字节的区域在堆区 strcpy( p1, "123456" ); //123456{post.content}放在常量区,编译器可能会将它与p3所指向 的"123456"优化成一块 printf("hight address\n"); printf("-------------栈--------------\n"); printf( "栈, 局部变量, local_A, addr:0x%08x\n", &local_A ); printf( "栈, 局部变量,(后进栈地址相对local_A低) local_B, addr:0x%08x\n", &local_B ); printf("-------------堆--------------\n"); printf( "堆, malloc分配内存, p1, addr:0x%08x\n", p1 ); printf("------------BSS段------------\n"); printf( "BSS段, 全局变量, 未初始化 g_E, addr:0x%08x\n", &g_E, g_E ); printf( "BSS段, 静态全局变量, 未初始化, g_D, addr:0x%08x\n", &g_D ); printf( "BSS段, 静态局部变量, 初始化, local_C, addr:0x%08x\n", &local_C); printf( "BSS段, 静态局部变量, 未初始化, local_D, addr:0x%08x\n", &local_D); printf("-----------数据段------------\n"); printf( "数据段,全局变量, 初始化 g_B, addr:0x%08x\n", &g_B); printf( "数据段,静态全局变量, 初始化, g_C, addr:0x%08x\n", &g_C); printf("-----------代码段------------\n"); printf( "代码段,全局初始化变量, 只读const, g_A, addr:0x%08x\n\n", &g_A); printf("low address\n"); return; } </stdio>

运行结果:

hight address -------------栈-------------- 栈, 局部变量, local_A, addr:0xffa70c1c 栈, 局部变量,(后进栈地址相对local_A低) local_B, addr:0xffa70c18 -------------堆-------------- 堆, malloc分配内存, p1, addr:0x087fe008 ------------BSS段------------ BSS段, 全局变量, 未初始化 g_E, addr:0x08049a64 BSS段, 静态全局变量, 未初始化, g_D, addr:0x08049a5c BSS段, 静态局部变量, 初始化, local_C, addr:0x08049a58 BSS段, 静态局部变量, 未初始化, local_D, addr:0x08049a54 -----------数据段------------ 数据段,全局变量, 初始化 g_B, addr:0x08049a44 数据段,静态全局变量, 初始化, g_C, addr:0x08049a48 -----------代码段------------ 代码段,全局初始化变量, 只读const, g_A, addr:0x08048620 low address

注意:
编译时需要-g选项,这样才可以看elf信息;
readelf -a a.out 

查看这个执行文件的elf信息,摘录部分如下:重点注意其中data段,text段还要有bss段的地址,然后比较这个地址和上面的运行结果,是否是在elf文件的各个段的地址之内。

Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .interp PROGBITS 08048114 000114 000013 00 A 0 0 1 [ 2] .note.ABI-tag NOTE 08048128 000128 000020 00 A 0 0 4 [ 3] .gnu.hash GNU_HASH 08048148 000148 000020 04 A 4 0 4 [ 4] .dynsym DYNSYM 08048168 000168 000070 10 A 5 1 4 [ 5] .dynstr STRTAB 080481d8 0001d8 000058 00 A 0 0 1 [ 6] .gnu.version VERSYM 08048230 000230 00000e 02 A 4 0 2 [ 7] .gnu.version_r VERNEED 08048240 000240 000020 00 A 5 1 4 [ 8] .rel.dyn REL 08048260 000260 000008 08 A 4 0 4 [ 9] .rel.plt REL 08048268 000268 000028 08 A 4 11 4 [10] .init PROGBITS 08048290 000290 000017 00 AX 0 0 4 [11] .plt PROGBITS 080482a8 0002a8 000060 04 AX 0 0 4 [12] .text PROGBITS 08048310 000310 0002e8 00 AX 0 0 16 [13] .fini PROGBITS 080485f8 0005f8 00001c 00 AX 0 0 4 [14] .rodata PROGBITS 08048614 000614 000326 00 A 0 0 4 [15] .eh_frame PROGBITS 0804893c 00093c 000004 00 A 0 0 4 [16] .ctors PROGBITS 08049940 000940 000008 00 WA 0 0 4 [17] .dtors PROGBITS 08049948 000948 000008 00 WA 0 0 4 [18] .jcr PROGBITS 08049950 000950 000004 00 WA 0 0 4 [19] .dynamic DYNAMIC 08049954 000954 0000c8 08 WA 5 0 4 [20] .got PROGBITS 08049a1c 000a1c 000004 04 WA 0 0 4 [21] .got.plt PROGBITS 08049a20 000a20 000020 04 WA 0 0 4 [22] .data PROGBITS 08049a40 000a40 00000c 00 WA 0 0 4 [23] .bss NOBITS 08049a4c 000a4c 00001c 00 WA 0 0 4 [24] .comment PROGBITS 00000000 000a4c 000114 00 0 0 1 [25] .debug_aranges PROGBITS 00000000 000b60 000020 00 0 0 1 [26] .debug_pubnames PROGBITS 00000000 000b80 00003a 00 0 0 1 [27] .debug_info PROGBITS 00000000 000bba 0001f4 00 0 0 1 [28] .debug_abbrev PROGBITS 00000000 000dae 00006f 00 0 0 1 [29] .debug_line PROGBITS 00000000 000e1d 000058 00 0 0 1 [30] .debug_frame PROGBITS 00000000 000e78 00003c 00 0 0 4 [31] .debug_str PROGBITS 00000000 000eb4 00000d 00 0 0 1 [32] .debug_loc PROGBITS 00000000 000ec1 000043 00 0 0 1 [33] .shstrtab STRTAB 00000000 000f04 000143 00 0 0 1 [34] .symtab SYMTAB 00000000 0015e8 000560 10 35 60 4 [35] .strtab STRTAB 00000000 001b48 0002ad 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific)

注意静态变量初始化为零和全局静态变量初始化为零的情况,都是存储在bss段★★★

从上面的elf文件可以看出,

  [23] .bss              NOBITS          08049a4c 000a4c 00001c 00  WA  0   0  4

  [22] .data             PROGBITS    08049a40000a40 00000c 00  WA  0   0  4

  [12] .text             PROGBITS      08048310000310 0002e8 00  AX  0   0 16

但是在结果中显示: BSS段, 静态局部变量,   初始化,    local_C, addr:0x08049a58 

 (0x08049a58 大于0x08049a4c 属于bss段)是初始化的静态局部变量但是却属于bss段,为什么?

原因是:local_C是局部静态变量但是却初始化为零。这和没有初始化,默认是零的情况一样,都存储在bss段,如果初始化为其他的值,那么local_C这个变量就会存储在data段。

对于GNU的编译器来说 虽说显式初始化成零和不初始化都会去.bss段,但是在文件的大小上还是稍微有一些区别的,会把显示初始化的这个量占用一个16字节的描述。所以如果确实是要初始化成0的话,就不要写显式的初始化了,虽然只有16个字节 但是省点是点~~~

到底怎么减小我们的BIN:
首先,尽量少用静态变量和全局变量。从效率和大小的角度来看,全局变量都不是最优选择(全局变量需要从非寄存器中加载)。
其次,如果必须要用全局变量,那也可以,尽量不要在定义时初始化,特别是不初始化成零的,可以在函数中进行初始化。

/*全局变量a 进行了初始化*/
int a[1000] = {1,2,3,4,5,6};
int b[100];
int main()
{
  return 0;
}

/*全局变量a 未进行了初始化,在函数中负责,但是bin大小却明显降低了*/
int a[1000];
int b[100];
int main()
{
  int i = 0;
  for(i = 7; --i;)
  {
    a[i] = i;
  }
  return 0;
}

 

可执行文件大小由什么决定?

可执行文件在存储时分为代码段、数据段和BSS段三个部分。

【例一】
程序1:
int ar[30000];
void main()
{
    ......

程序2:
int ar[300000] =  {1, 2, 3, 4, 5, 6 };
void main()
{
    ......

发现程序2编译之后所得的.exe文件比程序1的要大得多。当下甚为不解,于是手工编译了一下,并使用了/FAs编译选项来查看了一下其各自的.asm,发现在程序1.asm中ar的定义如下:
_BSS SEGMENT
     ?ar@@3PAHA DD 0493e0H DUP (?)    ; ar
_BSS ENDS 
而在程序2.asm中,ar被定义为:
_DATA SEGMENT
     ?ar@@3PAHA DD 01H     ; ar
                DD 02H
                DD 03H
                ORG $+1199988
_DATA ENDS 
区别很明显,一个位于.bss段,而另一个位于.data段,两者的区别在于:全局的未初始化变量存在于.bss段中,具体体现为一个占位符;全局的已初始化变量存于.data段中;而函数内的自动变量都在栈上分配空间。

.bss是不占用.exe文件空间的,其内容由操作系统初始化(清零);而.data却需要占用,其内容由程序初始化,因此造成了上述情况。

 

以上仅仅做为学习只用!!

 

参考材料:

可执行文件(ELF)格式的理解

http://www.cnblogs.com/xmphoenix/archive/2011/10/23/2221879.html

C程序内存区域分配(5个段作用)

http://www.cnblogs.com/bigbigtree/archive/2012/11/23/2784137.html

《Linux 下缓冲区溢出攻击的原理及对策》

https://www.ibm.com/developerworks/cn/linux/l-overflow/

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

程序或-内存区域分配(五个段)--终于搞明白了 的相关文章

  • Docker Compose 笔记 网络

    Compose 简介 1 引入 通过前面的知识 我们知道使用一个 Dockerfile 模板文件 可以让用户很方便的定义一个单独的应用容器 从而得到一个镜像 然而 在日常工作中 经常会碰到需要多个容器相互配合来完成某项任务的情况 例如要实现
  • xss挑战1-10关

    level1 没有任何过滤 直接URL后面加XSS测试语句 弹窗 level2 有搜索框 像第一关一样在搜索框测试一下 但是没有像第一关一样弹窗 查看源码发现我们的XSS语句被赋值给value并且在input标签里 所以我们需要闭合valu

随机推荐

  • Acwing-4644. 求和

    暴力解法 TLE了hh include
  • git入门教程

    文章目录 Git教程 创建版本库 把文件添加到版本库 查看文件状态 版本回退 工作区和暂存区 管理 撤销修改 删除文件 Git远程仓库 Git教程 Git是目前世界上最先进的分布式版本控制系统 Git用C语言开发 Linus一直痛恨的CVS
  • Midjourney关键字--兔子类

    1 A lovely and happy Pixar style rabbit baby wearing a checked shirt carrying a schoolbag to school with a sweet smile b
  • zkEVM战局简析:zkSync、StarkNet、Scroll和挑战者们

    不同的项目正在探索不同的方向 这或许是最利于行业的发展模式 原文作者 Grant Griffith 由 Odaily星球日报 Azuma 编译 编者按 10 月 28 日 由 Matter Labs 构建的以太坊扩容解决方案正式发布了 zk
  • JAVA之猜数字游戏

    1 随机生成一个0 99 包括0和99 的数字 从控制台输入猜测的数字 输出提示太大还是太小 继续猜测 直到猜到为止 游戏过程中 记录猜对所需的次数 游戏结束后公布结果 打开记事本 写如下一段代码 import java util Rand
  • SpringBoot2对接线程池

    SpringBoot2对接线程池 1 配置线程池Bean package com itennishy test config import java util concurrent ThreadPoolExecutor import org
  • Qt中extern全局变量的使用

    参考网址 https blog csdn net Soar dream article details 101025153 https blog csdn net weixin 45571586 article details 118109
  • C语言位操作

    C语言位操作 1 位与 按照二进制位一一对应 只有全1才为真 否则为假 特定位置置0用位与 2 位或 按照二进制 有真则真 全假才假 特定位置置1用位或 3 位取反 按照二进制位 1变为0 0变为1 逻辑取反 数当成整体 不变二进制 不是0
  • 错误 Angular2 Can't bind to 'routerLink' since it isn't a known property of 'a'

    Can t bind to routerLink since it isn t a known property of a 解决办法 检查是否没有 import RouterModule import RouterModule from a
  • 阿里java编程规范之异常处理、安全规约、MySql数据库

    注 本文内容整理自 阿里java编码规范 除 编程规约 外的其它规则 异常处理 强制 1 Java类库中可以通过预检查方式规避的 RuntimeException不应该通过catch的方式来处理 如 IndexOutOfBoundsExce
  • LruCache基本使用和原理分析

    最近在研究时区问题时 时区的底层实现涉及到BasicLruCache集合的使用 故而对LruCache做了部分的了解 BasicLruCache 是 Android 提供的一个简单的 LRU 缓存实现 但在标准的 Java 类库中并不存在
  • 基于SpringBoot的校园志愿者管理系统

    末尾获取源码 开发语言 Java Java开发工具 JDK1 8 后端框架 SpringBoot 前端 HTML Vue 数据库 MySQL5 7和Navicat管理工具结合 服务器 Tomcat8 5 开发软件 IDEA Eclipse
  • windows 安装 minio

    windows 安装 minio 1 通过powershell 安装 Invoke WebRequest Uri https dl min io server minio release windows amd64 minio exe Ou
  • 小试一下Google App Engine

    这两天关心了一下云 所以也看到google app engine了 今天小小试了一下 做下记录 主页 登录http code google com intl zh CN appengine 下载 App Engine SDK GoogleA
  • 使用StarRocks导入大数据:详细教程及示例代码

    使用StarRocks导入大数据 详细教程及示例代码 StarRocks是一个快速 可扩展的大数据分析引擎 它提供了高性能的数据导入功能 在本文中 我们将介绍如何使用StarRocks导入大数据 并提供相应的示例代码 步骤1 准备工作 在开
  • 云计算平台常用命令

    云计算IAAS篇 mysql篇 mysql uroot p000000 使用root账号登录mysql use mysql 切换到mysql层 show tables 查询mysql数据库列表 select from mysq
  • linux高性能服务器开发之TCP/IP协议族(1)

    TCP IP协议族体系结构以及主要协议 每层协议完成不一样的功能 上层协议得借助下层协议提供的服务 计网 数据链物层 数据链物层实现网卡接口的网络驱动程序 网络驱动程序隐藏一些 物理层不同电气特性 为上层提供一个统一的接口 常用的协议ARR
  • 【中兴ZXV10 B860A1.1】

    这里写自定义目录标题 开启adb 开启adb 部分盒子的ADB调试位置 在设置页面中可以有开启开发者选项 地区界面不同 位置不同有的在设置里 如果找不到 直接按住遥控器 返回 不放 5秒后 快速不停按 左键 点击 打开ADB调试 这时侯让你
  • 2021-08-04 读书笔记:Python 学习手册(2)

    读书笔记 Python 学习手册 2 结于2021 08 04 OREILY的书籍 可读性很强 入门类 而且这本书很厚 第三部分 语句和语法 第四部分 函数 第三部分 语句和语法 第10章 Python语句简介 Python是面向过程的 基
  • 程序或-内存区域分配(五个段)--终于搞明白了

    一 在学习之前我们先看看ELF文件 ELF分为三种类型 o 可重定位文件 relocalble file 可执行文件以及共享库 shared library 三种格式基本上从结构上是一样的 只是具体到每一个结构不同 下面我们就从整体上看看这