C++的内存管理

这篇文章是我在学习高质量C++/C编程指南中的第7章"内存管理"后的一篇笔记,之前我也写过相关的文章指针以及内存分配,但我感觉那篇还不是很好,这篇我很把它更完善一些

一.内存的常见分配方式

  1. 从静态区分配,一般是全局变量和static类型变量

  2.从栈区分配内存,一般是局部的变量,会随着所在函数的结束而自动释放

  3.从堆中分配,一般是使用手动分配,使用malloc()函数和new来申请任意大小空间,不过要手动释放空间,相应的使用free()函数和delete释放,

    如果不释放该空间,而且指向该空间的指针指向了别的空间.则该空间就无法释放,造成内存泄露,造成了内存浪费

二.内存的使用规则

  1.在使用malloc()或new申请空间时,要检查有没有分配空间成功,判断方法是判断指针是否为NULL,如申请一块很大的内存而没有这么大的内存则分配内存会失败

  2.申请成功后最好是将该内存清空,使用memset()后ZeroMemory()清空,不然存在垃圾而造成有时候输出很大乱码

  3.不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。(这句话不太理解)

  4.要防止数组或指针内存越界,

  5.申请内存成功后,使用结束后要释放,系统不会自动释放手动分配的内存

  6.内存释放后,指针还是指向那块地址,不过这指针已经是"野指针"了,所以释放内存后指针要指向NULL,不然很危险,容易出错,if()对野指针的判断不起作用

三.指针和数组

  1. 数组里的数据可以单个修改,但指针的不行,如我的例子,char str[] = "hello",数组的大小有6个字符(注意\0),可以通过str[0] = 'X'修改了的个字符,而指针

char *p = "Word",p是指向了一串常量的字符串,常量字符串是不可修改的,如 p[0] = 'X',编译器编译时不会保存,但执行时会出错

 

 

   2.内容的复制与比较

   内容的复制要使用strcpy()函数,不要使用赋值符"=",内容的比较也是不要使用比较符号"<,>,==",使用strcmp()函数

 

  1. // 数组…   
  2.   
  3.     char a[] = "hello";  
  4.   
  5.     char b[10];  
  6.   
  7.     strcpy(b, a);           // 不能用   b = a;   
  8.   
  9.     if(strcmp(b, a) == 0)   // 不能用  if (b == a)  

// 数组…
char a[] = "hello";
char b[10];
strcpy(b, a); // 不能用 b = a;
if(strcmp(b, a) == 0) // 不能用 if (b == a)

 

  1. // 指针…   
  2.   
  3.    int len = strlen(a);  
  4.   
  5.    char *p = (char *)malloc(sizeof(char)*(len+1));  
  6.   
  7.    strcpy(p,a);            // 不要用 p = a;   
  8.   
  9.    if(strcmp(p, a) == 0)   // 不要用 if (p == a)  

// 指针…
int len = strlen(a);
char *p = (char *)malloc(sizeof(char)*(len+1));
strcpy(p,a); // 不要用 p = a;
if(strcmp(p, a) == 0) // 不要用 if (p == a)

    3,计算空间的大小

 对数组的计算是使用sizeof()函数,该函数会按照内存对齐的方式4的倍数计算,而指针的空间大小没法计算,只能记住在申请空间时的空间大小

注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针,不论数组a的容量是多少,sizeof(a)始终等于sizeof(char *)

 

 

 

  1. void Func(char a[100])  
  2.   
  3.     {  
  4.   
  5.         cout<< sizeof(a) << endl;   // 4字节而不是100字节   
  6.   
  7. }  

void Func(char a[100])
{
cout<< sizeof(a) << endl; // 4字节而不是100字节
}

 

四.指针的内存的传递

如果函数的参数是指针,则不要使用该参数来申请内存空间,这样没有实际的用处,而且这样当函数结束时还得不到释放内存而造成内存泄露

 

     这个问题可以使用"指针的指针"的方法可以解决,不然使用返回指针地址的办法,先看一下使用 "指针的指针"方法,

还可以考虑一下引用

 

 

使用返回内存地址的方法

 

 使用返回的方式传递内存地址容易出错的地方在于放回"栈内存"的指针,当GetMemory()函数结束时栈内存也被释放,

 

 

 像这个代码

 

  1. char *GetString2(void)  
  2.   
  3. {  
  4.   
  5.     char *p = "hello world";  
  6.   
  7.     return p;  
  8.   
  9. }  
  10.    
  11. void Test5(void)  
  12.   
  13. {  
  14.   
  15.     char *str = NULL;  
  16.   
  17.     str = GetString2();  
  18.   
  19.     cout<< str << endl;  
  20.   
  21. }  
  22.    

char *GetString2(void)
{
char *p = "hello world";
return p;
}
void Test5(void)
{
char *str = NULL;
str = GetString2();
cout<< str << endl;
}

函数Test5运行虽然不会出错,但是函数GetString2的设计概念却是错误的。因为GetString2内的“hello world”是常量字符串,位于静态存储区,

它在程序生命期内恒定不变。无论什么时候调用GetString2,它返回的始终是同一个“只读”的内存块。

 

五.动态内存释放问题与野指针

   1. 当我们使用free()和delete释放一块内存时,指针还是指向原来的地址,不过这时候的指针时野指针,

可以验证一下.这图是我调试到if()语句时的情况,p还没有指向NULL,只是释放了p指向的空间了

 

 

 

执行的结果可以看看...

 

 

所以有这样的一些特征:

1.指针销毁了,并不表示所指的空间也得到了释放 :内存泄露

2.内存被释放了,并不表示指针也被销毁了或指向NULL :野指针

 

六.malloc()/free()与new/delete的区别(摘抄原文)

malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。对于非内部数据类型的对象而言,

光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函

数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。

因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。

我们先看一看malloc/free和new/delete如何实现对象的动态内存管理,看代码

 

  1. class Obj  
  2.   
  3. {  
  4.   
  5. public :  
  6.   
  7.         Obj(void){ cout << “Initialization” << endl; }  
  8.   
  9.         ~Obj(void){ cout << “Destroy” << endl; }  
  10.   
  11.         void    Initialize(void){ cout << “Initialization” << endl; }  
  12.   
  13.         void    Destroy(void){ cout << “Destroy” << endl; }  
  14.   
  15. };  
  16.    
  17. void UseMallocFree(void)  
  18.   
  19. {  
  20.   
  21.     Obj  *a = (obj *)malloc(sizeof(obj));   // 申请动态内存   
  22.   
  23.     a->Initialize();                        // 初始化   
  24.   
  25.     //…   
  26.   
  27.     a->Destroy();   // 清除工作   
  28.   
  29.     free(a);        // 释放内存   
  30.   
  31. }  
  32.    
  33. void UseNewDelete(void)  
  34.   
  35. {  
  36.   
  37.     Obj  *a = new Obj;  // 申请动态内存并且初始化   
  38.   
  39.     //…   
  40.   
  41.     delete a;           // 清除并且释放内存   
  42.   
  43. }  
  44.    

class Obj
{
public :
Obj(void){ cout << “Initialization” << endl; }
~Obj(void){ cout << “Destroy” << endl; }
void Initialize(void){ cout << “Initialization” << endl; }
void Destroy(void){ cout << “Destroy” << endl; }
};
void UseMallocFree(void)
{
Obj *a = (obj *)malloc(sizeof(obj)); // 申请动态内存
a->Initialize(); // 初始化
//…
a->Destroy(); // 清除工作
free(a); // 释放内存
}
void UseNewDelete(void)
{
Obj *a = new Obj; // 申请动态内存并且初始化
//…
delete a; // 清除并且释放内存
}

       类Obj的函数Initialize模拟了构造函数的功能,函数Destroy模拟了析构函数的功能。函数UseMallocFree中,由于malloc/free不能执行构造函数与析构函数,必须调用成员函数Initialize和Destroy来完成初始化与清除工作。函数UseNewDelete则简单得多。

       所以我们不要企图用malloc/free来完成动态对象的内存管理,应该用new/delete。由于内部数据类型的“对象”没有构造与析构的过程,对它们而言malloc/free和new/delete是等价的。

    既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。

如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,理论上讲程序不会出错,但是该程序的可读性很差。所以new/delete必须配对使用,malloc/free也一样。

 

 七.如何处理内存耗尽

   1.判断指针是否为NULL,如果是则马上用return语句终止本函数

   2.判断指针是否为NULL,如果是则马上用exit(1)终止整个程序的运行

   3.为new和malloc设置异常处理函数。例如Visual C++可以用_set_new_hander函数为new设置用户自己定义的异常处理函数,

      也可以让malloc享用与new相同的异常处理函数

 

 

malloc()/free()和new/delete的使用要点网上有更详细的说明

时间: 2024-12-04 00:26:20

C++的内存管理的相关文章

聊聊内存管理

这篇文章我们聊聊内存管理. 本来我想不针对于任何具体的操作系统来谈内存管理,但是又觉得不接地气.言之无物.所以我决定在阐述概念的同时,还针对IA32平台Linux下的内存管理做简要的介绍,并且以实验来证明结论.以下内容分拆为几个大标题和小节,内容前后承接. 物理地址空间 首先,什么是物理地址空间?我们知道CPU与外部进行信息传递的公用通道就是总线,一般而言,CPU有三大总线:控制总线.数据总线.地址总线.这三类总线在一定程度上决定了CPU对外部设备的控制和数据传送能力.其中地址总线决定了CPU能

c#内存管理.

尽管在.net framework中我们不太需要关注内存管理和垃圾回收这方面的问题,但是出于提高我们应用程序性能的目的,在我们的脑子里还是需要有这方面的意识.明白内存管理的基本行为将有助于我们解释我们程序中变量是如何操作的.在本文中我将讨论栈和堆的一些基本知识,变量的类型和某些变量的工作原理. 当你在执行程序的时候内存中有两个地方用于存储程序变量.如果你还不知道,那么就来看看堆和栈的概念.堆和栈都是用于帮助我们程序运行的,包含某些特殊信息的操作系统内存模块.那么堆和栈有什么不同呢? 堆VS栈的区

iOS开发系列—Objective-C之内存管理

概述 我们知道在程序运行过程中要创建大量的对象,和其他高级语言类似,在ObjC中对象时存储在堆中的,系统并不会自动释放堆中的内存(注意基本类型是由系统自己管理的,放在栈上).如果一个对象创建并使用后没有得到及时释放那么就会占用大量内存.其他高级语言如C#.Java都是通过垃圾回收来(GC)解决这个问题的,但在OjbC中并没有类似的垃圾回收机制,因此它的内存管理就需要由开发人员手动维护.今天将着重介绍ObjC内存管理: 引用计数器 属性参数 自动释放池 引用计数器 在Xcode4.2及之后的版本中

内存管理 之 存储器硬件知识

接下来,为了顺应Linux Kernel的学习,在操作系统方面首先学习的是内存管理.首先主要讲解物理内存的相关知识.本节主要讲解存储器的基础硬件知识,下一节讲解存储器的层次结构.   存储器是计算机系统的重要组成部分,它在计算机系统中的作用是存放程序和数据.存储器不仅使计算机具有记忆功能,而且是计算机高速自动运行的基础. 作为计算机的核心部件之一,存储器直接关系到整个计算机系统性能的高低.如何以合理的成本搭建出容量和速度都满足要求的存储器系统,始终是计算机体系结构设计中的关键问题之一:一方面,人

iOS ARC 内存管理要点

前言 在讨论 ARC 之前,我们需要知道 Objective-C 采用的是引用计数式的内存管理方式,这一方式的特点是: 自己生成的对象自己持有.比如:NSObject * __strong object = [NSObject alloc] init];. 非自己生成的对象自己也能持有.比如:NSMutableArray * __strong array = [NSMutableArray array];. 自己持有的对象不再需要时释放. 非自己持有的对象自己无法释放. 而 ARC 则是帮助我们

.NET中的内存管理,GC机制,内存释放过程

引言 作为一个.NET程序员,我们知道托管代码的内存管理是自动的..NET可以保证我们的托管程序在结束时全部释放,这为我们编程人员省去了不少麻烦,我们可以连想都不想怎么去管理内存,反正.NET自己会保证一切.好吧,有道理,有一定的道理.问题是,当我们用到非托管资源时.NET就不能自动管理了.这是因为非托管代码不受CLR(Common Language Runtime)控制,超出CLR的管理范围.那么如何处理这些非托管资源呢,.NET又是如何管理并释放托管资源的呢? 自动内存管理和GC 在原始程序

C# 语言规范--1.4 自动内存管理

规范 手动内存管理要求开发人员管理内存块的分配和回收.手动内存管理可能既耗时又麻烦.在 C# 中提供了自动内存管理,使开发人员从这个繁重的任务中解脱出来.在绝大多数情况下,自动内存管理可以提高代码质量和开发人员的工作效率,并且不会对表达能力或性能造成负面影响. 示例 using System; public class Stack {    private Node first = null;    public bool Empty {       get {          return

[share]深入探讨PHP中的内存管理问题

一. 内存 在PHP中,填充一个字符串变量相当简单,这只需要一个语句"<?php $str = "hello world "; ?>"即可,并且该字符串能够被自由地修改.拷贝和移动.而在C语言中,尽管你能够编写例如"char *str = "hello world ";"这样的一个简单的静态字符串:但是,却不能修改该字符串,因为它生存于程序空间内.为了创建一个可操纵的字符串,你必须分配一个内存块,并且通过一个函数(

PHP原理之内存管理中难懂的几个点

PHP的内存管理, 分为俩大部分, 第一部分是PHP自身的内存管理, 这部分主要的内容就是引用计数, 写时复制, 等等面向应用的层面的管理. 而第二部分就是今天我要介绍的, zend_alloc中描写的关于PHP自身的内存管理, 包括它是如何管理可用内存, 如何分配内存等. 另外, 为什么要写这个呢, 因为之前并没有任何资料来介绍PHP内存管理中使用的策略, 数据结构, 或者算法. 而在我们平时开发扩展, 修复PHP的bug的时候, 却对这一部分的知识需要有一个良好的理解. PHP开发组内的很多

深入SQL SERVER 2000的内存管理机制(二)

server 深入SQL SERVER 2000的内存管理机制(二)     http://msdn.microsoft.com/data/default.aspx?pull=/library/en-us/dnsqldev/html/sqldev_01262004.asp   可访问大地址的应用 (Large-Address-Aware Executables) 在Windows增加支持/3GB参数以前,一个应用程序是无法访问一个带有高位设置的指针.一个32位的指针只有前31位地址空间可以被用户