C语言内存分配(详细解说)

来源:互联网 发布:国内域名需要备案吗 编辑:程序博客网 时间:2024/06/05 22:59

要想完全理解C语言的内存分配,必须要知道计算机的组成和基本原理。

1. 计算机的组成

计算机的五大组成部分:运算器、控制器、存储器、输入设备和输出设备。

我们都知道计算机的处理中心是CPU,它主要由运算器和控制器组成。

1.运算器

实现算术运算和逻辑运算的部分,主要对数据进行加工处理。

2.控制器

计算机的指挥中心,它通过地址访问存储器,从存储器中取出指令(程序),并指出下一指令在存储器中的位置,将取出的指令经指令寄存器送往指令译码器,经过对指令的分析产生相应的操作,控制其他部件的有条不紊的工作。

程序计数器(pc) pc寄存器中的内容是,下一条要取的指令的16位单元存储地址。当执行一条指令时,首先需要根据pc中存放的指令地址,将指令由内存取到指令寄存器中,此过程称为“取指令”。与此同时,pc中的地址或自动加1或由转移指针给出下一条指令的地址。如此循环,执行每一条指令。

指令寄存器(IR) 存放当前要执行的指令

指令译码器(ID)对现行指令进行分析,确定指令类型、指令所要完成的操作以及寻址方式

时序产生器 用于产生时序脉冲和节拍电位去控制计算机的有序工作

状态/条件寄存器 用于保存指令执行完成后产生的条件码。例如:运算是否溢出,结果是正是负,是否有进位等。而且还保存中断和系统工作状态等信息。

微操作信号发生器 把指令提供的操作信号、时序产生器提供的时序信号以及由控制功能部件反馈的状态信号等综合成特定的操作序列,从而完成取指令的执行控制。

执行指令有四个步骤:取指令、指令译码、按指令操作码执行、形成下一条指令地址。

3.存储器

计算机存放所有数据和程序的记忆部分,它分为两大类:一类是内部存储器(内存),一类是外部存储器(外存)。存储器由若干个存储单元组成,每个存储单元都有一个地址,计算机通过地址对存储单元进行读写。

4.输入设备

向计算机输入信息(程序、数据、声音、文字、图形、图像等)的设备(键盘、鼠标、图形扫描仪、触摸屏、条形码输入器、光笔等)。

5.输出设备

主要有显示器、打印机和绘图仪等。

2. 内存分配

在任何程序设计环境及语言中,内存管理都十分重要。在目前的计算机系统或嵌入式系统中,内存资源仍然是有限的。因此在程序设计中,有效地管理内存资源是程序员首先考虑的问题

1. C程序结构:可执行代码存储时

下面是C语言可执行程序的基本情况:
image
上面分别是:
- test-代码区
- data-全局初始化数据区/静态数据区
- bss -未初始化数据区
- dec -十进制总和
- hex -十六进制总和
- filename -文件名

我们可以看出程序在未运行前,没有调入到内存时,分为三个部分:代码区(text)、数据区(data)、未初始化数据区(bss)。

1.代码区(text)

存放CPU可执行的机器指令,由于程序被经常使用,防止其被意外修改,代码区通常是只读的。

2.全局初始化数据区/静态数据区(data)

存放被初始化的全局变量、静态变量(全局静态变量和局部静态变量)、常量数据(如字符串常量)。

3.未初始化数据区(BSS)

存放未初始化的全局变量,BSS这个叫法是根据早期的汇编运算符而来的,这个汇编运算符标志着一个块的开始。BSS区的数据在程序开始执行之前被内核初始化为0或空指针(NULL)。

2.C程序结构:程序执行时

一个正在运行的C程序,是经过编译、连接后形成的二进制映像文件。其占用的内存分为4个段区域:
- 代码段
- 数据段(初始化数据区/静态数据区、未初始化数据区;或者是只读数据段Ro-data即常量数据,已经初始化读写数据段(RW-data),未初始化数据段即BBS(ZI-data))
- 堆区
- 栈区

程序运行时内存区域如下图:
image

1. 代码区

存放函数体的二进制代码。

指令根据程序设计流程依次执行,对于顺序指令,则只会执行一次,如果反复,则需使用跳转指令,如果进行递归,则需借助栈来实现。

代码区包括操作码和要操作的对象(或对象的地址引用),如果是立即数(即具体的数值,如2),将直接包含在代码中;如果是局部数据,将在栈中分配空间,然后引用该数据的地址;如果是BSS区和数据区,在代码中同样引用该数据的地址。

2. 数据段
  • 初始化的数据段(data),包含只读数据段
全局初始化数据区/静态数据区,只初始化一次。上面已经说过,在程序编译时,该区域已经被分配好了,这块内存在程序的整个运行期间都存在,当程序结束时,才会被释放。1. 已初始化的读写数据段:已初始化数据是在程序中声明,并且具有初值的变量,这些变量需要占用存储器的空间,在程序执行时它们需要位于可读写的内存区域内,并且有初值,以供程序运行时读写。在程序中一般为已经初始化的全局变量,已经初始化的静态局部变量(static修饰的已经初始化的变量)2. 只读数据段是程序使用的一些不会被更改的数据,使用这些数据的方式类似查表式的操作,由于这些变量不需要更改,因此只需要放置在只读存储器中即可。一般是const修饰的变量以及程序中使用的文字常量一般会存放在只读数据段中。
  • 未初始化的数据段(BSS)
在运行时改变其值。未初始化数据是在程序中声明,但是没有初始化的变量,这些变量在程序运行之前不需要占用存储器的空间。与读写数据段类似,它也属于静态数据区。但是该段中数据没有经过初始化。未初始化数据段只有在运行的初始化阶段才会产生,因此它的大小不会影响目标文件的大小。在程序中一般是没有初始化的全局变量和没有初始化的静态局部变量。
3. 栈区(stack)

存放函数的参数值和局部变量,由编译器自动分配释放,其操作方式类似于数据结构的栈。其特点是不需要程序员去考虑内存管理的问题,很方便;同时栈的容量很有限,在Linux系统中,栈的容量只有8M,并且当相应的范围结束时(如函数),局部变量就不能再使用。

4. 堆区(heap)

有些操作对象只有在程序运行时才能确定,这样编译器在编译时就无法为他们预先分配空间,只有程序运行时才分配,这就是动态内存分配。堆区就是用于动态内存分配(如malloc的动态内存分配),堆在内存中位于bss区和栈区之间,一般由程序员申请和释放。

之所以分配如此多的区域,主要是因为:

一个进程在运行时,代码是根据流程依次执行的,代码只需访问一次,当然跳转或递归时代码会被执行多次,而数据一般都需要访问多次,因此单独开辟空间以便访问和节约空间。

下面是一个详细的代码,来全面分析内存分配情况:

main.c

int a = 0; //a在全局初始化数据区char *p1; //p1在bss区(未初始化全局变量)static int c = 0; //c在全局初始化数据区(c是全局静态变量)struct employee{    char name[20];     int age;    float score; }e1; //e1在全局初始化数据区int main(){    int b; //b在栈区(局部变量)    char s[] = “abc”; //s在栈区,“abc”在常量区(全局初始化数据区)    char *p2; //p2在栈区    char *p3 = “123456”; //p3在栈区,“123456”在常量区(全局初始化数据区)    static int d = 0; //d在全局初始化数据区(静态局部变量)    struct student    {        char *name; //name在栈区,name指针指向是在堆区        int age;        float score;     }s1; //s1在栈区    p1 = (char*)malloc(10); //分配得来的10个字节的区域在堆区    p2 = (char*)malloc(20); //分配得来的20个字节的区域在堆区    name = (cahr *)malloc(20); //分配得来的20个字节的区域在堆区    /*从常量区的“hello world”字符串复制到刚分配到的堆区*/    strcpy(p1, “hello world”);     free(p1); //释放内存    free(p2); //释放内存}

关于堆栈之间的区别参考:堆栈之间的区别

常量相关,未完待续。