C++动态内存分配

来源:互联网 发布:知乎作死经历 编辑:程序博客网 时间:2024/05/29 19:41

  动态内存分配

  在C中动态分配内存的基本步骤有:

  1.用malloc类的函数分配内存;

  2.用这些内存支持应用程序;

  3.用free函数释放内存。

  这个方法在具体操作上可能存在一些小变化,不过这里列出的是最常见的。在下例中,我们用malloc函数为整数分配内存。指针将分配的内存赋值为5,然后内存被free函数释放。

int *pi = (int*) malloc(sizeof(int));*pi = 5;printf("*pi: %d\n", *pi);free(pi);

  当这段代码执行时会打印数字5。图2-1说明了在free函数执行之前内存如何分配。为方便说明问题,除非特别指出,我们假定示例代码出现在main函数中。

  整数的内存分配

  图2-1:整数的内存分配

  malloc函数的参数指定要分配的字节数。如果成功,它会返回从堆上分配的内存的指针。如果失败则会返回空指针。后面再讨论测试所分配内存的指针是否有效。sizeof操作符使应用程序更容易移植,还能确定在宿主系统中应该分配的正确的字节数。

  在本例中,我们试图为整数分配足够多的内存。假定长度是4,我们可以这么写:

int *pi = (int*) malloc(4));

  然而,依赖于系统所用的内存模型,整数的长度可能会发生变化。可移植的方法是使用sizeof操作符,这样不管程序在哪里运行都会返回正确的长度。

  注意 涉及解引操作的常见错误见下面的代码:

int *pi;   *pi = (int*) malloc(sizeof(int));

  问题出在赋值符号的左边。我们在解引指针,这样会把malloc函数返回的地址赋给pi中存放的地址所在的内存单元。如果这是第一次对指针进行赋值操作,那指针所包含的地址可能无效。正确的方法如下所示:

pi = (int*) malloc(sizeof(int));

  这种情况下不应该用解引操作符。

  稍后也会深入讨论free函数,它和malloc协同工作,不再需要内存时将其释放。

  注意 每次调用malloc(或类似函数),程序结束时必须有对应的free函数调用,以防止内存泄漏。

  一旦内存被释放,就不应该再访问它了。通常我们不会在释放内存后有意去访问,不过也存在意外访问的情况。

  这时系统的行为将依赖于实现。通常的做法总是把被释放的指针赋值为NULL。

  分配内存时,堆管理器维护的数据结构中会保存额外的信息。这些信息包括块大小和其他一些东西,通常放在紧挨着分配块的位置。如果应用程序的写入操作超出了这块内存,数据结构可能会被破坏。这可能会造成程序奇怪的行为或者堆损坏。

  考虑如下代码段,我们为字符串分配内存,让它可以存放最多5个字符外加结尾的NUL字符。for循环在每个位置写入0,但是没有在写入6字节后停止。for语句的结束条件是写入8字节。写入的0是二进制0而不是ASCII字符0的值。

char *pc = (char*) malloc(6);for(int i=0; i<8; i++) {    *pc[i] = 0;}

  在图2-2中,6字节的字符串后面还分配了额外的内存,这是堆管理器用来记录内存分配的。如果我们越过字符串的结尾边界写入,额外的内存中的数据会损坏。在本例中,额外的内存跟在字符串后面。不过,实际的位置和原始信息取决于编译器。

  堆管理器用到的额外内存

  图2-2:堆管理器用到的额外内存

  内存泄漏

  如果不再使用已分配的内存却没有将其释放就会发生内存泄漏,导致内存泄漏的情况可能如下:

  丢失内存地址;

  应该调用free函数却没有调用(有时候也称为隐式泄漏)。

  内存泄漏的一个问题是无法回收内存并重复利用,堆管理器可用的内存将变少。如果内存不断地被分配并丢失,那么当需要更多内存而malloc又不能分配时程序可能会终止,因为它用光了内存。在极端情况下,操作系统可能崩溃。

  下面这个简单的例子可以说明这个问题:

char *chunk;while (1) {    chunk = (char*) malloc(1000000);    printf("Allocating\n");}

  chunk变量指向堆上的内存。然而,在它指向另一块内存之前没有释放这块内存。最终,程序会用光内存然后非正常终止,即使没有终止,至少内存的利用效率也不高。

  1. 丢失地址

  下面的代码段说明了当pi被赋值为一个新地址时丢失内存地址的例子。当pi又指向第二次分配的内存时,第一次分配的内存地址就会丢失。

int *pi = (int*) malloc(sizeof(int));*pi = 5;...pi = (int*) malloc(sizeof(int));

  图2-3说明了这一点,“前”和“后”分别表示在执行第二次malloc之前和之后的程序状态。由于没有释放地址500处的内存,程序已经没有地方持有这个地址。

  丢失地址

  图2-3:丢失地址

  下面这个例子是为字符串分配内存,将其初始化,并逐个打印字符串:

char *name = (char*)malloc(strlen("Susan")+1);strcpy(name,"Susan");while(*name != 0) {    printf("%c",*name);    name++;}

  然而每次迭代name都会增加1,最后name会指向字符串结尾的NUL字符,如图2-4所示,分配内存的起始地址丢失了。

  丢失动态分配的内存的地址

  图2-4:丢失动态分配的内存的地址

  2. 隐式内存泄漏

  如果程序应该释放内存而实际却没有释放,也会发生内存泄漏。如果我们不再需要某个对象但它仍然保存在堆上,就会发生隐式内存泄漏,一般这是程序员忽视所致。这类泄漏的主要问题是对象在使用的内存其实已经不需要了,应该归还给堆。最差的情况是,堆管理器可能无法按需分配内存,导致程序不得不终止。最好的情况是我们持有了不必要的内存。

  在释放用struct关键字创建的结构体时也可能发生内存泄漏。如果结构体包含指向动态分配的内存的指针,那么可能需要在释放结构体之前先释放这些指针。

原创粉丝点击