当前位置:首页 > 学习资源 > 讲师博文 > C语言的内存模型

C语言的内存模型 时间:2024-02-21      来源:华清远见

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)使用完堆区空间后及时释放空间

 

为什么分这么多区域?在实际生活中,这和公司差不多,部门分的细致,工作分发的就有针对性,效率就会高。

 

上一篇:本地存储和cookie之间的区别是什么?

下一篇:入行嵌入式,哪些技术能力是必须的

戳我查看2020年嵌入式每月就业风云榜

点我了解华清远见高校学霸学习秘籍

猜你关心企业是如何评价华清学员的

干货分享
相关新闻
前台专线:010-82525158 企业培训洽谈专线:010-82525379 院校合作洽谈专线:010-82525379 Copyright © 2004-2024 北京华清远见科技发展有限公司 版权所有 ,京ICP备16055225号-5京公海网安备11010802025203号

回到顶部