void GetMemory(char *p) { p = (char *)malloc(100); } void Test(void) { char *str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); } 请问运行Test函数会有什么样的结果? 答:程序崩溃。 因为GetMemory并不能传递动态内存, Test函数中的 str一直都是 NULL。 strcpy(str, "hello world");将使程序崩溃。 | char *GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char *str = NULL; str = GetMemory(); printf(str); } 请问运行Test函数会有什么样的结果? 答:可能是乱码。 因为GetMemory返回的是指向“栈内存”的指针,该指针的地址不是 NULL,但其原现的内容已经被清除,新内容不可知。 |
void GetMemory2(char **p, int num) { *p = (char *)malloc(num); } void Test(void) { char *str = NULL; GetMemory(&str, 100); strcpy(str, "hello"); printf(str); } 请问运行Test函数会有什么样的结果? 答: (1)能够输出hello (2)内存泄漏 | void Test(void) { char *str = (char *) malloc(100); strcpy(str, “hello”); free(str); if(str != NULL) { strcpy(str, “world”); printf(str); } } 请问运行Test函数会有什么样的结果? 答:篡改动态内存区的内容,后果难以预料,非常危险。 因为free(str);之后,str成为野指针, if(str != NULL)语句不起作用。 |
1. 关键字、操作符与库函数
- 关键字是编译器保留的文字,不能被用户拿来重新声明,像const, new, if等等
- 操作符必须要有操作对象,操作符本质上可以视为编译器内置的基础的函数。操作符在c++中,可以被重载(除了部分例外,比如 . :: sizeof)。
- 库函数是编写在编译器头文件库里,要包含头文件才能调用的封装函数。
2. 自由存储区与堆的区别
堆(heap)是C语言和操作系统的术语。堆是操作系统所维护的一块特殊内存,它提供了动态分配的功能,当运行程序调用malloc()时就会从中分配,稍后调用free可把内存交还。而自由存储是C++中通过new和delete动态分配和释放对象的抽象概念,通过new来申请的内存区域可称为自由存储区。基本上,所有的C++编译器默认使用堆来实现自由存储,也即是缺省的全局运算符new和delete也许会按照malloc和free的方式来被实现,这时藉由new运算符分配的对象,说它在堆上也对,说它在自由存储区上也正确。但程序员也可以通过重载操作符,改用其他内存来实现自由存储,例如全局变量做的对象池,这时自由存储区就区别于堆了。我们所需要记住的就是:
堆是操作系统维护的一块内存,而自由存储是C++中通过new与delete动态分配和释放对象的抽象概念。堆与自由存储区并不等价。
3. 野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)指针变量在定义时如果未初始化,其值是随机的,指针变量的值是别的变量的地址,意味着指针指向了一个地址是不确定的变量,此时去解引用就是去访问了一个不确定的地址,所以结果是不可知的。
4. malloc和new区别
在C++中,申请动态内存与释放动态内存用new/delete 与 malloc/free都可以,new/malloc申请动态内存,操作系统无法自动回收,需要对应的delete/free释放空间。
- malloc/free 是c语言中的库函数,需要头文件支持;而new/delete 是c++中的关键字
- 使用new操作符时编译器会根据类型信息自行计算所需要的内存,而malloc需要显式指出所需内存的尺寸
int *p = new int[2];
int *q = (int *)malloc(2*sizeof(int));
-
malloc内存分配成功时,返回的是void*,需要转换成所需要的类型
new内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符
free释放内存的时候需要的是void* 类型的参数
delete释放内存的时候需要使用具体类型的指针
-
new操作符在分配失败的时候会抛出bac_alloc异常
malloc在分配内存失败时返回NULL
-
new操作符从自由存储区(free store)上为对象动态分配内存空间,允许重载new/delete操作符
malloc函数从堆上动态分配内存,malloc不允许被重载
-
new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。
malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。
5. new()和new[]的区别
- new()创建一个对象
- new[]创建一个动态数组
- 释放方法也不一样,new()对应delete,new[]对应delete[]
char *pc = new char('a'); //开辟一个内存单元,并用括号里的初始化
char *pca = new char[15]; //开辟一个数组
释放内存的方法也不一样:
delete pc;
delete []pc;
6. delete和delete[]的区别
new 分配的单个对象的内存空间的时候用 delete,回收用 new[] 分配的一组对象的内存空间的时候用 delete[],其实要分基本数据类型和自定义数据类型。
-
基本数据类型
int *a = new int[10];
...
delete a; // 方式1
delete [ ] a; //方式2
基本的数据类型对象没有析构函数,并且new 在分配内存时会记录分配的空间大小,则delete时能正确释放内存,无需调用析构函数释放其余指针。因此两种方式均可。
- 自定义数据类型
#include <iostream>;
using namespace std;
class T {
public:
T() { cout << "constructor" << endl; }
~T() { cout << "destructor" << endl; }
};
int main()
{
const int NUM = 3;
T* p1 = new T[NUM];
cout << hex << p1 << endl; //输出P1的地址
// delete[] p1;
delete p1;
cout << endl;
T* p2 = new T[NUM];
cout << p2 << endl; //输出P2的地址
delete[] p2;
return 0;
}
从运行结果中我们可以看出,delete p1 在回收空间的过程中,只有 p1[0] 这个对象调用了析构函数,其它对象如 p1[1]、p1[2] 等都没有调用自身的析构函数,这就是问题的症结所在。如果用 delete[],则在回收空间之前所有对象都会首先调用自己的析构函数。
基本类型的对象没有析构函数,所以回收基本类型组成的数组空间用 delete 和 delete[] 都是应该可以的;但是对于类对象数组,只能用 delete[]。对于 new 的单个对象,只能用 delete 不能用 delete[] 回收空间。
所以一个简单的使用原则就是:new 和 delete、new[] 和 delete[] 对应使用。
7. delete或者free之要将指针指为NULL
delete和free被调用后,内存不会立即回收,指针也不会指向空,delete或free仅仅是告诉操作系统,这一块内存被释放了,可以用作其他用途。但是由于没有重新对这块内存进行写操作,所以内存中的变量数值并没有发生变化,这时候就会出现野指针的情况。因此,释放完内存后,应该把指针指向NULL
#include <iostream>
using namespace std;
int main()
{
int *p = new int;
*p = 3;
cout << *p << endl;
cout << p << endl;
delete p;
cout << *p << endl;
cout << p << endl;
return 0;
}
delete之后,p指向的地址并没有发生变化,而地址里面的内容却成了随机数,那么,我们可以得到下面这条结论
我们在删除一个指针之后,编译器只会释放该指针所指向的内存空间,而不会删除这个指针本身。
此时p也就成为一个野指针。
#include <iostream>
using namespace std;
int main()
{
int *p = new int;
delete p;
p = NULL;
delete p;
}
如果不加p=NULL;程序运行时就会挂掉,加上就不会了,对NULL空间多次释放时没有问题的,但是不是NULL就不可以,因为这段空间已经释放掉,不属于本程序,不能够取随意的释放。
8. 使用原则
- 原则1: 优先使用new,delete
- 原则2: 要new和delete配对,new[]和delete [],malloc和free配对使用
- 原则3: free和delte后指针一定赋予NULL,防止成为野指针
9.C++智能指针如何指向数组
智能指针在帮助C++程序员管理动态内存方面可谓神兵利器,但是在有些情况下我们想要对数组进行动态内存管理就会发现一个问题 咦?shared_ptr 在默认情况下是不能指向数组的,那是为什么呢。
原因是因为我们的 shared_ptr 默认的删除器是使用 Delete 对智能指针中的对象进行删除,而 delete 要求 new 时是单一指针 Delete时也应该是指针 new时是数组 delete 也应该用数组类型去delete
shared_ptr
所以我们如果想让我们的 share_ptr 去指向指针 我们只需要去使用一个可调用对象即可 在这种情况下比较常用的函数或者lambda表达式均可
bool del(int *p){
delete [] p;
}
shared_ptr<int> shared(new int[100],del);//使用函数
shared_ptr<int> ptr(new int[100],
[](int *p){delete [] p;});//使用lambda表达式
因为智能指针没有重载下标运算符 意味着我们不能想数组那样去使用这个指针 那怎么样才可以使用呢
shared_ptr 有一个函数可以返回当前智能指针的内置指针的函数 就是成员函数get() 但是我们要注意当智能指针指针已经释放内存以后,get得到的指针就成了空悬指针
有一点需要注意 在我们得到get返回的指针以后,智能指针对象的引用计数其实并没有增加
get() const noexcept
{ return _M_ptr; }
这是get的函数定义
但是如果我们delete从get得到的指针 并不会出现多次delete的错误,现在还不是很理解为什么
我们该如何使用从get中得到的指针呢 其实很简单,就是我们一般的指针操作即可
auto x = ptr.get();
cout << *(x+i) << endl;
注意!!!
其实只是C++11 中不支持而已 C++17中已经支持
unique_ptr
相比与shared_ptr unique_ptr对于动态数组的管理就轻松多了 我们只需要直接使用即可
unique_ptr<int[]>unique(new int[100]);
而且unique_ptr是重载了下标运算符的,意味着我们可以方便把其当数组一样使用
Boost C++库
著名的Boost库其实是支持指向数组的,使用方法与unique_ptr差不多
Boost库是什么?
类名为boost::shared_array,定义在<boost/shared_ptr.hpp>
boost::shared_array<int> arr(new int[100]);
参考文献: