目录
一.C/C++内存分布
1. 选择题:
2. 填空题:
3.说明
4.堆栈深层理解的考察题目
(1)
(2)
C++内存管理方式——————————————————
二.new/delete操作内置类型
>:1 malloc和new区别
>:2 free和delete区别
举个[]不匹配的例子:
(1)例子1:开链表节点
(2)例子2:栈
(3)例子3:栈构造队列 MyQueue 形式
(4)malloc失败,返回空指针;new失败,抛异常
【2】捕获异常
【3】捕获异常通常写法
5.new的相关易错题目
(1)题目1:求构造函数和析构函数的执行次数
(2)题目2:参数存放在哪个区?new申请的空间存放在哪个区?
(3)题目3:
三.operator new与operator delete函数(重要点进行讲解)
1.operator new 底层是封装了malloc:
operator delete 底层是封装了free:
2.new的真正底层原理(针对自定义类型):
① call operator new :调用operator new函数申请空间 ② call Stack构造函数:在申请的空间上执行构造函数,完成对象的构造
delete的原理
new T[N]的原理
delete[]的原理
3.定位new表达式(placement-new) (了解)
4.malloc/free和new/delete的区别(总结)
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
static int staticVar = 1;
int localVar = 1;
int num1[10] = {1, 2, 3, 4};
char char2[] = "abcd";
char* pChar3 = "abcd";
int* ptr1 = (int*)malloc(sizeof (int)*4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int)*4);
free (ptr1);
free (ptr3);
}
答:(从左往右,从上往下顺序)前3个选C:因为全局变量,静态变量或静态全局变量生命周期都是全局,都属于静态区。静态的全局的在静态区;不可修改的常量,编译好的指令在常量区。const都可以修饰局部,全局,静态变量,不改变其空间位置,只是使其不可修改。常变量也是在栈上的
4,5选A:局部变量和数组都是在栈上。
6,7选A:6是数组存在栈上没问题,7是数组指向的内容,他是把常量字符串的内容拷贝到数组中,数组指向的内容是在栈上
下面有关c++内存分配堆栈说法错误的是( )
A.对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制
B. 对于栈来讲,生长方向是向下的,也就是向着内存地址减小的方向;对于堆来讲,它的生长方向是向上的,是向着内存地址增加的方向增长
C.对于堆来讲,频繁的 new/delete 势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题
D.一般来讲在 32 位系统下,堆内存可以达到4G的空间,但是对于栈来讲,一般都是有一定的空间大小的
答:
A.栈区主要存在局部变量和函数参数,其空间的管理由编译器自动完成,无需手动控制,堆区是自己申请的空间,在不需 要时需要手动释放
B.栈区先定义的变量放到栈底,地址高,后定义的变量放到栈顶,地址低,因此是向下生长的,堆区则相反
C.频繁的申请空间和释放空间,容易造成内存碎片,甚至内存泄漏,栈区由于是自动管理,不存在此问题
D错。
D.32位系统下,最大的访问内存空间为4G,所以不可能把所有的内存空间当做堆内存使用,故错误
C++中关于堆和栈的说法,哪个是错误的:( )
A.堆的大小仅受操作系统的限制,栈的大小一般较小
B.在堆上频繁的调用new/delete容易产生内存碎片,栈没有这个问题
C.堆和栈都可以静态分配
D.堆和栈都可以动态分配
答:
A.堆大小受限于操作系统,而栈空间一般有系统直接分配
B.频繁的申请空间和释放空间,容易造成内存碎片,甚至内存泄漏,栈区由于是自动管理,不存在此问题
C.堆无法静态分配,只能动态分配
D.栈可以通过函数_alloca进行动态分配,不过注意,所分配空间不能通过free或delete进行释放
C错。(第一次错选成了D)
new申请对象后用delete释放,并且要格式匹配:int* p1=new int 对应 delete p1;int* p2 = new int[10] 对应 delete[] p2; 对应不匹配,可能会报错,也可能不会报错。建议一定要匹配。
int main()
{
int* p1 = new int; // new 1个int对象
int* p2 = new int[10]; // new10个int对象
// new一个int对象,初始化成10
int* p3 = new int(10);
// new10个int对象,初始化成{}中的值
int* p4 = new int[10]{ 10, 1, 2, 3 };
// 不匹配,可能会报错,可能不会报错。建议一定要匹配
delete p1;
delete[] p2;
delete p3;
delete[] p4;
return 0;
}
对于内置类型而言,用malloc和new,除了用法不同,没有什么区别
①他们区别在于自定义类型, malloc只开空间,new是 开空间+调用构造函数初始化
②malloc失败,返回空指针;new失败,抛异常(当内存不足时会开辟失败)解释——>(4)
所以malloc必须assert检查,new不用检查。
③new既是操作符也是关键字,malloc是函数
free是释放空间,delete是 调用析构函数清理资源+释放空间
内置类型:
char* p = new char[100]; delete p;
内存泄漏?报错?崩溃?
虽然是对的,但是不建议这么写,应该写成 delete []p。
内存泄漏吗?:不会,对于内置类型,此时delete就相当于free,因此不会造成内存泄漏
报错吗?:不会,编译不会报错,这个写法虽然不规范但是是对的,建议针对数组释放使用delete[],如果是自定义类型,不使用方括号就会运行时错误
崩溃吗?不会,对于内置类型,程序不会崩溃,但不建议这样使用
自定义类型:
A* p = new A[100];
delete p; (自定义类型不用delete []p;会导致内存泄漏吗?)
不会内存泄漏:new []和new 底层调用operator new,operator new又调用malloc,delete和delete[] 同理,底层都是调用free。所以不会内存泄漏
不会报错,有可能崩溃:崩溃是源自于他底层实现的一些原因,底层实际上就是它为了知道调用多少次析构函数,这里的内存空间前面会给你多开几个字节去存那个对象的个数,像那种方框,delete的时候,他就不会以为你头上有四个字节,这个指针偏移不对,反正等等这些底层原因吧,这个地方可能就会崩溃
struct ListNode
{
ListNode* _next;
int _val;
ListNode(int val = 0)
:_next(nullptr)
, _val(val)
{}
};
struct ListNode* BuyListNode(int x)
{
struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode));
assert(node);
node->_next = NULL;
node->_val = x;
return node;
}
int main()
{
//C语言完整开节点写法:
//ListNode* n1 = BuyListNode(1);
//直接使用malloc开节点:
ListNode* n1 = (struct ListNode*)malloc(sizeof(struct ListNode));
assert(n1);
//new写法开节点:
ListNode* n2 = new ListNode(2);
ListNode* n3 = new ListNode(3);
ListNode* n4 = new ListNode(4);
return 0;
}
new使用起来会很方便!
class Stack
{
public:
Stack(int capacity = 0)
{
cout << "Stack(int capacity)" << endl;
_a = new int[capacity];
_capacity = capacity;
_top = 0;
}
~Stack()
{
cout << "~Stack()" << endl;
delete[] _a;
_capacity = 0;
_top = 0;
}
void Push(int x)
{}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st;
Stack* ps1 = (Stack*)malloc(sizeof(Stack));
//ps1->Stack(); 这样是错的,直接malloc以后,就没法初始
//化了,构造函数不能显示调用,并且成员变量是私有,不能直接访问
assert(ps1);
Stack* ps2 = new Stack; // 开空间+调用构造函数初始化
free(ps1);
delete ps2; // 调用析构函数清理资源 + 释放空间
return 0;
}
class Stack
{
public:
Stack(int capacity = 0)
{
cout << "Stack(int capacity)" << endl;
_a = new int[capacity];
_capacity = capacity;
_top = 0;
}
~Stack()
{
cout << "~Stack()" << endl;
delete[] _a;
_capacity = 0;
_top = 0;
}
void Push(int x)
{}
private:
int* _a;
int _top;
int _capacity;
};
class MyQueue {
public:
//如果Stack没有写默认构造函数,而是普通带参构造函数就需要我们显示的写出来
//MyQueue()
// : _pushST(4)
// , _popST(4)
//{}
private:
Stack _pushST;
Stack _popST;
};
int main()
{
MyQueue* obj = new MyQueue;
delete obj;
return 0;
}
new一个 MyQueue 空间,调用 MyQueue类 的默认构造函数去初始化成员变量 _pushST 和_popST,因为成员变量是内置类型,MyQueue类 的默认构造函数再去调用对应类的默认构造函数去初始化 _pushST 和_popST 这些成员变量。
【1】(当内存不足时会开辟失败)为了展示malloc和new的报错信息,需要让malloc和new都失败,就先利用p0开一个GB的空间,目的是消耗很大空间,然后再malloc和new就会失败
int main()
{
void* p0 = malloc(1024 * 1024 * 1024);
cout << p0 << endl;
void* p1 = malloc(1024 * 1024 * 1024); //malloc失败,返回空指针
cout << p1 << endl;
void* p2 = new char[1024 * 1024 * 1024]; //new失败,抛异常
cout << p2 << endl;
return 0;
}
为了防止new弹报错窗口,我们需要去捕获异常,这里简单了解一下。
把可能抛异常的部分放在 try 中,当捕捉到异常后,直接跳到catch中,这里就是在p2那一行捕获异常就直接跳到catch中,后面的 cout << p2 << endl; 根本没执行。
int main()
{
void* p0 = malloc(1024 * 1024 * 1024);
cout << p0 << endl;
void* p1 = malloc(1024 * 1024 * 1024); //malloc失败,返回空指针
cout << p1 << endl;
try
{
//new失败,抛异常
void* p2 = new char[1024 * 1024 * 1024];
cout << p2 << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
函数封装程序后,把函数放进try中,当捕获到异常就会跳进catch,即:在程序外层捕获异常,不用抛异常后立即捕捉。
void func()
{
void* p0 = malloc(1024 * 1024 * 1024);
if (p0 == nullptr)
{
cout << p0 << endl;
}
// malloc失败,返回空指针
void* p1 = malloc(1024 * 1024 * 1024);
if (p1 == nullptr)
{
cout << p1 << endl;
}
// new失败,抛异常
void* p2 = new char[1024 * 1024 * 1024];
cout << p2 << endl;
}
int main()
{
try
{
func();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
【1】
ClassA *pclassa=new ClassA[5];
delete pclassa;
c++语言中,类ClassA的构造函数和析构函数的执行次数分别为( )
A.5,1
B.1,1
C.5,5
D.程序可能崩溃
答:申请对象数组,会调用构造函数5次,delete由于没有使用[],此时只会调用一次析构函数,但往往会引发程序崩溃
选D。
【2】
以下代码中,A 的构造函数和析构函数分别执行了几次: ( )
A*pa=new A[10];
delete []pa;
A.1、1
B.10、10
C.1、10
D.10、1
答:申请数组空间,构造函数调用的次数就是数组的大小,所以调用了10次构造函数;如果释放数组空间,delete使用了[],则会对应的调用数组大小次数的析构函数,调用了10次析构函数。
选B。
函数参数使用的空间是在()中申请的,malloc或new是在()中申请空间的?
答案:参数在栈空间存放,malloc或new申请的空间为堆区
栈区 堆区
下面有关malloc和new,说法错误的是? ( )
A.new 是创建一个对象(先分配空间,再调构造函数初始化), malloc分配的是一块内存
B.new 初始化对象,调用对象的构造函数,对应的delete调用相应的析构函数,malloc仅仅分配内存,free仅仅回收内存
C.new和malloc都是保留字,不需要头文件支持
D.new和malloc都可用于申请动态内存,new是一个操作符,malloc是是一个函数
答:
A.new会申请空间,同时调用构造函数初始化对象,malloc只做一件事就是申请空间
B.new/delete与malloc/free最大区别就在于是否会调用构造函数与析构函数
C.需要头文件malloc.h,只是平时这个头文件已经被其他头文件(
D.new是操作符,malloc是函数
选C。
operator new的源代码:
operator delete的源代码:
operator new 跟malloc功能一样, 唯一不同是失败后抛异常(CPP摒弃了C语言中开辟失败返回空指针值的做法,而是转为失败后抛异常)
int main()
{
// 跟malloc功能一样, 唯一不同是失败后抛异常
Stack* ps2 = (Stack*)operator new(sizeof(Stack));
operator delete(ps2);
Stack* ps1 = (Stack*)malloc(sizeof(Stack));
assert(ps1);
free(ps1);
Stack* ps3 = new Stack;
// call operator new
// call Stack构造函数
return 0;
}
new调用operator new,operator new又调用malloc
int main()
{
//定位new写法:相当于先开空间,再调用构造函数初始化
Stack* obj = (Stack*)operator new(sizeof(Stack));
//针对一个空间,显示调用构造函数初始化
new(obj)Stack(4);
//等价于new的写法: Stack* obj = new Stack(4)
return 0;
}