C语言程序内存分段
在程序运行时操作系统会给每个进程分配一套独立虚拟地址把地址划分为不同段不同段放不同性质的数据。操作系统给进程的虚拟内存空间对所有语言都是一样但是高级语言在原来内存之上又封装了一层自己的内存模型。C语言直接操作系统系统内存分段几乎无中间层。本文将主要讲述C语言的内存分段以及注意事项。一、进程内存七段代码段.text只读数据段(.rodata)初始化数据段(.data)未初始化数据段(.bss)堆(heap)栈(stack)内核空间二、各段总结表1、汇总表段存放内容读写生命周期栈stack局部变量、形参读写函数调用期间自动回收堆heapmalloc申请内存读写手动malloc、free未初始化数据段(.bss)未初始化/0的全局/static变量读写进程全程启动清零初始化数据段.data初始化非0全局/static变量读写进程全程只读数据段rodata字符串字面量、全局const常量只读进程全程代码段.text程序机器指令只读整个进程运行期间2、使用window上进行内存测试#includestdio.h#includestdlib.h// ---------------- 全局变量 ----------------intg_init100;// .data 初始化全局intg_uninit;// .bss 未初始化全局constintg_const666;// .rodata 全局只读常量char*str_lithello rodata;//字符串字面量存rodatastaticints_init200;// .data static初始化staticints_uninit;// .bss static未初始化voidfunc(intparam){intlocal_stack;// 栈局部变量staticintstatic_local99;// .data函数内staticstaticintstatic_local_no;// .bss函数内static未初始化int*p_heap(int*)malloc(sizeof(int));//堆内存printf(函数func内部地址\n);printf(函数参数param-》栈 : %p\n,param);printf(局部变量local_stack-》栈: %p\n,local_stack);printf(static局部(初始化) : %p\n,static_local);printf(static局部(未初始化) : %p\n,static_local_no);printf(malloc堆地址 : %p\n,p_heap);free(p_heap);}intmain(void){intmain_local;// main的局部变量栈printf(【.text 代码段】main函数地址 : %p\n,main);printf(【.rodata全局const】g_const : %p\n,g_const);printf(【.rodata字符串字面量】 : %p\n,hello rodata);printf(【.data已初始化全局】g_init : %p\n,g_init);printf(【.data static全局】s_init : %p\n,s_init);printf(【.bss未初始化全局】g_uninit : %p\n,g_uninit);printf(【.bss static全局】s_uninit : %p\n,s_uninit);printf(\nmain函数栈变量\n);printf(main局部变量main_local : %p\n,main_local);func(10);return0;}运行结果3、特殊情况说明(1、static不论是写在函数内还是函数外都不会存在栈static全局-有初始化的放在.data,无初始化放在.bss(2)、const变量位置区分局部const只是编译期检查内存依然可写不是放rodata#includetools.h#includewindows.hconstintglobal_i10;//全局const-》放在.rodatavoiddef_f(){constintdef_i20;//局部const-》放在栈printf(局部const-修改前的值为%d\n,def_i);int*p(int*)def_i;//取const变量地址强制去掉const属性*p200;//通过指针修改栈上内存printf(局部const-修改后的值为%d\n,def_i);}intmain(){SetConsoleOutputCP(65001);SetConsoleCP(65001);printf(全局const的值为%d\n,global_i);def_f();return0;}运行结果(3)字符串字面量字符串常量修改程序会崩溃字符串拷贝到数组上是可以修改的intmain(){SetConsoleOutputCP(65001);SetConsoleCP(65001);char*p1abc;//abc存放到rodatap1是栈指针//p1[0]1;charp2[]123;//把字符串复制到栈数组可修改printf(数组修改前第一个字符的值为%c\n,p2[0]);p2[0]x;printf(数组修改后第一个字符的值为%c\n,p2[0]);return0;}运行结果4大数组局部大数组char buf [1010241024]放到栈会栈溢出全局大数组char buf [1010241024]放到bss段不会栈溢出动态分配char * bufmalloc(1010241024):放到堆5编译优化影响开启-O优化后有些常量会被直接编译进指令不一定存在rodata6共享库so动态链接动态库的段会映射到进程虚拟地址空间多个进程共享.text只读段。三、各段详解1、代码段.text存放编译后的机器指令就是写的函数代码属性只读防止程序运行修改指令特点编译时就确定大小运行时不能修改2、只读数据段.rodataread-only data存放字符串常量、const修饰的全局常量局部的const int a10是栈上变量不在rodata只有全局/静态const常量放在rodata属性只读写操作直接报段错误segment fault3、初始化数据段.data存放已经初始化的全局变量、static静态变量不为0属性可读可写程序启动时把可执行文件中初始值拷贝到该段。在嵌入式系统中Flash Rom中存在.data初始值镜像给RAM-data段用的初始化模板上电拷贝到RAM。RAM中data则存放已初始化全局/static上电从Flash复制过来4、未初始化数据段.bssBlock Started by Symbol存放全局变量、static变量没有初始化或者初始化为0特点不占可执行文件大小exe文件中不存这些0程序启动操作系统直接把这段内存全部清0.所以定义很大的全局数组放bssexe不会变大。5、堆heap位置bss上方向上增长地址变大来源malloc、realloc、calloc、free手动申请释放特点完全手动管理内存操作系统不会自动回收大小运行时动态变化没有自动清零calloc除外常见问题内存泄漏、野指针、越界6、栈stack位置高地址向下增长地址变小存放局部变量、函数形参、函数返回地址、临时变量特点函数进入自动分配函数退出自动回收不需要手动free栈空间很小Linux默认一般8MB左右栈越界直接段错误栈溢出stack overflow–不要定义巨大局部数组会直接栈溢出崩溃7、内核空间最高地址用户程序不能直接访问操作系统内核使用访问直接报错。四、实际开发注意事项1、栈空间有限千万不要局部定义超大数组大数组用全局bss或malloc堆2、不要修改字符串字面量3、static局部变量不在栈函数多次调用会保留上次值多线程环境下容易出现线程不安全4、堆内存一定要free否则内存泄漏free之后不要再继续使用指针野指针5、.bss全局变量自动初始化为0栈局部变量不会清零默认为随机垃圾值一定手动初始化。voidf(){inta;//栈随机值不是0}intg;//全局bss自动为06、区分虚拟地址和物理地址打印出来的指针地址都是虚拟地址不是真实物理内存地址。操作系统做地址转换7、const局部变量只是编译期保护可以绕过编译修改但是属于C语言未定义行为UB尽量不要写。

相关新闻