C语言基础:分配内存
## 1、内存分配
静态数据在程序载入内存时分配
自动数据在程序执行代码块时分配,并在程序离开该块时销毁
### (1)自动分配
```C
float x;
char str[] = "hello world";
```
上面两行语句,分别为x和字符串预留了足够的内存空间
### (2)指定分配
```C
int arr[100];
```
上面的声明,预留了100个int类型空间,并且为该段空间提供了一个标识符arr
## 2、动态分配
在程序运行时分配内存
### (1)动态分配内存函数malloc()
```C
void* malloc(size_t size);
//接收所需的内存字节数,分配一段匿名的内存空间,分配成功返回此段空间的首字节地址,分配失败返回空指针
//返回值可以赋值给“通用指针”,在ANSI C中,应该坚持使用强制类型转换,只是为了提高代码的可读性
```
```C
double* ptd;
ptd = (double)malloc(30 * sizeof(double));
//分配了30个double类型的空间,并设置指针ptd指向该段空间的首字节地址。相当于创建了一个内含30个double类型元素的数组
```
----
声明数组的3种方法
1、使用常量表达式,显式指明数组维度,利用数组名访问数组元素,可以在静态内存或自动内存中创建
2、声明变长数组(C99),用变量表达式表示数组的维度,利用数组名访问数组元素,只能在自动内存中创建
3、调用malloc()函数动态分配一段内存,并将返回值赋值给一个指针,利用指针访问数组元素,此指针可以是静态的或自动的
使用第2种和第3种方法可以创建动态数组,即可在程序运行时选择数组的大小和分配内存
----
通常,malloc()要与free()配套使用。
### (2)内存释放函数free()
```C
void free(void* pt); //释放之前malloc()分配的内存,参数即为malloc()的返回值
```
因此,动态分配内存的存储期从调用malloc()开始,到调用free()结束
不能使用free()释放通过其他方式分配的内存
free()的重要性:
静态内存的数量在编译时是固定的,在程序运行期间也不会改变。
自动变量使用的内存数量在程序执行期间自动增加或减少
动态分配的内存数量只会增加,除非使用free()函数进行释放
```C
int main(void)
{
double glad[2000];
for(int i = 0; i < 100; i++)
{
gobble(glad, 2000);
}
return 0;
}
void gobble(double arr[], int n)
{
double* temp = (double*)malloc(n * sizeof(double));
}
```
假设上面的代码忘记使用free()函数释放内存,那么main()每调用一次gobble(),就会分配16000字节的内存。每当gobble()执行完毕后,临时指针变量temp被销毁,无法再访问之前动态分配的内存空间,但此块内存空间也无法被重复使用,因为没有被free()释放。main()调用1000次gabble()函数,就会动态分配1600万个字节的空间。或许在循环结束前,内存就已经被消耗殆尽。
上述问题被称为内存泄漏。
### (3)exit()函数
如果内存分配失败,可以调用exit()函数结束程序,其原型定义在stdlib.h中。
返回值:EXIT_SUCCESS表示普通的程序结束,相当于0;EXIT_FAILURE表示程序异常中止
### (4)calloc()函数
```C
void* calloc(size_t member, size_t size);
//第1个参数是所需的存储单元的数量,第2个参数是存储单元的大小(以字节为单位)
//将分配的内存中的所有位都设置为0
```
```C
int* ptn;
ptn = (int*)calloc(100, sizeof(int)); //分配了100个int类型的空间,并将首字节地址赋值给指针ptn
```
使用calloc()动态分配的内存,也需要使用free()进行释放
### (5)动态内存分配和变长数组
变长数组是自动存储类型,因此程序在离开变长数组定义所在的块之后,变长数组占用的内存空间会被自动释放
malloc()创建的数组不必局限在一个函数内访问。例如,被调函数创建是一个数组并返回指针,供主调函数访问,然后主调函数调用free()释放之前被调用函数分配的内存。因此,free()中的参数可以与malloc()的返回值不同,但必须指向同一块内存
动态创建多维数组,使用变长数组更加方便,使用malloc()则相对繁琐
```C
int n = 5;
int m = 6;
int (*p1)[6]; //声明一个指向内含6个int类型元素的指针
int (*p2)[m]; //声明一个指向内含m个int类型元素的指针,要求支持变长数组
p1 = (int* [6])malloc(n * 6 * sizeof(int)); //维度为(n * 6)的数组
p2 = (int* [m])malloc(n * m * sizeof(int)); //维度为(n * m)的数组,要求支持变长数组
```
### (6)动态内存分配和存储类别
程序将静态对象、自动对象和动态分配的对象存储在不同的区域
静态对象被存储在静态存储区
自动对象被存储在栈中,会随着自动对象的创建与销毁相应的增加和减少
动态分配的内存在调用malloc()或相关函数的时候存在,在调用free()后释放。这部分内存由程序员管理。使用动态内存通常比使用栈内存慢。
## 3、ANSI C类型限定符
### (1)const类型限定符
为变量赋予常量属性,使其值不能被修改
### (2)volatile类型限定符
次限定符告知计算机,代理(而不是变量所在的程序)可以改变变量的值。
通常,它被用于硬件地址以及在其他程序或同时运行的线程*享数据。例如,一个地址上可能存储着当前计算机的时间,无论程序做什么,地址中的值都随着时间变化而改变;或者用一个地址接收另一台计算机传入的信息
volatile的语法与const相同
```C
volatile int locl; //locl是一个易变的位置
volatile int* ploc; //ploc是一个指向易变的位置的指针
```
volatile修饰符设计编译器的优化。假设有以下代码:
```C
val1 = x;
/*一些不使用x的代码*/
val2 = x;
```
以上代码使用了两次x且未改变x的值,但中间有一些不使用x的代码。智能的(进行优化的)编译器会将x的值临时存入寄存器,在val2需要使用x时,从寄存器(而不是从原始内存位置)中读取x的值,以节约时间。
上述过程被称为高速缓存
但如果其它代理在以上两条语句之间改变了x的值,那么就不能进行高速缓存优化了。
现在,如果声明中没有volatile关键字,编译器会假定变量的值在使用过程中不变,然后再尝试优化代码
----
volatile关键字可以与const同时使用,限定一个值。例如,通常用const把硬件时钟设置为程序不能更改的变量,但可以通过代理改变
### (3)restrict类型限定符
restrict关键字允许编译器优化某部分代码以更好地支持计算
它只能用于指针,表明该指针是访问数据对象的唯一且初始的方式
```C
int arr[10];
int* restrict restar = (int*)malloc(10 * sizeof(int));
int* par = arr;
```
指针restar是访问malloc()所分配内存的唯一且初始的方式,因此可以用restrict关键字限定它,
par既不是访问arr数组的初始方式,也不是唯一方式,所以不能用restrict关键字限定
```C
for(int n = 0; n < 10; n++)
{
par[n] += 5;
restar[n] += 5;
ar[n] *= 2;
par[n] += 3;
restar[n] += 3;
}
```
由于之前声明了restar是访问它所指向的数据块的唯一且初始的方式,所以编译器可以把设计restar的两条语句替换成以下语句,效果相同:
```C
restar[n] += 8;
```
但不能将与par相关的两条语句进行类似的替换,否则将导致错误计算,因为在par两次访问相同的数据之间,arr改变了该数据的值
----
在上述例子中,如果未使用restrict关键字,编译器就必须假设最坏的情况,使用restrict关键字之后,编译器可直接对代码进行优化
restrict限定符还可用于函数形参中的指针。这意味着编译器可以假定在函数体内其他标识符不会修改该指针指向的数据,而且编译器可以尝试对其优化,使其不做其他用途。
restrict有两个读者,一个是编译器,告知编译器可以*假定一些优化方案;一个是用户,告知用户要使用满足restrict要求的参数。