一、内存分配
虚拟地址空间在32位环境下的大小为 4GB,在64位环境下的大小为 256TB,那么,一个C语言程序的内存在整个地址空间中是如何分布的呢?数据在哪里?代码在哪里?为什么要这样分布?这些就是本节要讲解的内容。
程序内存在地址空间中的分布情况称为内存模型
(Memory Model)。内存模型由操作系统构建,在Linux和Windows下有所差异,并且会受到编译模式的影响,本节我们讲解Linux下32位环境和64位环境的内存模型。
1.内核空间和用户空间
- 对于32位环境,理论上程序可以拥有 4GB 的虚拟地址空间,我们在C语言中使用到的变量、函数、字符串等都会对应内存中的一块区域。
- 但是,在这 4GB 的地址空间中,要拿出一部分给操作系统内核(每个进程都留部分空间给内核代码)使用,应用程序无法直接访问这一段内存(如果访问,则会发生段错误),这一部分内存地址被称为
内核空间
(Kernel Space)。 - Windows 在默认情况下会将高地址的 2GB 空间分配给内核(也可以配置为1GB),而 Linux 默认情况下会将高地址的 1GB 空间分配给内核。也就是说,应用程序只能使用剩下的 2GB 或 3GB 的地址空间,称为
用户空间
(User Space)。
2.Linux下32位环境的用户空间内存分布情况
我们暂时不关心内核空间的内存分布情况,下图是Linux下32位环境的一种经典内存模型:
对各个内存分区的说明:
3.局部变量
局部变量、形参只在当前函数中有效,函数结束它们的内存不在了。局部变量存储在栈里的, 局部变量的值未知,在使用要特别注意,建议赋值为0。
4.全局变量
一般作用于整个文件,甚至是其它文件,它的生命周期与进程的生命周期是一致,全局变量一般定义在所有函数前面。
5.栈区(先进后出 FILO)
存放函数的参数值、局部变量的值等,其操作方式类似于数据结构中的栈,栈可能是向下增长,也可能向上增长,这个取决系统对栈的管理, 栈上的内存由系统自动分配和释放,不能由程序员控制。
6.堆区
一般由程序员分配和释放,若程序员不释放,程序运行结束时由操作系统回收。 malloc(), calloc, realloc,free()等函数操作的就是这块内存,这也是本章要讲解的重点。
程序员唯一能控制的内存区域就是堆(Heap)
7.代码段
.test:用于存储用户代码(for while if … printf)
.init:用于存储系统给每一个用户进程自动添加的初始化代码
8.数据段
.bss:用于存储未初始化的静态数据(全局变量及static修改的局部变量)及用static修改局部变量(包括赋值及不赋值),它初始化值为0
二、关键字
1.static
- 用static修饰局部变量:使其变为静态存储方式(静态数据区,且初始化只会初始化一次),那么这个局部变量在函数执行完成之后不会被释放,而是继续保留在内存中。
- 用static修饰全局变量:使其只在本文件内部有效,而其他文件不可连接或引用该变量。
- static在修改全局变量,只能本文使用,相当于私有。
- 用static修饰函数:对函数的连接方式产生影响,使得函数只在本文件内部有效,对其他文件是不可见的(这一点在大工程中很重要很重要,避免很多麻烦,很常见)。这样的函数又叫作静态函数。使用静态函数的好处是,不用担心与其他文件的同名函数产生干扰,另外也是对函数本身的一种保护机制。
2.extern
- 引用同一个文件中的变量
- 引用另一个文件中的变量
- 引用另一个文件中的函数
3.const
const主要用来修饰变量、函数形参和类成员函数:
- 用const修饰常量:定义时就初始化,以后不能更改。
- 用const修饰形参:func(const int a){};该形参在函数里不能改变。
- 用const修饰类成员函数:该函数对成员变量只能进行只读操作,就是const类成员函数是不能修改成员变量的数值的。
- 被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。常量是不可以改变的。
4.volatile
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量在内存中的值,而不是使用保存在寄存器里的备份(虽然读写寄存器比读写内存快),保证数据每次都是从内存当中读取。
以下几种情况都会用到volatile:
- 并行设备的硬件寄存器(如:状态寄存器)
- 一个中断服务子程序中会访问到的非自动变量
- 多线程应用中被几个任务共享的变量
5.typedef
typedef为C语言的关键字,作用是为一种数据类型定义一个新名字。这里的数据类型包括内部数据类型(int,char等)和自定义的数据类型(struct等)。在编程中使用typedef目的一般有两个,一个是给变量一个易记且意义明确的新名字,另一个是简化一些比较复杂的类型声明。
typedef int INT; //INT代替int
typedef unsigned int u32;
INT b; // == int b
u32 c; // unsigned int c
typedef struct info
{
char name[20];
char sex;
float high;
float weight;
}*stu,STU_INFO;
//将struct info *取别名为stu
//将struct info 取别名为STU_INFO
typedef char * PCHAR; PCHAR == char *
PCHAR p1, p2; //可行,同时声明两个指向字符的指针
typedef int Array[100]; //Array == int 有100个元素
Array ia;//相当于 int ia[100];
int (*func)(int *p); //函数指针
int (*func[5])(int *);//函数指针数组
//函数声明 函数指针(函数返回值 int *)
int *(*pFun)(int, char*);
//取别名
typedef int *(*pFun)(int, char*); //函数指针 ,返回值为 int *
6. malloc/free
如果程序需要自己开辟内存与释放内存空间,可使用C语句提供的库函数实现。开辟空间是会一直存在。它的生命周期跟进程生命周期一致(除非提前释放),开辟内存在堆区间。