C语言内存布局 · 一
一.基本概念
1. 变量
1.1. 全局变量(外部变量):出现在代码块之外的变量
1.2. 局部变量(自动变量):一般情况下,代码块内部定义的变量为局部变量,也可使用auto显示定义。
1.3. 静态变量:内存位置在程序执行期间一直不改变的变量,用关键字static修饰。代码块内部的静态变量只能被该代码块内部访问,代码块外部的静态变量只能被定义这个变量的文件访问。
PS.用extern修饰变量时,根据具体情况,既可以看作是定义也可以看作是声明;但extern修饰函数时只能是定义,没有二义性。(声明可以多处,定义只能一处)
采用static修饰的全局变量只对此文件有效,不能extern到其他文件中使用。
例:
extern int a; //声明一个全局变量a
int a; //定义一个全局变量a
Extern int a = 0; //定义一个全局变量a并给初值(一旦给予赋值,便一定是定义)
int a = 0; //定义一个全局变量a并给初值
2. 作用域
作用域是指程序源代码中定义变量的区域,包含了其中变量,常量,函数等等定义信息和赋值信息。即该定义的可执行范围。
3. 函数
一段可以被其他代码块引用的代码块。
PS. C语言中函数默认全局,可使用关键字static将函数声明为静态函数。
二.内存五区
假设给一个进程分配4G的内存:
1G |
内核区 (用户代码不能读写) |
|||
0xc0000 000(3G)
3G
0x08048000
|
栈区(向下增长) |
栈段 |
||
内存映射段 |
||||
堆区(向上增长) |
堆段 |
|||
全 局 区 |
未初始化全局变量(bss) |
bss段 |
可读写数据 |
|
已初始化全局变量(.data) |
数据段 |
|||
文字常量区(.rodata) |
只读数据 |
|||
程序区 |
代码段 |
|||
保留区(未映射) |
||||
①字段解释:
1. Stack段:局部变量存储区域(一般情况定义的变量都存储在这里)
2. Heap段:用户动态分配内存区域
3. 内存映射段:存放动态库/静态库,以及文件映射,匿名映射等一切有依赖性的东西的内存区域。
4. bss段:存放未初始化的全局或静态变量内存区域
5. 数据段:一般指存放已初始化的全局变量或静态变量的内存区域
6. 代码段:一般指存放程序执行代码的内存区域
②C-存储区:
2. 堆区:一般由程序员分配释放,若程序员不释放,程序结束时系统释放。它与数据结构中的堆完全不同,其存储方式类似于链表。(malloc、calloc、realloc、free)
3. 全局区/静态区:程序结束后由系统释放,用于存放全局变量、静态变量,已初始化的全局变量和静态变量放在一块区域,未初始化的全局变量和未初始化的静态变量放在相邻另一块区域。
4. 文字常量区:程序结束后由系统释放,用于存放常量、字符串等。
5. 程序代码区:存放函数体(类成员函数和全局函数)的二进制代码。
③申请后系统的处理方式:
栈区:只要栈剩余空间大于所申请的空间,系统将为程序提供内存,否则将报错。(栈溢出)
④申请大小限制:
⑤申请效率:
栈区:栈内存分配运算内置于处理器指令集中,效率很高,但分配的内存容量有限,程序员无法控制。
堆区:由动态内存开辟,一般速度比较慢,容易产生内存碎片,使用结束后需程序员自行释放内存空间。
⑥内存分析:
1.栈区:
#include <stdio.h>
#include <string.h>
char *getMen(){
char p[64];
//栈区存放 局部变量
strcpy(p, "123456789");
//向p所代表的内存中写入内容
//printf("p:%s\n",p);
return p;
void main(){
char *temp = NULL;
temp = getMen();
if (temp == NULL)
return 0;
printf("temp:%s\n", temp);//输出temp
system("pause");
return 0;}
此代码什么都不会输出。因为变量p是直接在栈区中创建,相当于在栈区中划分出64个单位的内存空间。而strcpy函数只管拷贝,不区分所属哪个区。getMen函数在将p返回出去时,p仅仅代表这个内存空间的别名,而不是字符串“123456789”。
但是在这段代码中子函数getMen运行结束,其分配的栈内存被系统回收,存的内容自然被清空。如果想要获得输出结果,则需要将变量p用static定义成静态变量。
PS.定义*getMen时使用指针,防止开辟64个单位大小的内存空间。超出编译器的栈区所能开出的最大范围,导致编译过程崩溃。
2.堆区:
#include <stdio.h>
#include <string.h>
char *getMen(int num){
char *p1 = NULL;
p1 = (char*)malloc(sizeof(char) * num);
if (p1 == NULL)
return NULL;
return p1;}
int main(){
char *temp = NULL;
temp = getMen(10);
if (temp == NULL)
return 0;
strcpy(temp, "111222");
//向temp做指向的内存空间中copy数据,而不是向指针变量temp中
printf("temp:%s\n", temp);//输出temp
system("pause");
return 0;}
由于通过malloc函数动态分配的内存空间在堆区中,无法被系统自动析构,所以可以输出结果。除此之外,存放在堆区与存放在全局区没有区别。
3.全局区:
#include <stdio.h>
#include <string.h>
char *getStr1(){
char *p1 = "abcd";
return p1;}
char *getStr2(){
char *p2 = "abcd";
return p2;}
int main(){
char *p1 = NULL;
char *p2 = NULL;
p1 = getStr1();
p2 = getStr2();
//打印p1 p2 所指向内存空间的数据,不是p1 p2中的数据
//p1 = abcd , p2 = abcd
printf("p1:%s , p2:%s \n", p1, p2);
//打印p1,p2的值
//p1: 19184372 , p2: 19184372
printf("p1:%d , p2:%d \n", p1, p2);
system("pause");
return 0;}
栈区和全局区都是存储变量,但最大的不同在于:栈区会自动进行解析,而全局区是在整个程序结束后才进行解析。但一个局部变量对应的函数结束时,这个变量对应的内存空间就会被系统清除。因此栈区中的“char p[64]”p变量在返回后没有输出结果。而“static char p[64]”p变量是全局变量,因此返回后有输出结果。(关于指针和引用会在下一篇日志中详解)
在main函数中首先在栈区开辟了名为p1,p2的内存空间。接着调用getStr1,getStr2两个函数,此时在栈区又开辟了名为p1,p2的内存空间,但是“char *p1 = "abcd"”采用“=”,说明将字符串常量“abcd”赋值给变量p1。而字符串常量保存在文字常量区,同样也是在整个程序运行结束后,系统才开始析构,可以近似算作存储在全局区。这样通过调用变量p1的内存空间,可以找到对应放在全局区的“abcd”
在getStr1函数结束后,p1变量被清除,那么p1变量指向“abcd”的箭头同样被擦除,现在只剩下,main函数的p1变量和p1指向“abcd”的箭头。p2同理,但在全局区,系统不会再次开辟一段内存空间存储“abcd”,如果新的内存空间所储存的数据和全局区内已有的内存空间数据一致,则系统只会保留一份。
4.函数调用:
Ⅰ.在程序未执行结束时,main函数里分配的空间均可以被其他自定义函数访问。
Ⅱ.自定义函数若在堆区(malloc动态分配内存等)或全局区(字符串常量等)分配的内存,即便此函数结束,这些内存空间也不会被系统回收,内存中的内容可以被其他自定义函数和main函数使用。
⑦C++-存储区:
1. 栈区:局部变量,函数传参值,自动释放,效率高但内存少。
2. 堆区:malloc函数从堆上申请内存,用free释放内存,若不释放,程序结束释放
3. 自由存储区:自由存储区是C++基于new操作符的一个抽象概念。new操作符在此申请内存,用delete释放内存,若不释放,程序结束释放
4. 全局区/静态区:存储全局变量或静态变量。内存在编译时就分配好了(程序执行前),整个程序运行期间都存在,程序结束时释放。
5. 常量存储区:存储常量(const),不允许修改。