- 程序设计缺陷分析与实践
- 尹浩 于秀山编著
- 3561字
- 2018-12-27 12:36:11
2.2 内存管理
无论是C或C++,内存分配都只有三种方式:
(1)从静态存储区域分配。内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量、static变量。
(2)在栈上创建。执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3)从堆上分配,也称动态内存分配。程序在运行的时候用malloc()或new()申请任意大小的内存,由程序员决定何时用free()或delete()释放。因此,动态内存的生存期由程序员决定,使用非常灵活,但也是编程出现问题最多的地方。
C语言中与内存申请相关的函数主要有:alloca()、calloc()、malloc()、free()、realloc()、sbrk()等。
其中,alloca()是向栈申请内存,因此无需释放;malloc()分配的内存是位于堆中的,并且没有初始化内存的内容,因此基本上使用malloc()分配之后,都会调用函数memset()来初始化这部分的内存空间;calloc()则初始化这部分内存,并将其设置为0;realloc()则对malloc()申请的内存大小进行调整,申请的内存最终需要通过函数free()来释放;而sbrk()则是增加数据段的大小。
malloc()/calloc()/free()基本上都是C函数库实现的,与操作系统(以下简称OS)无关。C函数库内部通过一定的结构来保存当前有多少可用内存。如果程序使用malloc()分配的内存大小超出库里所留存的空间,那么将首先调用sbrk()增加可用空间,然后再分配空间。使用free()释放的内存并不立即返回给OS,而是保留在内部结构中。可以打个比方:sbrk()类似于批发,一次性向OS申请大块的内存,而malloc()等函数则类似于零售,只要满足程序运行时的要求即可,这套机制类似于缓冲。
之所以使用这套机制,是因为系统调用不能支持任意大小的内存分配(有的系统调用只支持固定大小以及其倍数的内存申请),这样的话,对于小内存的分配会造成浪费。系统调用申请内存代价昂贵,涉及到用户态和核心态的转换。
函数malloc()和calloc()都可以用来分配动态内存空间,但两者稍有区别。
malloc()函数有一个参数,即要分配的内存空间的大小,函数原型:
void *malloc(size_t size);
calloc()函数有两个参数,分别为元素的数目和每个元素的大小,这两个参数的乘积就是要分配的内存空间的大小,函数原型:
void *calloc(size_t numElements,size_t sizeOfElement);
如果调用成功,函数malloc()和calloc()都将返回所分配内存空间的首地址。
malloc() 函数和calloc()函数的主要区别是前者不能初始化所分配的内存空间。如果由malloc()函数分配的内存空间原来没有被使用过,则其中的每一位可能都是0;反之,如果这部分内存空间曾经被分配、释放和重新分配,则其中可能遗留各种各样的数据,这也就是使用malloc()函数的程序开始时(内存空间还没有被重新分配)能正常运行,但经过一段时间后(内存空间已被重新分配)可能会出现问题的原因所在。
calloc() 函数会将所分配内存空间中的每一位都初始化为0,也就是说,如果为字符类型或整数类型的元素分配内存,那么这些元素将保证会被初始化为0;如果为指针类型的元素分配内存,那么这些元素通常(但无法保证)会被初始化为空指针;如果为实数类型的元素分配内存,那么这些元素可能(只在某些计算机中)会被初始化为浮点型的0。
malloc() 函数和calloc()函数的另一点区别是:calloc()函数会返回一个由某种对象组成的数组,但malloc()函数只返回一个对象。为了明确是为一个数组分配内存空间,有些程序员会选用calloc()函数。但是,除了是否初始化所分配的内存空间这一点之外,绝大多数程序员认为以下两种函数的调用方式没有区别:
calloc(numElements,sizeOfElement);
malloc(numElements *sizeOfElement);
需要解释的一点是,理论上(按照ANSI C标准),指针的算术运算只能在一个指定的数组中进行,但是在实践中,即使C编译程序或翻译器遵循这种规定,许多C程序还是冲破了这种限制。因此,尽管malloc()函数并不能返回一个数组,它所分配的内存空间仍然能供一个数组使用(对realloc()函数来说同样如此,尽管它也不能返回一个数组)。
总之,当在calloc()函数和malloc()函数之间作选择时,只需考虑是否要初始化所分配的内存空间,而不用考虑函数是否能返回一个数组。
程序运行过程中调用malloc()分配内存,但是没有调用free()释放已分配的内存,会造成内存泄漏。即使申请的部分内存没有被使用,但是由于没有调用free()进行内存释放,因此系统认为这部分内存仍在使用,造成不断的向系统申请内存,导致系统可用内存不断减少。内存泄漏仅仅发生在程序运行阶段,当程序退出时,OS将回收所有的资源,这也就是为何有时重启一下程序后软件会重新正常运行的一个原因。
55.条件判断语句导致指针重复释放
例64:
1 #include <stdlib.h>
2 int main()
3 {
4 int i;
5 struct student
6 {
7 int num;
8 char name[20];
9 char sex;
10 int age;
11 };
12 struct student *p;
13 p=(struct student*)calloc(1,sizeof(struct student));
14 if(p==null)
15 return 0;
16 scanf("%d",&i);
17 if(i!=1000)
18 {
19 p->num=1000;
20 free(p);
21 }
22 else
23 {
24 p->num=i;
25 }
26 free(p);
27 return(1);
28 }
例64中,第17行语句当if语句判断条件为真时,指针p将被释放,被释放的指针p在后面将被再次释放,导致安全漏洞。
56.指针被重复释放
例65:
1 struct st
2 {
3 int number;
4 char *s;
5 char scores;
6 }
7 void release(st *t)
8 {
9 free(t);
10 }
11 int main()
12 {
13 st *p=(st*)malloc(sizeof(st));
14 if(p==null) return 0;
15 release(p);
16 free(p);
17 return 1;
18 }
例65中,第15行语句通过调用release()函数释放指针p,被释放的指针p在后面被free()函数再次释放,导致安全漏洞。
57.函数使用已释放的指针作为参数
例66:
1 #include<stlib.h>
2 struct st
3 {
4 int number;
5 char *s;
6 char scores;
7 }
8 f(st *t)
9 {
10 int j;
11 scanf("%d",&j);
12 (t->scores)= (t->scores)+j;
13 }
14 int main()
15 {
16 int i;
17 st *p;
18 st *p=(st*)malloc(sizeof(st));
19 if(p==null) return 0;
20 free(p);
21 f(p); //释放了内存后的指针p成为“野指针”,再继续使
用将非常危险。
22 }
例66中,第20行语句对指针p进行了释放,释放后的指针p仍被作为参数传递给函数f()。
58.条件判断语句导致函数返回被释放的指针
例67:
1 f(char *p)
2 {
3 return p;
4 }
5 char *main()
6 {
7 char *x=malloc(1);
8 char *y;
9 y=x;
10 if (y!=null)
11 free(y); //释放了内存后的指针y成为“野指针”。
12 f(y);
13 }
例67中第10行语句,当if语句为真时,指针y被释放,在这种情况下,程序中调用函数f(),函数f()将返回已经被释放的指针y,导致程序错误。
59.函数返回已释放的指针
例68:
1 f(char *p)
2 {return p;}
3 main()
4 {
5 char * x = malloc(1);
6 free(x);
7 f(x);
8 }
例68中,第6行语句释放了指针x,随后调用函数f(),函数f()将返回已经被释放的指针,导致程序错误。
60.条件判断语句导致使用已释放的内存
例69:
1 #include<stlib.h>
2 main()
3 {
4 int *x=malloc(4);
5 scanf("%d",&j);
6 if(j>0)
7 {
8 free(x);
9 }
10 x=&j;
11 }
例69中,当条件判断语句成立时,指针x将被释放(其指向的内存被释放),第10行语句给x赋值将产生使用释放内存的错误。
61.使用已释放的内存
例70:
1 char *GetMemory(void)
2 {
3 char p[] = "hello world";
4 return p;
5 }
6 void Test(void)
7 {
8 char *str= NULL;
9 str = GetMemory();
10 printf(str);
11 }
例70中,GetMemory()函数返回的是指向“栈内存”的指针,str变量通过GetMemory()函数将得不到字符串“hello world”。
62.内存分配和释放函数不匹配
指针内存分配和释放函数正确匹配是malloc/free,new/delete,new[]/delete[]。
例71:
1 #include<stlib.h>
2 main()
3 {
4 int j;
5 int *p;
6 scanf("%d",&j);
7 p=(int*)malloc(sizeof(int));
8 if(j>0)
9 {
10 delete(p);
11 p=null;
12 }
13 if(p!=null)
14 free(p);
15 }
例71中,第7行语句使用malloc()函数为指针p申请内存空间,第8行语句中当if条件成立时,第10行语句中使用与malloc()函数不匹配的delete()函数释放指针p的内存空间。
例72:
1 main()
2 {
3 int *p;
4 p=(int*)malloc(sizeof(int));
5 delete p;
6 }
例72中,第4行语句使用malloc()函数为指针p申请内存空间,在第5行语句中使用与malloc()函数不匹配的delete()函数释放指针p的空间。
63.内存申请和释放函数不匹配
类中需要成对使用operator new和operator delete,operator new[]和operator delete[]以及new和delete。
例73:
1 #include <stdio.h>
2 class A
3 {
4 public:
5 A() {}
6 void* operator new(size_t size);
7 };
例73中,类A使用operator new申请内存,在该类中并没有使用operator delete释放申请的内存。
修改方法:在第6行语句后添加语句“void operator delete( void* );”释放operator new申请的内存。
64.使用delete[]删除单个对象的内存空间
C++中,通过delete函数回收new函数为单个对象分配的内存空间,用delete[]函数回收new[]分配的数组空间。
例74:
1 class A {
2 public:
3 A( ) {}
4 };
5
6 void foo( ) {
7 A *a = new A;
8 delete[] a;
9 }
例74中,a是使用new为单个对象分配的内存空间,在第8行语句中应该使用语句“delete a;”释放该内存空间。
65.使用delete删除已分配的数组空间
C++中一定要配对使用不同形式的new和delete。
例75:
1 class A
2 {
3 public:
4 A() {}
5 };
6 void foo() {
7 A *a = new A[100];
8 delete a;
9 }
例75中,a由new[]申请生成具有100个对象的数组空间,删除时应该使用和new[]配对的delete[]删除。
修改方法:将第8行语句改为:“delete[] a;”。
66.条件判断语句导致非堆内存被释放
例76:
1 #include<stlib.h>
2 main()
3 {
4 const int i=1;
5 int j;
6 int *p;
7 p=&i; //p是指向常量静态内存的指针;
8 scanf("%d",&j);
9 if(j>0)
10 {
11 free(p);
12 }
13 }
例76中,第9行语句,当if条件成立时,程序试图通过指针p释放常量内存。
67.静态内存被释放
例77:
1 main()
2 {
3 const int i=1;
4 int *p;
5 p=&i;
6 free(p);
7 }
例77中,第6行语句,程序通过指针释放常量内存。
68.条件判断语句导致释放未分配的内存
例78:
1 #include<stlib.h>
2 main()
3 {
4 int j;
5 scanf("%d",&j);
6 void *p=malloc(j); //p指向j个字节大小的存储区。
7 if(j>5)
8 {
9 p+=10; //p指针指向,向后移10个字节;
10 }
11 free(p);
12 }
例78中,当if条件判断语句成立时,第11行语句将释放未分配的内存。
69.释放未分配的内存
例79:
1 main()
2 {
3 void *p=malloc(10);
4 p+=10;
5 free(p);
6 }
例79中,第5行语句试图释放未分配的内存。
70.条件判断语句导致空指针被释放
例80:
1 #include<stlib.h>
2 int main()
3 {
4 int t;
5 scanf("%d",&t);
6 char *x;
7 x = NULL;
8 if(t>0)
9 free(x);
10 return 0;
11 }
例80中,当第8行语句中的if条件成立时,程序将在第9行语句释放空指针x。
71.释放空指针
例81:
1 int main()
2 {
3 char *x;
4 x = NULL;
5 free(x);
6 return 0;
7 }
例81中,第5行语句释放空指针x。