C++ 内存分配和管理(八股总结)
C++是如何做内存管理的(有哪些内存区域)?
(1)堆,使用malloc、free动态分配和释放空间,能分配较大的内存;
(2)栈,为函数的局部变量分配内存,能分配较小的内存;
(3)全局/静态存储区,用于存储全局变量和静态变量;
(4)常量存储区,专门用来存放常量;
(5)自由存储区:通过new和delete分配和释放空间的内存,具体实现可能是堆或者内存池。
补充:堆是C和操作系统的术语,自由存储区是C++的术语,指的是通过new和delete动态分配和释放对象的抽象概念;基本上C++也会用堆区实现自由存储,但程序员可以通过重载操作符,改用其他内存实现自由存储,比如全局变量做的对象池。
堆和栈的内存有什么区别?
(1)堆中的内存需要手动申请和手动释放,栈中内存是由OS自动申请和自动释放;
(2)堆能分配的内存较大(4G(32位机器)),栈能分配的内存较小(1M);
(3)在堆中分配和释放内存会产生内存碎片,栈不会产生内存碎片;
(4)堆的分配效率低,栈的分配效率高;
(5)堆地址从低向上,栈由高向下。
C++和C分别使用什么函数来做内存的分配和释放?有什么区别,能否混用?
C使用malloc/free,C++使用new/delete,前者是C语言中的库函数,后者是C++语言的运算符,对于自定义对象,malloc/free只进行分配内存和释放内存,无法调用其构造函数和析构函数,只有new/delete能做到,完成对象的空间分配和初始化,以及对象的销毁和释放空间,不能混用,具体区别如下:
(1)new分配内存空间无需指定分配内存大小,malloc需要;
(2)new返回类型指针,类型安全,malloc返回void*,再强制转换成所需要的类型;
(3)new是从自由存储区获得内存,malloc从堆中获取内存;
(4)对于类对象,new会调用构造函数和析构函数,malloc不会(核心)。
什么是内存对齐(字节对齐),为什么要做内存对齐,如何对齐?
(1)内存对齐的原因:关键在于CPU存取数据的效率问题。为了提高效率,计算机从内存中取数据是按照一个固定长度的。比如在32位机上,CPU每次都是取32bit数据的,也就是4字节;若不进行对齐,要取出两块地址中的数据,进行掩码和移位等操作,写入目标寄存器内存,效率很低。内存对齐一方面可以节省内存,一方面可以提升数据读取的速度;
(2)内容:内存对齐指的是C++结构体中的数据成员,其内存地址是否为其对齐字节大小的倍数。
(3)对齐原则:1)结构体变量的首地址能够被其最宽基本类型成员的对齐值所整除;2)结构体内每一个成员的相对于起始地址的偏移量能够被该变量的大小整除;3)结构体总体大小能够被最宽成员大小整除;如果不满足这些条件,编译器就会进行一个填充(padding)。
(4)如何对齐**:**声明数据结构时,字节对齐的数据依次声明,然后小成员组合在一起,能省去一些浪费的空间,不要把小成员参杂声明在字节对齐的数据之间。
#pragma pack
指令:这是一个编译器指令,用于控制结构体或联合体中成员的对齐。例如,#pragma pack(push, 1)
可以设置对齐为1字节,而 #pragma pack(pop)
则恢复到之前的对齐设置。
#pragma pack(push, 1)
struct MyStruct {
char a; // 1 byte
int b; // 4 bytes
};
#pragma pack(pop)
C++11 alignas
关键字:C++11 引入了 alignas
关键字,允许程序员显式指定变量或类型的最小对齐要求。
struct alignas(16) MyStruct {
char a;
int b;
};
这里,MyStruct
的实例将保证16字节对齐。
malloc、calloc、realloc、alloca
- malloc:申请指定字节数的内存。申请到的内存中的初始值不确定。
- calloc:为指定长度的对象,分配能容纳其指定个数的内存。申请到的内存的每一位(bit)都初始化为 0。
- realloc:更改以前分配的内存长度(增加或减少)。当增加长度时,可能需将以前分配区的内容移到另一个足够大的区域,而新增区域内的初始值则不确定。
- alloca:在栈上申请内存。程序在出栈的时候,会自动释放内存。但是需要注意的是,alloca 不具可移植性, 而且在没有传统堆栈的机器上很难实现。alloca 不宜使用在必须广泛移植的程序中。C99 中支持变长数组 (VLA),可以用来替代 alloca。
在 C++ 中,malloc
、calloc
、realloc
和 alloca
是动态内存分配函数,主要用于在运行时分配和管理内存。下面对它们分别进行定义、主要用途和区别分析。
1. malloc
(Memory Allocation)
定义
malloc
函数用于分配指定大小的内存块,返回一个指向该内存块的指针,初始值为未定义(即未初始化)。
用途
- 用于在堆中分配内存。
- 适合需要明确大小但不需要初始化的内存场景。
示例代码
#include <cstdlib>
#include <iostream>
int main() {
int* ptr = (int*)malloc(5 * sizeof(int)); // 分配 5 个整型大小的内存块
if (ptr == nullptr) {
std::cout << "Memory allocation failed\n";
return 1;
}
for (int i = 0; i < 5; ++i) {
ptr[i] = i + 1; // 手动初始化
}
for (int i = 0; i < 5; ++i) {
std::cout << ptr[i] << " ";
}
std::cout << "\n";
free(ptr); // 释放内存
return 0;
}
输出
1 2 3 4 5
2. calloc
(Contiguous Allocation)
定义
calloc
用于分配指定数量的内存块,并将所有分配的内存初始化为零。
用途
- 适合需要分配连续的内存块且初始化为零的场景。
- 提高内存分配后的安全性。
示例代码
#include <cstdlib>
#include <iostream>
int main() {
int* ptr = (int*)calloc(5, sizeof(int)); // 分配 5 个整型大小的内存块并初始化为 0
if (ptr == nullptr) {
std::cout << "Memory allocation failed\n";
return 1;
}
for (int i = 0; i < 5; ++i) {
std::cout << ptr[i] << " "; // 输出已初始化为 0 的值
}
std::cout << "\n";
free(ptr); // 释放内存
return 0;
}
输出
0 0 0 0 0
3. realloc
(Reallocation)
定义
realloc
用于调整之前分配的内存大小。它可能会移动原内存块到新的位置,返回新内存块的指针。
用途
- 动态调整已分配内存大小。
- 避免重新分配和手动复制数据。
示例代码
#include <cstdlib>
#include <iostream>
int main() {
int* ptr = (int*)malloc(3 * sizeof(int));
if (ptr == nullptr) {
std::cout << "Memory allocation failed\n";
return 1;
}
for (int i = 0; i < 3; ++i) {
ptr[i] = i + 1;
}
// 调整内存大小为 5
ptr = (int*)realloc(ptr, 5 * sizeof(int));
if (ptr == nullptr) {
std::cout << "Memory reallocation failed\n";
return 1;
}
for (int i = 3; i < 5; ++i) {
ptr[i] = i + 1; // 初始化新分配的部分
}
for (int i = 0; i < 5; ++i) {
std::cout << ptr[i] << " ";
}
std::cout << "\n";
free(ptr);
return 0;
}
输出
1 2 3 4 5
4. alloca
(Allocate on Stack)
定义
alloca
用于在栈上分配内存,分配的内存在函数返回后会自动释放。
用途
- 用于短期内存分配,避免显式释放。
- 適合临时数据的内存管理。
示例代码
#include <cstdlib>
#include <iostream>
int main() {
int* ptr = (int*)alloca(5 * sizeof(int)); // 分配 5 个整型大小的内存块在栈上
for (int i = 0; i < 5; ++i) {
ptr[i] = i + 1;
}
for (int i = 0; i < 5; ++i) {
std::cout << ptr[i] << " ";
}
std::cout << "\n";
// 不需要调用 free
return 0;
}
输出
1 2 3 4 5
区别分析
特性 | malloc | calloc | realloc | alloca |
---|---|---|---|---|
分配位置 | 堆 | 堆 | 堆 | 栈 |
是否初始化 | 否 | 是(初始化为 0) | 保持原始数据,扩展部分未初始化 | 否(与 malloc 类似) |
内存调整 | 不支持 | 不支持 | 支持 | 不支持 |
释放方式 | 手动调用 free | 手动调用 free | 手动调用 free | 自动释放 |
malloc、free
用于分配、释放内存
malloc、free 使用
申请内存,确认是否申请成功
char *str = (char*) malloc(100);
assert(str != nullptr);
释放内存后指针置空
free(p);
p = nullptr;
new、delete
- new / new[]:完成两件事,先底层调用 malloc 分配了内存,然后调用构造函数(创建对象)。
- delete/delete[]:也完成两件事,先调用析构函数(清理资源),然后底层调用 free 释放空间。
- new 在申请内存时会自动计算所需字节数,而 malloc 则需我们自己输入申请内存空间的字节数。
new、delete 使用
申请内存,确认是否申请成功
int main()
{
T* t = new T(); // 先内存分配 ,再构造函数
delete t; // 先析构函数,再内存释放
return 0;
}
原文地址:https://blog.csdn.net/xiaolan7777777/article/details/145260220
免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!