内存管理_高质量C++/C编程指南

第七章 内存管理

参考:变量存放区域

7.1 内存分配方式

  1. 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的 整个运行期间都存在。例如全局变量,static 变量。
  2. 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
  3. 从堆上分配,亦称动态内存分配。程序在运行的时候用 mallocnew 申请任意 多少的内存,程序员自己负责在何时用 freedelete 释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

7.2 常见的内存错误

  1. 内存未分配成功,在使用内存之前检查指针是否为 NULL,使用用 assert(p!=NULL)或者if(p==NULL)进行检查以及预防。
  2. 内存分配虽然成功,但是尚未初始化就引用它。主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组) 。
  3. 内存分配成功并且已经初始化,但操作越过了内存的边界。,例如数组下标越界。
  4. 忘记了释放内存,造成内存泄露。
  5. 释放了内存却继续使用它。
  • 注意不要返回指向“栈内存”的“指针”或者“引用”, 因为该内存在函数体结束时被自动销毁。
  • 使用 free 或 delete 释放了内存后,没有将指针设置为 NULL,导致产生“野指针”。

7.3 指针与数组的对比

char a[] = "hello world"; 
char *p = a; 
cout<< sizeof(a) << endl; // 12 字节 
cout<< sizeof(p) << endl; // 4 字节

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

7.4 指针参数传内存

void GetMemory(char *p, int num) 
{ 
	p = (char *)malloc(sizeof(char) * num); 
    //编译器总是要为函数的每个参数制作临时副本,指针参数 p 的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p 的内容,就导致参数 p 的内容作相应的修改。
} 
void Test(void) 
{ 
	char *str = NULL; 
	GetMemory(str, 100); // str 仍然为 NULL 
	strcpy(str, "hello"); //error运行错误
    //_p 申请了新的内存,只是把_p 所指的内存地址改变了,但是 p 丝毫未变。所以函数 GetMemory并不能输出任何东西。事实上,每执行一次 GetMemory 就会泄露一块内存,因为没有用free 释放内存。 
}

更改后:
//1、用指针参数去申请内存,需要用指针的指针
void GetMemory2(char **p, int num) //二级指针
{ 
	*p = (char *)malloc(sizeof(char) * num); 
}
void Test2(void) 
{ 
    char *str = NULL; 
    GetMemory2(&str, 100); // 注意参数是 &str,而不是 str 
    strcpy(str, "hello"); 
    cout<< str << endl; 
    free(str); 
}

//2、用函数返回值来传递动态内存
//注意:不要用 return 语句返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡,数据无法保存
char *GetMemory3(int num) 
{ 
    char *p = (char *)malloc(sizeof(char) * num); //动态内存,堆区
    //char p[] = "hello world";	栈区
    return p; 
}
void Test3(void) 
{ 
    char *str = NULL; 
    str = GetMemory3(100); 
    strcpy(str, "hello"); 
    cout<< str << endl; 
    free(str); 
} 

7.5 free和delete针对于指针

freedelete只是把指针所指的内存给 释放掉,但并没有把指针本身干掉。

char *p = (char *) malloc(100); 
strcpy(p, “hello”); 
free(p); // p 所指的内存被释放,但是 p 所指的地址仍然不变 if(p != NULL) // 没有起到防错作用 
{ 
	strcpy(p, “world”); // 出错 
} 

p 被 free以后其地址仍然不变(非NULL),只是该地址对应的内存是垃圾,p 成了“野指针”。如果此时不把 p 设置为 NULL,会让人误以为 p 是个合法的指针。

野指针

野指针就是指针的指向方向的位置是不可知的(随机的、不正确的、没有明确限制的)。

“野指针”不是 NULL 指针,是指向“垃圾”内存的指针。

指针变量没有被初始化任何指针变量刚被创建时不会自动成为 NULL 指针,它 的缺省值是随机的。所以,指针变量在创建的同时应当被初始化,要么 将指针设置为 NULL,要么让它指向合法的内存。

//1、指针未初始化
#include <stdio.h>
int main()
{
	int * p;//局部变量未初始化,默认随机值
	*p = 20;
	return 0;
}

指针操作超越了变量的作用范围。

class A 
{ 
public: 
	void Func(void){ cout << “Func of class A<< endl; } 
};

void Test(void) 
{ 
    A *p; 
    { 
        A a; 
        p = &a; // 注意 a 的生命期 
    } 
    p->Func(); // p 是“野指针” 
} 
//函数 Test 在执行语句 p->Func()时,对象 a 已经消失,而 p 是指向 a 的,所以 p 就成了“野指针”。
//2、指针访问越界
#include <stdio.h>
int main()
{
	int arr[] = { 0 };
	int *p = arr;
	for (int i = 0; i <= 11; i++)
	{
		//当指针指向范围外,p就是野指针
		*(p + i) = i;
	}
	return 0;
}

7.8 malloc/freenew/delete

参考1

参考2

operator new 和 operator delete

mallocfree是 C++/C 语言的标准库函数,new/delete 是 C++的运算符。它们都可用于申请动态内存和释放内存。

C++中针对于非内部数据类型的对象,对象每次在创建时需要自动执行构造函数,对象在消亡之前要自动执行析构函数,光用 maloc/free 无法满足动态对象的要求。因此 C++语言需要一个能完成动态内存分配和初始化工作的运算符 new,以及一个 能完成清理与释放内存工作的运算符 delete。注意 new/delete 不是库函数

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; // 清除并且释放内存 
}

由于内部数据类型的“对象”没有构造与析构的过程,对它们而言 malloc/freenew/delete 是等价的

因为 C++程序经常要调用 C 函数,而 C 程序只能用 malloc/free 管理动态内存

C的动态内存管理

//C的动态内存管理
int main()
{
    int n = 10;
    int *ipa = (int*)malloc(sizeof(int)*n);
    int *ipb = (int*)calloc(n,sizeof(int));
    ipa = (int*)realloc(ipa,sizeof(int)*n*2);
    //
    free(ipa);
    ipa = NULL;
    free(ipb);
    ipb = NULL;
    return 0;
}
malloc和free的使用
//函数malloc的原型如下: 
void * malloc(size_t size); 
//用 malloc 申请一块长度为 length 的整数类型的内存,程序如下: 
int *p = (int *) malloc(sizeof(int) * length);

//函数 free 的原型如下:
void free( void * memblock );
  • malloc返回值的类型是void *,所以在调用 malloc 时要显式地进行类型转换,将 void * 转换成所需要的指针类型。
  • malloc函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。如 int 变量在 16 位系统 下是 2 个字节,在 32 位下是 4 个字节;而 float 变量在 16 位系统下是 4 个字节,在 32 位下也是 4 个字节。
  • 如果 p 是 NULL 指针, 那么 free 对 p 无论操作多少次都不会出问题。如果 p 不是 NULL 指针,那么 free 对 p 连续操作两次就会导致程序运行错误。

C++的动态内存管理

new和delete的使用
//C++的动态内存管理
//1、new运算符的使用
int main()
{
    int n = 10;
    int* ipa = new int(10); // 1 // 2;
    int* ipb = new int[n](10);
    int* ipc = new int[n] {1, 2, 3, 4, 5, 6, 7};
    delete ipa;
    delete[]ipb;
    delete[]ipc;
    return 0;
}

//2、new的函数方式使用
int main()
{
    int n = 10;
    int *ipa = (int*)::operator new(sizeof(int));
    // (int*)malloc(sizeof(int));
    int *ipb = (int*)::operator new(sizeof(int)*n);
    // (int*)malloc(sizeof(int)*n);
    ::operator delete(ipa);
    ::operator delete(ipb);
    return 0;
}

//3、定位new
int main()
{
    int n = 10;
    int* ipa = (int*)malloc(sizeof(int));
    int* ipb = (int*)::operator new(sizeof(int) * n);
    new(ipa) int(20);
    new(ipb) int[]{ 1,2,3,4,5,6,7,8,9 };
    free(ipa);
    ::operator delete(ipb);
    return 0;
}
new的种类
int *p1 = new int(20);

int *p2 = new (nothrow) int;//不抛出异常版本的new,返回值跟空判断。

const int *p3 = new const int(40);//开辟常量内存

//定位new  
int data = 0;
int *p4 = new (&data) int(50);
//在指定的内存上划分出4字节的内存赋值为50
cout << "data:" << data << endl;//data:50
operator new 和 operator delete

当我们在C++中使用newdelete时,其实执行的是全局的::operator new::operator delete,不过这两个函数并没有重载new表达式或delete表达式。

可以重载operator newoperator delete来控制内存的分配和释放,但不能重载new表达式和delete表达式。也就是说,可以自定义的是实际的内存分配和释放,但不能自定义构造函数和析构函数的调用
我们提供新的operator new函数和operator delete函数的目的在于改变内存分配的方式,但是不管怎样,都不能改变new运算符和delete运算符的基本含义。

void *operator new(size_t);     //allocate an object
void *operator delete(void *);    //free an object

void *operator new[](size_t);     //allocate an array
void *operator delete[](void *);    //free an array
一条new的三步骤
  1. new表达式调用一个名为operator new(或operator new[])的标准库函数,该函数分配一块足够大的、原始的、未命名的内存空间以便存储特定类型的对象(或者对象的数组);
  2. 编译器运行相应的构造函数以构造这些对象,并为其传入初始值;
  3. 对象被分配了空间并构造完成,返回一个指向该对象的指针。
//new:分配空间--->调用构造函数初始化--->对象构造完成,返回一个指向该对象的指针。
// new表达式
std::string *sp = new std::string("a value");  // 分配并初始化一个std::string对象
std::string *arr = new std::string[10];        // 分配10个默认初始化的std::string对象
一条delete的两步骤
  1. 对所指对象或者对象数组执行对应的析构函数;
  2. 编译器调用名为operator deleteoperator delete[]的标准库函数释放内存空间。
//delete:调用析构函数--->释放空间
// delete表达式
delete sp;          // 销毁*sp,然后释放sp指向的内存空间
delete [] arr;      // 销毁数组中的元素,然后释放对应的内存空间

总结

对于内置类型 new / delete / malloc/free 可以混用。

区别:

1、 new/delete 是C++中的运算符。 malloc / free 是函数。

2、 malloc申请内存空间时,手动计算所需大小,new只需类型名,自动计算大小;

3、 malloc申请的内存空间不会初始化,new可以初始化;

4、 malloc的返回值为void*, 接收时必须强转,new不需要;

5、 malloc申请内存空间失败时,返回的是NULL,使用时必须判空;

new申请内存空间失败时抛出异常bad_alloc,所以要有捕获异常处理程序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值