第七章 内存管理
文章目录
7.1 内存分配方式
- 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的 整个运行期间都存在。例如全局变量,static 变量。
- 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
- 从堆上分配,亦称动态内存分配。程序在运行的时候用
malloc
或new
申请任意 多少的内存,程序员自己负责在何时用free
或delete
释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
7.2 常见的内存错误
- 内存未分配成功,在使用内存之前检查指针是否为
NULL
,使用用assert(p!=NULL)
或者if(p==NULL)
进行检查以及预防。 - 内存分配虽然成功,但是尚未初始化就引用它。主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组) 。
- 内存分配成功并且已经初始化,但操作越过了内存的边界。,例如数组下标越界。
- 忘记了释放内存,造成内存泄露。
- 释放了内存却继续使用它。
- 注意不要返回指向“栈内存”的“指针”或者“引用”, 因为该内存在函数体结束时被自动销毁。
- 使用 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针对于指针
free
和delete
只是把指针所指的内存给 释放掉,但并没有把指针本身干掉。
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/free
与new/delete
operator new 和 operator delete
malloc
与free
是 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/free
和 new/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++中使用new
和delete
时,其实执行的是全局的::operator new
和::operator delete
,不过这两个函数并没有重载new
表达式或delete
表达式。
可以重载operator new
和operator 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的三步骤
- new表达式调用一个名为operator new(或operator new[])的标准库函数,该函数分配一块足够大的、原始的、未命名的内存空间以便存储特定类型的对象(或者对象的数组);
- 编译器运行相应的构造函数以构造这些对象,并为其传入初始值;
- 对象被分配了空间并构造完成,返回一个指向该对象的指针。
//new:分配空间--->调用构造函数初始化--->对象构造完成,返回一个指向该对象的指针。
// new表达式
std::string *sp = new std::string("a value"); // 分配并初始化一个std::string对象
std::string *arr = new std::string[10]; // 分配10个默认初始化的std::string对象
一条delete的两步骤
- 对所指对象或者对象数组执行对应的析构函数;
- 编译器调用名为
operator delete
或operator 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
,所以要有捕获异常处理程序。