1.内存模型
C语言程序中内存大致分为五个区域
1.全局区:bss区(Block Started by Symbol)用来存放程序中未初始化的全局变量的内存区域,data区(data segment)用来存放程序中已初始化的全局变量的内存区域。
2. 常量区: 常量区存放的是常量,如const修饰的全局变量、字符常量、字符串常量以及整型常量等
3. 代码区(text segment): 用来存放程序执行代码的内存区域。
4. 堆(heap):用来存放进程运行中被动态分配的内存段,它的大小并、不固定,可动态扩张或缩减,需要程序员手动申请和释放。当调用malloc分配内存时,新分配的内存就被动态添加到堆上,当调用free释放堆区申请的内存。
5. 栈(stack):存放程序中的局部变量(但不包括static声明的变量,static变量放在数据段中)。同时,在函数被调用时,栈用来传递参数和返回值。由于栈先进先出特点。所以栈特别方便用来保存/恢复调用现场
2.代码区
存放 CPU 执行的机器指令。通常代码区是可共享的(即另外的执行程序可以调用它),使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。代码区通常是只读的,使其只读的原因是防止程序意外地修改了它的指令。另外,代码区还规划了局部变量的相关信息。
总结:你所写的所有代码都会放入到代码区中,代码区的特点是共享和只读。
3.全局区
全局区中主要存放的数据有:全局变量、静态变量(被static修饰的变量)
全局区也叫静态区
这部分可以细分为data区和bss区
3.1全局变量
定义在函数体外部的是全局变量,未初始化是初值自动为0。
存储位置:全局区
生命周期:同整个程序共存亡
作用域:整个程序
3.2 static修饰的变量
1)变量的存放位置在全局区(静态区)
如果静态变量有初值,存放.data区,没有初值存放在.bss区域
2)生命周期为整个程序
3)限制作用域
修饰局部变量: 和普通局部变量作用域没有区别,但是生命周期被延长为整个程序。
也就是在作用函数外有生命但是不能被操作,变成了植物人。
修饰全局变量: 限制在本文件中使用,不能被extern外部引用
4)只初始化一次,初值赋值0
举例对比用static和不用的局部变量在函数中自加操作
#include <stdio.h>
void fun()
{
static int a;
int b=0;
a++;
b++;
printf("%4d %4d\n",a,b);
}
int main()
{
fun();
fun();
return 0;
}
第一次调用打印:1 1
第二次调用打印:2 1
因为被static修饰的局部变量只初始化一次,并且会保留上次调用函数结束后的值,因为存放在全局区了,但是作用域不变还是作用域当前函数内。
4. 常量
程序运行中不会发生变化的量,存在常量区。
4.1 字符型常量
类型为char。从ascii表中找的的字符都是字符常量,不可以改变。用单引号括起来的就是字符常量,例如'A'。
用’’括起来就是字符常量:
'a' -字符a
'\0' 空字符
' ' 空字符
'\n' 换行
例子:
printf("%c\n",'A');
printf("%c\n",97);
printf("%c\n",'\x42');
printf("%c\n",ca);
可以把字符常量赋值给字符变量
举例子:
char a = 'A';
char b = '\x41';
char c = '\101';
printf("%c\n",97);
printf("%c\n",'\x41');
printf("%c\n",ca);
printf("%c\n",'A'+1);
因为C规定转义字符'\x41'中\是转义字符引导符,后跟一个x表示x后面的数字是十六进制表示法,用' '括起来表示一字节ASCII码。\转义符后面加数字代表转义成八进制的字符,后面的数字是八进制。
4.2 字符串常量
用" "括起来的是字符串
"hello" 字符串后面\0
printf("%s\n","hello");
4.3 整型常量
整型就是类型为整数的常量,包括从负数到零到正数的所有整数。可以用二进制、八进制、十进制和十六进制表示。
例如打印15三种表达形式:
int a=15;//把整型常量赋给整型变量
printf("%d\n",0b1111);//二进制
printf("%d\n",15);//十进制
printf("%d\n",017);//八进制
printf("%d\n",0xF);//十六进制
printf("%d\n",a);
打印出来都是15
4.4 浮点型常量
浮点型常量就是类型为浮点数的常量,包括从负数到零到正数的所有浮点数。
float double
4.5 指数常量
用科学计数法表示
3*10^8 ->3e8
2*10^-12 ->2e-12
4.6 标识常量(宏定义)
宏定义:起标识作用
(1)只是单纯的进行文本替换,在预处理的时候进行。
(2)遵循标识符的命名规则
(3)一般大写表示
格式:#define 宏名 常量或表达式
特点:只能单纯替换,不要进行手动运算(原样替换,替换完再计算)
5.栈区
栈区用来存储函数内部的变量(包括main()函数)。它是一个FILO(First In Last Out,先进后出)的数据结构。每当一个函数声明一个新的变量它将被压入栈中。当一个函数运行结束后,这个函数所有在栈中相关的变量都将被删除,而且它们所占用的内存将会被释放。这就产生了函数内部的局部变量。栈区是一段非常特殊的内存区,它由CPU自动管理,所以你不必手动申请和释放内存。
内存由系统自动申请,在变量生命周期结束时由系统释放,也就是说,在程序运行的时候,系统有多个任务,就是检测变量是否该释放了,简单来说,就是cpu要抽时间去执行这部分功能。所以,如果这种变量比较多,不加节制的定义的话,那CPU的额外的工作量就会加大,综合下来,程序的运行效率就会低下。
举例:
char *p = "hello";
//就可以说p在栈区开辟4字节空间存放字符串常量“hello”的首地址
//“hello“:存放在常量区
char buf[32]="hello";
//可以说buf:在栈区开辟32字节空间,存放"hello"字符串
5.1局部变量
定义在函数体内部的为局部变量
存储位置:栈区
生命周期:同函数共存亡
作用域:作用域函数体内部
初值:为初始化时初值未随机值
5.2如何避免栈的溢出
局部数组过大。当函数内部的数组过大时,有可能导致堆栈溢出。
递归调用层次太多。递归函数在运行时会执行压栈操作,当压栈次数太多时,也会导致堆栈溢出。
指针或数组越界。这种情况最常见,例如进行字符串拷贝,或处理用户输入等等。
解决这类问题的办法有两个:一是增大栈空间,二是改用动态分配,使用堆(heap)而不是栈(stack)。
6. 堆区
堆区由我们程序员随时申请,由我们自己随时释放。一般可以用malloc()开辟,用free()释放。
注意:
1)手动开辟堆区空间,要注意内存泄漏:
当指针指向开辟堆区空间后,又对指针重新赋值,则没有指针指向开辟的堆区空间,就会造成内存泄漏。
2)使用完堆区空间后及时释放空间
为什么分这么多区域?在实际生活中,这和公司差不多,部门分的细致,工作分发的就有针对性,效率就会高。