C语言程序内存分段
2026/8/18 16:53:05 网站建设 项目流程

在程序运行时,操作系统会给每个进程分配一套独立虚拟地址,把地址划分为不同段,不同段放不同性质的数据。操作系统给进程的虚拟内存空间对所有语言都是一样,但是高级语言在原来内存之上又封装了一层自己的内存模型。C语言直接操作系统系统内存分段,几乎无中间层。本文将主要讲述C语言的内存分段,以及注意事项。
一、进程内存七段:
代码段(.text)
只读数据段(.rodata)
初始化数据段(.data)
未初始化数据段(.bss)
堆(heap)
栈(stack)
内核空间
二、各段总结表
1、汇总表

存放内容读写生命周期
栈(stack)局部变量、形参读写函数调用期间,自动回收
堆(heap)malloc申请内存读写手动malloc、free
未初始化数据段(.bss)未初始化/0的全局/static变量读写进程全程,启动清零
初始化数据段(.data)初始化非0全局/static变量读写进程全程
只读数据段(rodata)字符串字面量、全局const常量只读进程全程
代码段(.text)程序机器指令只读整个进程运行期间

2、使用window上进行内存测试

#include<stdio.h>#include<stdlib.h>// ---------------- 全局变量 ----------------intg_init=100;// .data 初始化全局intg_uninit;// .bss 未初始化全局constintg_const=666;// .rodata 全局只读常量char*str_lit="hello rodata";//字符串字面量存rodatastaticints_init=200;// .data static初始化staticints_uninit;// .bss static未初始化voidfunc(intparam){intlocal_stack;// 栈:局部变量staticintstatic_local=99;// .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("\n====main函数栈变量====\n");printf("main局部变量main_local : %p\n",&main_local);func(10);return0;}

运行结果:

3、特殊情况说明:
(1)、static不论是写在函数内还是函数外,都不会存在栈
static全局-有初始化的放在.data,无初始化放在.bss
(2)、const变量位置区分
局部const只是编译期检查,内存依然可写,不是放rodata

#include"tools.h"#include<windows.h>constintglobal_i=10;//全局const-》放在.rodatavoiddef_f(){constintdef_i=20;//局部const-》放在栈printf("局部const-修改前的值为%d\n",def_i);int*p=(int*)&def_i;//取const变量地址,强制去掉const属性*p=200;//通过指针修改栈上内存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*p1="abc";//"abc"存放到rodata,p1是栈指针//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 * buf=malloc(1010241024):放到堆
(5)编译优化影响
开启-O优化后,有些常量会被直接编译进指令,不一定存在rodata
(6)共享库so,动态链接
动态库的段会映射到进程虚拟地址空间,多个进程共享.text只读段。
三、各段详解
1、代码段.text
存放:编译后的机器指令,就是写的函数代码
属性:只读,防止程序运行修改指令
特点:编译时就确定大小,运行时不能修改
2、只读数据段.rodata(read-only data)
存放:字符串常量、const修饰的全局常量(局部的const int a=10是栈上变量,不在rodata,只有全局/静态const常量放在rodata)
属性:只读,写操作直接报段错误(segment fault)

3、初始化数据段.data
存放:已经初始化的全局变量、static静态变量,不为0
属性:可读可写
程序启动时,把可执行文件中初始值拷贝到该段。在嵌入式系统中,Flash Rom中,存在.data初始值镜像(给RAM-data段用的初始化模板,上电拷贝到RAM)。RAM中,data则存放已初始化全局/static,上电从Flash复制过来
4、未初始化数据段.bss(Block Started by Symbol)
存放:全局变量、static变量,没有初始化或者初始化为0
特点:不占可执行文件大小(exe文件中不存这些0,程序启动操作系统直接把这段内存全部清0.所以定义很大的全局数组放bss,exe不会变大。)
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,自动为0

6、区分虚拟地址和物理地址
打印出来的指针地址都是虚拟地址,不是真实物理内存地址。操作系统做地址转换
7、const局部变量只是编译期保护,可以绕过编译修改,但是属于C语言未定义行为UB,尽量不要写。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询