基本存储类别和动态内存分配

本文部分内容参考了C Primer Plus(sixth edition)一书

存储类别和内存分布

  简单介绍一下变量的存储类别和它的内存分布,我们先通过一张表来了解一些基本术语:

术语 解释 举例
作用域  描述程序中可访问的标识符的区域 块作用域,函数作用域,函数原型作用域,文件作用域
链接 变量属于哪一部分私有 外部链接(全局可见),内部链接(单一文件可见),无链接(块内可见)
存储期 描述通过标识符访问对象的生命期 静态存储期,线程存储期,自动存储期,动态分配存储期
翻译单元 一个源文件包含多个头文件,编译时当作一个翻译单元,作一个文件 头文件包含在源代码文件内

 

  〉〉块指的是一对花括号括起来的代码。C99之后,块也可以是循环语句+循环体(单一语句)。具有块作用域的变量只能在块内可见。C99之前,变量只允许声明在块的开头。无链接。

  〉〉函数作用域仅用于goto标签,一个标签首次出现在函数的内层块中,它的作用域将延伸直整个函数。无链接。

  〉〉函数原型作用域:声明函数原型时形参的作用域,仅限于原型。无链接。注意,如果在某一函数int fun的原型中声明变长数组,应该这样声明int fun(int n, int m, int vla[n][m]);

  〉〉文件作用域:定义在函数外,从定义处到文件末尾均可见。具有内部和外部链接。

  〉〉外部链接:具有外部链接的变量在程序的所有文件中都可见。例如,main.c中定义了一个外部链接变量int var;在lock.c中就要用extern int x;来声明使用。

  〉〉内部链接:具有该链接的变量只在该文件可见。例如:static int var; var使用时不必加上像外部链接变量的声明。

  〉〉无链接:不具有文件作用域的变量无链接。

  〉〉静态存储期:生命(在内存中存在时间):程序开始到结束。

  〉〉线程存储期:生命:从线程开始到结束。

  〉〉自动存储期:生命以块为参照,离开块即消失。

  〉〉动态分配存储期:由程序员管理。

  总结表如下:

存储类别 存储期 作用域 链接 声明方式
自动 自动 块内
静态外部链接 静态 文件(整个程序) 外部 所有函数外,在另一文件使用前需要用extern再次声明
静态内部链接 静态 文件(单一翻译单元) 内部 所有函数外,用static声明
静态无链接 静态 块内,用static声明

 

  此外还有寄存器变量,由于这种变量实用性不高,不讨论。

  在内存中,静态数据(包括常量)占用一个区域,自动数据占用另一个区域,动态分配的数据占第三个区域(内存堆)。

动态内存分配函数malloc()和释放函数free()

  在C99标准发布之前,我们不能使用变量作为声明数组的元素个数。如果我们要创建一个自定义长度的数组,就不能使用原来定义普通静态数组的方法。那么该怎么办呢?这时候就要通过动态分配内存来声明动态数组了。

  malloc()函数原型在stdlib.h文件中。它返回一个指向void类型的指针,这个指针指向分配内存的首地址。下面这段代码分配一个具有10个int类型元素的动态数组,用指针ptr指向这个数组的首元素。

int *ptr, n = 10;
ptr = (int *)malloc( (size_t)n * sizeof(int) );

  malloc()的括号里应填入所需要分配的内存大小,以字节为单位,类型是size_t。为了提供程序的可移植性,我们用了sizeof()运算符来获取当前系统int类型的变量所占用的字节大小。建议大家使用强制类型转换来使用指向void类型的指针。

  但是,如果分配内存失败了,会怎么样?malloc()函数会返回一个指向NULL的指针,如果程序在内存分配失败后继续使用内存,就会导致程序异常终止。所以,在分配内存之后,需要检查一下成功分配了没有。

int *ptr, n = 10;
ptr = (int *)malloc( (size_t)n * sizeof(int) );
if(ptr == NULL)
    exit(EXIT_FAILURE);

或者:

int *ptr, n = 10;
if( !( ptr = (int *)malloc( (size_t)n * sizeof(int)) ) )
    exit(EXIT_FAILURE);

都是很好的方法。

  我们知道,malloc()分配的内存具有动态分配存储期,由程序员控制变量的生命期。如果程序结束前没有及时地释放它,程序结束之后就仍然存在。更糟糕的是,如果程序运行中,指向该块内存首地址的指针全部被销毁,那么这块内存就永远无法正确访问,也就无法释放这块内存,这就会导致内存泄露。内存泄露问题很严重,会导致系统停止运行!因为没有空余的内存空间,所以,在分配内存之后如果不需要再次使用,一定要释放!警惕不小心更改指向动态分配内存空间的指针!释放动态分配的内存,我们用free()函数。

free(ptr);

  这行代码就释放了之前用malloc()分配的内存空间。

  如果ptr所指向的内存块不是通过malloc(),calloc()或realloc()分配的,或者指向一个已经删除的空间,该结果未定义!

动态数组排序实例

  动态数组和变长数组(C99)类似,但不表示数组的长度在程序运行中是可变的,只是说可以用变量来表示数组长度。

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <conio.h>
 4 
 5 int main(int argc, char * argv[]){
 6     int * ptr;
 7     int key, size, i, j;
 8 
 9     printf("请输入动态数组大小:");
10     while( !( scanf("%d",&size) ) || size < 0 ){
11         printf("输入错误,请重新输入。\n:");
12         while( getchar() != '\n' );//调整输入缓冲区
13     }
14 
15     if( !( ptr = (int *)malloc( (size_t)size * sizeof(int)) ) ){//分配内存
16         printf("内存请求失败!");
17         exit(EXIT_FAILURE);
18     }
19 
20     printf("请依次输入数组元素内容:\n");
21     for(i = 0; i < size; i++)
22         scanf("%d",&ptr[i]);
23     while( getchar() != '\n' );
24 
25     for(i = 1; i < size; i++){//插入排序
26         j = i - 1;
27         key = ptr[i];
28         while( j >= 0 && ptr[j] > key){
29             ptr[j + 1] = ptr[j];
30             j--;
31         }
32         ptr[j + 1] = key;
33     }
34 
35     printf("各元素排序后输出如下:");
36     for(i = 0; i < size; i++)
37         printf("%d  ",ptr[i]);
38 
39     free(ptr); //释放内存,重中之重
40 
41     _getch();
42     return 0;
43 }
动态数组排序实例

运行结果:

动态内存分配calloc()函数

  分配内存,除了使用malloc()函数外,还可以使用calloc()函数。calloc()函数和malloc()函数最大的区别就是:calloc()函数在分配内存之后,会把块中的所有位置0(某些硬件系统中,不是把所有的位都置0来表示浮点值0)。

  calloc()函数接受两个size_t类型的参数。第一个参数表明存储单元的数量,第二个参数表示每个存储单元的大小(以字节为单位)。

  现在如果我们要申请一个元素个数为n个的int类型动态数组ar,我们可以这样做:

int * ar;
int n = 10;
ar = (int *)calloc((size_t)n, sizeof(int) ); 
if( ar == NULL )
    exit(EXIT_FAILURE);

  如果分配内存成功,此时,ar数组里的每一个元素都为0。

  同样,我们可以使用free()来释放calloc()分配的内存:

free(ar);

补充:realloc()函数

  原型:void *realloc(void * ptr, size_t size);

  简单描述:把ptr指向的内存空间更改为size字节,size字节内的内存块内容不变。该函数返回块的位置。如果不能重新分配空间,则返回NULL。如果ptr为NULL,其行为与调用带size参数的malloc()相同。如果size为0且ptr不是NULL,其行为与调用带ptr参数的free相同。

  不推荐大量使用这个函数,因为当不能重新分配空间时,函数返回NULL,原来已分配的内存块地址丢失,会造成内存泄露。尽管在很多时候这个函数非常方便。如果你需要使用这个函数,请先保存原来内存块的地址!

 

posted @ 2016-08-10 15:49  Mr_Blug  阅读(985)  评论(0编辑  收藏  举报