变量、局部变量、静态全局变量和静态局部变量运行时内存分配方式研究"/>
[C/C++语言]全局变量、局部变量、静态全局变量和静态局部变量运行时内存分配方式研究
0、前言
在分析一个程序的堆栈、bss段、text段、RO、RW、ZI等概念时,首先区分一下程序进程和程序可执行文件,同时区分一下哈佛结构和冯诺依曼结构。
程序进程就是程序运行时的程序,程序文件是编译后生成的可执行文件,比如.bin文件等。
哈佛结构和冯诺依曼结构的主要区别就是处理器能不能实现取指令和取数据的并发进行。嵌入式芯片中主要是哈佛结构,PC机上是冯诺依曼结构。
(1)经典的哈佛结构:
程序存储器和数据存储器是各自独立的存储器。处理器应该有两套总线,一套是程序存储器的数据和地址总线,一套是数据存储器的数据和地址总线。取指令和取数据能并发进行。51的程序进程的逻辑代码段放在ROM中,而变量部分则放在RAM中,取ROM中的指令和RAM中的变量是两套总线。
(2)改进型哈佛结构:
程序存储器和数据存储器是各自独立的存储器。处理器只有一套总线,分时访问程序存储器和数据存储器,但是在处理器中有I-Cache和D-Cache将程序和数据分开,所以处理器仍然可以并步执行取指令和取数据。从ARM9开始以后所有的ARM处理器内核都是改进型的哈佛结构。ARM的逻辑代码和变量都是存放在RAM中的,但是,它在内存中划分了两部分的空间,其中一部分放逻辑代码,另一部分存放变量,之间不会相互干扰。
(3)冯诺依曼结构:
没有程序存储空间和数据存储空间之分。处理器只有一套总线,取指令和取数据是不能同时进行的。程序进程全部在RAM中,他们之间一般是按照代码的执行顺序依次存储。由于全部在RAM中,运行速度快,所需的RAM多。
1、C/C++语言程序内存分配方式
(1)从静态存储区域分配
在程序编译的时候就已经分配好内存,这块内存在程序的整个运行期间都存在,主要包括全局变量、static(静态)变量。
(2)从栈上分配
在执行函数时,函数内局部变量(注意必须是非static)的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。另外,函数调用时参数传递及跳转场景创建等需要的内存也是从栈上分配的。
(3)从堆上分配
一般我们也叫动态内存分配。程序在运行的时候用malloc或new申请的内存,程序员自己负责在何时用free或delete释 放内存。动态内存的生存期由程序员决定,使用非常灵活,但如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,频繁地分配和释放不同大小的堆空间将会产生堆内碎块。
2、程序运行时刻内存分配典型方式
主要分为:.bss段、.data段、.rodata段、.text段、stack段和heap段。
2.1、.bss段
存放没有被初始化或初始化为0的全局变量、静态变量。因为是全局变量或静态变量,所以在程序运行的整个生命周期内都存在于内存中。这个段中的变量只占用程序运行时的内存空间,而不占用程序文件的储存空间(即编译生成的可执行文件大小不包含此段内容)。
/*代码片段*/
char g_buffer[20*1024]={0};
int main(void)
{//define local static variablestatic int l_static_var_a = 3;static int l_static_var_b;//define local variableint l_var_a = 4;int l_var_b;l_var_b = l_var_a + l_static_var_a + g_static_var_a;return 0;
}/*运行时内存分配*/
0000000000201040 g O .bss 0000000000005000 g_buffer依次可以看出全局的初始化为0的变量,占用.bss段内存。
来源:静态存储区域
2.2、.data段
初始化过的全局变量数据段,该段用来保存初始化了的非0的全局变量,如果全局变量初始化为0,则编译有时会出于优化的考虑,将其放在bss段中。因为也是全局变量,所以在程序运行的整个生命周期内都存在于内存中。与bss段不同的是,data段中的变量既占程序运行时的内存空间,也占程序文件的储存空间。
来源:静态存储区域
/*代码片段*/
char g_buffer[20*1024]={1};
int main(void)
{//define local static variablestatic int l_static_var_a = 3;static int l_static_var_b;//define local variableint l_var_a = 4;int l_var_b;l_var_b = l_var_a + l_static_var_a + g_static_var_a;return 0;
}/*内存占用情况*/0000000000201040 g O .data 0000000000005000 g_buffer结论:初始化非零的全局变量,占用.data段。
2.3、.rodata段
该段是常量数据段,存放只读数据,比如printf语句中的格式字符串和开关语句的跳转表。也就是你所说的常量区。例如,全局作用域中的 const int ival = 10,ival存放在.rodata段;再如,函数局部作用域中的printf("Hello world %d\n", c);语句中的格式字符串"Hello world %d\n",也存放在.rodata段。
但是注意并不是所有的常量都是放在常量数据段的,其特殊情况如下:
1)有些立即数与指令编译在一起直接放在代码段。
2)对于字符串常量,编译器会去掉重复的常量,让程序的每个字符串常量只有一份。
3)用数组初始化的字符串常量是没有放入常量区的。
4)用const修饰的全局变量是放入常量区的,但是使用const修饰的局部变量只是设置为只读起到防止修改的效果,没有放入常量区。
5)有些系统中rodata段是多个进程共享的,目的是为了提高空间的利用率。
注意:程序加载运行时,.rodata段和.text段通常合并到一个Segment(Text Segment)中,操作系统将这个Segment的页面只读保护起来,防止意外的改写。
/*代码片段*/const short gcs_var_c = 10;
int main(void)
{//define local static variablestatic int l_static_var_a = 3;static int l_static_var_b;//define local variableint l_var_a = 4;int l_var_b;l_var_b = l_var_a + l_static_var_a + g_static_var_a;printf("Hello\n");return 0;
}/*运行时内存占用*/
0000000000000704 g O .rodata 0000000000000002 gcs_var_c
2.4、.text段
text段就是代码段,用来存放程序的代码(如函数)。它与rodata段的主要不同是,存储可执行文件的指令;也有可能包含一些只读的常数变量,例如字符串常量等。
2.5、stack
(1)为函数内部的局部变量提供存储空间。
(2)进行函数调用时,存储“过程活动记录”。
(3)用作暂时存储区,如计算一个很长的算术表达式时,可以将部分计算结果压入堆栈。
来源:栈空间
2.6、heap
堆能够根据需要自动增长。堆区域是用来动态分配的内存空间,用 malloc 函数申请的,用free函数释放。calloc、realloc和malloc类似:前者返回指针的之前把分配好的内存内容都清空为零;后者改变一个指针所指向的内存块的大小,可以扩大和缩小,它经常把内存拷贝到别的地方然后将新地址返回。
来源:堆空间
3、针对嵌入式开发编译程序文件中分区讨论
Code,RO_data,RW_data,ZI_data,RO,RW,常出现在嵌入式程序编译完成后的统计,例如MDK,IAR,ARM GCC等。
(1)Code:即代码域,它指的是编译器生成的机器指令。
(2)RO_data:ReadOnly data,即只读数据域,它指程序中用到的只读数据,全局变量,例如C语言中const关键字定义的全局变量就是典型的RO-data。
(3)RW_data:ReadWrite data,即可读写数据域,它指初始化为“非0值”的可读写数据,程序刚运行时,这些数据具有非0的初始值,且运行的时候它们会常驻在RAM区,因而应用程序可以修改其内容。例如全局变量或者静态变量,且定义时赋予“非0值”给该变量进行初始化。
(4)ZI_data:ZeroInitialie data,即0初始化数据,它指初始化为“0值”的可读写数据域,它与RW_data的区别是程序刚运行时这些数据初始值全都为0,而后续运行过程与RW-data的性质一样,它们也常驻在RAM区,因而应用程序可以更改其内容。包括未初始化的全局变量,和初始化为0的全局变量。
(5)RO:只读区域,包括RO_data和code。
当程序存储在ROM中时,所占用的大小:Code + RO_data + RW_data 。
当程序执行时, RW_data和 ZI_data在RAM中,RO_data和Code视cpu架构(51、arm、x86)不同处于ROM或者RAM中。其中ZI_data对应.bss段,RW_data对应数据段,code对应.text段, RO_data对应和.rodata段。
4、代码示例及objdump/readelf工具
/** test objdump and readelf tools* Date:2021-9-15* */
#include <stdio.h>//define global variable which been initialized
int g_var_a = 1;
//define global variable which not been initialized
int g_var_b;
//define static global variable
static int g_static_var_a = 2;
static int g_static_var_b;int main(void)
{//define local static variablestatic int l_static_var_a = 3;static int l_static_var_b;//define local variableint l_var_a = 4;int l_var_b;l_var_b = l_var_a + l_static_var_a + g_static_var_a;return 0;
}
zhanghui@pekshcsitd24207:/mnt/disk/project/test_zh/test_objdump_readelf$ objdump -t test.otest.o: file format elf64-x86-64SYMBOL TABLE:
0000000000000238 l d .interp 0000000000000000 .interp
0000000000000254 l d .note.ABI-tag 0000000000000000 .note.ABI-tag
0000000000000274 l d .note.gnu.build-id 0000000000000000 .note.gnu.build-id
0000000000000298 l d .gnu.hash 0000000000000000 .gnu.hash
00000000000002b8 l d .dynsym 0000000000000000 .dynsym
0000000000000348 l d .dynstr 0000000000000000 .dynstr
00000000000003c6 l d .gnu.version 0000000000000000 .gnu.version
00000000000003d8 l d .gnu.version_r 0000000000000000 .gnu.version_r
00000000000003f8 l d .rela.dyn 0000000000000000 .rela.dyn
00000000000004b8 l d .init 0000000000000000 .init
00000000000004d0 l d .plt 0000000000000000 .plt
00000000000004e0 l d .plt.got 0000000000000000 .plt.got
00000000000004f0 l d .text 0000000000000000 .text
00000000000006a4 l d .fini 0000000000000000 .fini
00000000000006b0 l d .rodata 0000000000000000 .rodata
00000000000006b4 l d .eh_frame_hdr 0000000000000000 .eh_frame_hdr
00000000000006f0 l d .eh_frame 0000000000000000 .eh_frame
0000000000200df0 l d .init_array 0000000000000000 .init_array
0000000000200df8 l d .fini_array 0000000000000000 .fini_array
0000000000200e00 l d .dynamic 0000000000000000 .dynamic
0000000000200fc0 l d .got 0000000000000000 .got
0000000000201000 l d .data 0000000000000000 .data
000000000020101c l d .bss 0000000000000000 .bss
0000000000000000 l d ment 0000000000000000 ment
0000000000000000 l df *ABS* 0000000000000000 crtstuff.c
0000000000000520 l F .text 0000000000000000 deregister_tm_clones
0000000000000560 l F .text 0000000000000000 register_tm_clones
00000000000005b0 l F .text 0000000000000000 __do_global_dtors_aux
000000000020101c l O .bss 0000000000000001 completed.7698
0000000000200df8 l O .fini_array 0000000000000000 __do_global_dtors_aux_fini_array_entry
00000000000005f0 l F .text 0000000000000000 frame_dummy
0000000000200df0 l O .init_array 0000000000000000 __frame_dummy_init_array_entry
0000000000000000 l df *ABS* 0000000000000000 test.c
0000000000201014 l O .data 0000000000000004 g_static_var_a
0000000000201020 l O .bss 0000000000000004 g_static_var_b
0000000000201018 l O .data 0000000000000004 l_static_var_a.2254
0000000000201024 l O .bss 0000000000000004 l_static_var_b.2255
0000000000000000 l df *ABS* 0000000000000000 crtstuff.c
00000000000007f4 l O .eh_frame 0000000000000000 __FRAME_END__
0000000000000000 l df *ABS* 0000000000000000
0000000000200df8 l .init_array 0000000000000000 __init_array_end
0000000000200e00 l O .dynamic 0000000000000000 _DYNAMIC
0000000000200df0 l .init_array 0000000000000000 __init_array_start
00000000000006b4 l .eh_frame_hdr 0000000000000000 __GNU_EH_FRAME_HDR
0000000000200fc0 l O .got 0000000000000000 _GLOBAL_OFFSET_TABLE_
00000000000006a0 g F .text 0000000000000002 __libc_csu_fini
0000000000000000 w *UND* 0000000000000000 _ITM_deregisterTMCloneTable
0000000000201000 w .data 0000000000000000 data_start
000000000020101c g .data 0000000000000000 _edata
00000000000006a4 g F .fini 0000000000000000 _fini
0000000000000000 F *UND* 0000000000000000 __libc_start_main@@GLIBC_2.2.5
0000000000201028 g O .bss 0000000000000004 g_var_b
0000000000201000 g .data 0000000000000000 __data_start
0000000000000000 w *UND* 0000000000000000 __gmon_start__
0000000000201008 g O .data 0000000000000000 .hidden __dso_handle
00000000000006b0 g O .rodata 0000000000000004 _IO_stdin_used
0000000000000630 g F .text 0000000000000065 __libc_csu_init
0000000000201030 g .bss 0000000000000000 _end
00000000000004f0 g F .text 000000000000002b _start
000000000020101c g .bss 0000000000000000 __bss_start
00000000000005fa g F .text 0000000000000028 main
0000000000201020 g O .data 0000000000000000 .hidden __TMC_END__
0000000000000000 w *UND* 0000000000000000 _ITM_registerTMCloneTable
0000000000201010 g O .data 0000000000000004 g_var_a
0000000000000000 w F *UND* 0000000000000000 __cxa_finalize@@GLIBC_2.2.5
00000000000004b8 g F .init 0000000000000000 _init
更多推荐
[C/C++语言]全局变量、局部变量、静态全局变量和静态局部变量运行时内存分配方式研究
发布评论