C++动态内存分配

  动态内存分配

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一. 实验目的: 1.通过编写和调试存储管理的模拟程序以加深对存储管理方案的理解。熟悉虚存管理的各种页面淘汰算法 2.通过编写和调试地址转换过程的模拟程序以加强对地址转换过程的了解。 二.实验要求 实验程序由以下三大部分组成: (1) 通过随机数产生一个指令序列(实际上是指令的逻辑地址序列),共320条指令。指令的地址按下述原则生成: A:50%的指令是顺序执行的 B:25%的指令要实现向前跳转,均匀分布在前地址部分 C:25%的指令要实现向后跳转,均匀分布在后地址部分 具体的实施方法是: A:在[0,319]的指令地址之间随机选取一起点m B:顺序执行一条指令,即执行地址为m+1的指令 C:在前地址[0,m+1]中随机选取一条指令并执行,该指令的地址为m’ D:顺序执行一条指令,其地址为m’+1 E:在后地址[m’+2,319]中随机选取一条指令并执行 F:重复步骤A-E,直到320次指令 (2) 将每条指令的逻辑地址变换为页地址 设:页面大小为1K; 用户内存容量4页到32页; 用户虚存容量为32K。 在用户虚存中,按每K存放10条指令排列虚存地址,即320条指令在虚存中的存放方式为: 第 0 条-第 9 条指令为第0页(对应逻辑地址为[0,9]) 第10条-第19条指令为第1页(对应逻辑地址为[10,19]) ……………………………… 第310条-第319条指令为第31页(对应逻辑地址为[310,319]) 按以上方式,用户指令可组成32页。 (3) 分别使用FIFO算法和LFU算法,计算给用户进程的这32页分配4,5,…,32个页面(内存块)时其缺页率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值