内存模型
1.内存分布
以32位计算机系统为例,内存地址分布从低地址到高地址依次是:

.txt代码段
.rodata 常量段
.data 数据段,已经初始化的全局变量和静态变量
.bss 数据段,没有初始化的全局变量和静态变量,这里会默认给它初始化为0
.heap 堆区,程序员自己开辟内存空间,向上生长
内存映射段,高效的IO映射方式,装载一个共享的动态内存库,
.stack 栈区,存储非静态局部变量、函数返回值、函数参数等
这里栈向上生长而堆向上生长的原因主要是为了最大化的利用地址空间资源的利用。
2.C语言中的动态内存管理方式
- malloc
// 开辟指定字节大小的内存空间,如果开辟成功就返回该空间的首地址,
// 如果开辟失败就返回一个NULL,只需要传递一个需要开辟的字节数大小即可。
void* malloc(size_t size);
// malloc开辟好空间后,不会对空间的内容做任何的初始化操作,
// 所以空间内的数据是一个随机值
- calloc
void* calloc(size_t num,size_t size);
// calloc的功能与malloc类似,但是calloc函数传参时需要传入两个参数
// (元素的个数,每个元素的大小);
// 同时calloc函数开辟好内存后会将空间内的每一个字节都初始化为0
- realloc
void *realloc(void *memblock, size_t size);
// realloc函数用来调整已经开辟好的动态内存的大小,
// 第一个参数是需要调整的动态内存的首地址,
// 第二个参数是动态内存调整后的新大小。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
printf("内存开辟失败\n");
}
else
{
printf("内存开辟成功\n");
int* ptr = (int*)realloc(p, 100);
//将空间扩展为100个字节大小
if (ptr != NULL)
{
printf("内存扩展成功!\n");
p = ptr;//开辟成功时
}
//使用结束,释放内存(后面介绍)
free(p);
p = NULL;
}
return 0;
}
这里realloc的内存扩展会出现三种情况(这里假设将内存大小从50字节扩展到100字节):
1.需要扩展的空间后方还有足够大的空间,则直接在原空间的后方进行扩展,并返回该内存空间的首地址(即原来的首地址)
2.需要扩展的空间后方没有足够大的空间,则需要在堆空间中重新找一块满足条件的空间,并将原空间中的数据拷贝到这个新的空间中,同时要释放原来的内存空间,最后返回新内存空间的首地址
3.后面没有足够大的空间了,同时堆区也没有这么大的空间,这时就表示开辟内存失败,返回一个null值。这时还需要手动释放之前的内存块。
realloc的内存扩展(缩小的情况):
1.缩小内存块时,超出新大小的数据将会被丢弃。
- free
void free( void *memblock );
// 将用malloc、calloc、realloc申请的动态内存空间释放掉,释放的大小就是之前申请的空间的大小。
free(p);
p = NULL;
内存泄露:
指的是程序中已动态分配的堆内存由于某种原因程序未释放或者无法释放,造成资源的浪费,导致程序运行速度减慢甚至是系统崩溃等后果。释放完内存之后,还需要将该内存块的首地址改为NULL,否则该指针将会变成一个野指针。如果传入free的是一个空指针,则free函数什么也不会做。
3.C++中动态内存管理方式
C++可以直接使用C语言中提供的内存管理方式,但是有些地方使用起来比较麻烦,于是提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
// 动态申请单个某类型的空间
int* p1 = new int;
delete p1;
// 等价于
int* p1 = (int *)malloc(sizeof(int));
free(p1);
// 动态申请多个某种类型的空间
int* p2 = new int[10];
delete[] p2;
// 等价于
int* p2 = (int *)malloc(sizeof(int)*10);
free(p1);
// 动态申请单个某类型的空间并进行初始化
int* p3 = new int(10);
delete p3;
// 等价于
int* p3 = (int*)malloc(sizeof(int));
*p3 = 10;
free(p3);
// 动态申请多个某种类型的空间并初始化
int* p4 = new int[10]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
delete[] p4;
//等价于
int* p4 = (int*)malloc(sizeof(int)* 10); //申请
for (int i = 0; i < 10; i++) //赋值
{
p4[i] = i;
}
free(p4); //销毁
同时new和delete也可以用来操作自定义的数据类型
class Test{
public:
Test(int a):_a(a){}
private:
int _a;
}
int main(){
Test t1 = new Test;
free(t1);
}
总结:
- C++中如果是申请内置类型的对象或者数组,用new/delete和malloc/free没有区别
- 如果是自定义的类型,就会有所差,new和delete分别会申请空间+构造函数、析构函数+释放空间,而malloc和free仅仅只是开辟空间和释放空间
4.operator new和operator delete函数
new和delete是用户进行动态内存分配和释放的操作符,operator new和operator delete是系统提供的全局函数,new和delete在底层是通过调用全局函数operator new和operator delete来进行申请和释放空间的。实际上,operator new的底层通过调用malloc函数来申请空间的,当malloc申请空间成功时直接返回;若申请空间失败,则尝试执行空间不足的应对措施,通过用户自定义的一些措施来继续申请空间或者抛出异常。而operator delete的底层是通过free函数来释放空间的。
5.new的几种类型
1.普通new
用于分配单个对象或数组,并返回指向分配内存的指针
2.带初始化的new
在分配内存的同时对对象进行初始化操作
3.定位new
用于在指定的内存地址上构造对象,通常用于内存池或者内存管理
#include <iostream>
#include <new> // 包含 std::nothrow
int main() {
char buffer[sizeof(int)]; // 分配一块足够大的内存
int* p = new (buffer) int(42); // 在 buffer 上构造一个 int 对象
std::cout << *p << std::endl; // 输出 42
p->~int(); // 手动调用析构函数
return 0;
}
//定位new的使用有两种形式
#include <iostream>
using namespace std;
class A
{
public:
A(int a = 0) //构造函数
:_a(a)
{}
~A() //析构函数
{}
private:
int _a;
};
int main()
{
//new(place_address)type 形式
A* p1 = (A*)malloc(sizeof(A));
new(p1)A;
//new(place_address)type(initializer-list) 形式
A* p2 = (A*)malloc(sizeof(A));
new(p2)A(2021);
//析构函数也可以显示调用
p1->~A();
p2->~A();
return 0;
}
4.不抛出异常的new
默认情况下,new在分配失败时会抛出std::bad_alloc异常,可以使用std::nothrow指定new在分配失败时返回nullptr而不是抛出异常。
#include <iostream>
#include <new> // 包含 std::nothrow
int main() {
int* p = new (std::nothrow) int;
if (!p) {
std::cerr << "Memory allocation failed" << std::endl;
} else {
*p = 10;
std::cout << *p << std::endl;
delete p;
}
return 0;
}

浙公网安备 33010602011771号