参考:https://www.runoob.com/cplusplus/cpp-tutorial.html
本教程旨在提取最精炼、实用的C++面试知识点,供读者快速学习及本人查阅复习所用。
目录
第六章 动态内存
C++ 程序中的内存分为两个部分:
- 栈:在函数内部声明的所有变量都将占用栈内存。
- 堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存。
6.1 new和delete运算符
在 C++ 中,可以使用特殊的运算符为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址。这种运算符即 new 运算符。如果不再需要动态分配的内存空间,可以使用 delete 运算符,删除之前由 new 运算符分配的内存。
#include <iostream>
using namespace std;
int main ()
{
double* pvalue = NULL; // 初始化为 null 的指针
pvalue = new double; // 为变量请求内存
*pvalue = 29494.99; // 在分配的地址存储值
cout << "Value of pvalue : " << *pvalue << endl;
delete pvalue; // 释放内存
return 0;
}
6.2 动态内存分配
6.2.1 数组的动态内存分配
假设我们要为一个字符数组(一个有 20 个字符的字符串)分配内存,我们可以使用上面实例中的语法来为数组动态地分配内存:
char* pvalue = NULL; // 初始化为 null 的指针
pvalue = new char[20]; // 为变量请求内存
要删除我们刚才创建的数组,语句如下:
delete [] pvalue; // 删除 pvalue 所指向的数组
二维数组示例:
#include <iostream>
using namespace std;
int main()
{
int **p;
int i,j; //p[4][8]
//开始分配4行8列的二维数据
p = new int *[4];
for(i=0;i<4;i++){
p[i]=new int [8];
}
for(i=0; i<4; i++){
for(j=0; j<8; j++){
p[i][j] = j*i;
}
}
//打印数据
for(i=0; i<4; i++){
for(j=0; j<8; j++)
{
if(j==0) cout<<endl;
cout<<p[i][j]<<"\t";
}
}
//开始释放申请的堆
for(i=0; i<4; i++){
delete [] p[i];
}
delete [] p;
return 0;
}
6.2.2 对象的动态内存分配
对象与简单的数据类型没有什么不同:
#include <iostream>
using namespace std;
class Box
{
public:
Box() {
cout << "调用构造函数!" <<endl;
}
~Box() {
cout << "调用析构函数!" <<endl;
}
};
int main( )
{
Box* myBoxArray = new Box[4];
delete [] myBoxArray; // 删除数组
return 0;
}
如果要为一个包含四个 Box 对象的数组分配内存,构造函数将被调用 4 次,同样地,当删除这些对象时,析构函数也将被调用相同的次数。
6.3 相关面试题
Q:new/delete具体步骤
A:使用new操作符来分配对象内存时会经历三个步骤:
- 第一步:调用operator new 函数分配一块足够大的,原始的,未命名的内存空间以便存储特定类型的对象。
- 第二步:编译器运行相应的构造函数以构造对象,并为其传入初值。
- 第三部:对象构造完成后,返回一个指向该对象的指针。
使用delete操作符来释放对象内存时会经历两个步骤:
- 第一步:调用对象的析构函数。
- 第二步:编译器调用operator delete函数释放内存空间。
Q:new/delete与malloc/free的区别是什么?
A:
- malloc/free是C语言的标准库函数, new/delete是C++的运算符。它们都可用于申请动态内存和释放内存;
- malloc/free不会去自动调用构造和析构函数,对于基本数据类型的对象而言,光用malloc/free无法满足动态对象的要求;
- malloc/free需要指定分配内存的大小,而new/delete会自动计算所需内存大小;
- new返回的是指定对象的指针,而malloc返回的是void*,因此malloc的返回值一般都需要进行强制类型转换。
Q:C++内存管理
A:在C++中,虚拟内存分为代码段、数据段、BSS段、堆区、栈区以及文件映射区六部分。
- 代码段:包括只读存储区和文本区,其中只读存储区存储字符串常量,文本区存储程序的机器代码。
- 数据段:存储程序中已初始化的全局变量和静态变量
- BSS段:存储未初始化的全局变量和静态变量(局部+全局),以及所有被初始化为0的全局变量和静态变量。
- 堆区:调用new/malloc函数时在堆区动态分配内存,同时需要调用delete/free来手动释放申请的内存。
- 映射区:存储动态链接库以及调用mmap函数进行的文件映射
- 栈区:使用栈空间存储函数的返回地址、参数、局部变量、返回值
Q:内存的分配方式
A:内存分配方式有三种:
- 静态存储区,是在程序编译时就已经分配好的,在整个运行期间都存在,如全局变量、常量。
- 栈上分配,函数内的局部变量就是从这分配的,但分配的内存容易有限。
- 堆上分配,也称动态分配,如我们用new,malloc分配内存,用delete,free来释放的内存。
Q:简单介绍内存池?
A:内存池是一种内存分配方式。通常我们习惯直接使用new、malloc申请内存,这样做的缺点在于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。内存池则是在真正使用内存之前,预先申请分配一定数量、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是,使得内存分配效率得到提升。
Q:简单描述内存泄漏?
A:内存泄漏一般是指堆内存的泄漏,也就是程序在运行过程中动态申请的内存空间不再使用后没有及时释放,导致那块内存不能被再次使用。
Q:C++中的不安全是什么概念?
A:C++中的不安全包括两种:一是程序得不到正确的结果,二是发生不可预知的错误(占用了不该用的内存空间)。可能会发生如下问题:
- 最严重的:内存泄漏,程序崩溃;
- 一般严重的:发生一些逻辑错误,且不便于调试;
- 较轻的:丢失部分数据,就像强制转换一样。
Q:内存中的堆与栈有什么区别?
A:
- 申请方式:栈由系统自动分配和管理,堆由程序员手动分配和管理。
- 效率:栈由系统分配,计算机底层对栈提供了一系列支持:分配专门的寄存器存储栈的地址,压栈和入栈有专门的指令执行,因此,其速度快,不会有内存碎片;堆由程序员分配,堆是由C/C++函数库提供的,机制复杂,需要一些列分配内存、合并内存和释放内存的算法,因此效率较低,可能由于操作不当产生内存碎片。
- 扩展方向:栈从高地址向低地址进行扩展,堆由低地址向高地址进行扩展。
- 程序局部变量是使用的栈空间,new/malloc动态申请的内存是堆空间;同时,函数调用时会进行形参和返回值的压栈出栈,也是用的栈空间。