目录

一、C/C++ 内存分布

二、程序内存区域划分

三、代码变量内存分析

1. globalVar 在哪里?

2. staticGlobalVar 在哪里?

3. staticVar 在哪里?

4. localVar 在哪里?

5. num1 在哪里?

6. char2 在哪里?

7. *char2 在哪里?

8. pChar3 在哪里?

9. *pChar3 在哪里?

10. ptr1 在哪里?

11. *ptr1 在哪里?

四、C语言动态内存管理

1. malloc

功能

用法

特点

2. calloc

功能

用法

特点

3. realloc

功能

用法

特点

4. free

功能

用法

五、malloc/calloc/realloc 区别

六、C++ 动态内存管理

1. new/delete 操作内置类型

七、new/delete 与 malloc/free 区别

八、自定义类型与构造析构

malloc/free

new/delete

九、new申请空间失败

十、operator new 与 operator delete


一、C/C++ 内存分布

在学习动态内存管理之前,首先需要了解程序运行时内存是如何分布的。

先看下面这段代码:

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";

    const 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);
}

二、程序内存区域划分

C/C++ 程序运行时,内存通常分为以下几个区域:

区域 作用
栈(Stack) 存放局部变量、函数参数等
堆(Heap) 动态申请的内存
数据段/静态区 全局变量、静态变量
代码段/常量区 程序代码、字符串常量

三、代码变量内存分析

1. globalVar 在哪里?

int globalVar = 1;

属于数据段(静态区)

因为它是全局变量。


2. staticGlobalVar 在哪里?

static int staticGlobalVar = 1;

属于数据段(静态区)

static 修饰的全局变量仍然在静态区。


3. staticVar 在哪里?

static int staticVar = 1;

属于数据段(静态区)

静态局部变量生命周期是整个程序运行期间。


4. localVar 在哪里?

int localVar = 1;

属于栈

局部变量存放在栈区。


5. num1 在哪里?

int num1[10];

属于栈

数组本身是局部变量。


6. char2 在哪里?

char char2[] = "abcd";

属于栈

因为这是字符数组,会把字符串拷贝到数组中。


7. *char2 在哪里?

char2[0]

属于栈,这里错误率很高,很多同学认为*char就是“abcd”,在常量区,但是这里是把“abcd”拷贝在了这个char数组里,数组内容在栈上。我们知道,char2是数组名,而数组名又代表数组首元素的地址,*char2就是char[0],即a


8. pChar3 在哪里?

const char* pChar3 = "abcd";

属于栈

pChar3 本身是局部指针变量。


9. *pChar3 在哪里?

"abcd"

这个就属于代码段(常量区)了,因为指针指向常量区

字符串常量存储在常量区。


10. ptr1 在哪里?

int* ptr1 = (int*)malloc(sizeof(int) * 4);

ptr1 本身是在栈上,因为它是局部变量。


11. *ptr1 在哪里?

malloc申请的空间

属于堆,ptr指向malloc 从堆区申请的空间。


四、C语言动态内存管理

C语言主要通过以下函数进行动态内存管理:

malloc
calloc
realloc
free

1. malloc

功能

动态申请指定字节大小的空间。

用法

int* p = (int*)malloc(sizeof(int) * 10);

特点

  • 申请的空间不初始化

  • 返回 void*

  • 失败返回 NULL


2. calloc

功能

申请空间并初始化为0。

用法

int* p = (int*)calloc(10, sizeof(int));

特点

  • 自动初始化为0

  • 参数是:

    • 元素个数

    • 单个元素大小


3. realloc

功能

扩容或缩容动态空间。

用法

p = (int*)realloc(p, sizeof(int) * 20);

特点

  • 可能原地扩容

  • 也可能开辟新空间后拷贝数据

  • 返回新地址

void Test ()
{
   // 1.malloc/calloc/realloc的区别是什么?
    int* p2 = (int*)calloc(4, sizeof (int));
    int* p3 = (int*)realloc(p2, sizeof(int)*10);
    // 这里需要free(p2)吗?
    free(p3 );
}

如果是原地扩容,p2==p3,释放p2就是释放p3

如果是异地扩容,编译器会自己释放p2

所以我们不需要自己手动释放p2


4. free

功能

释放动态申请的空间。

用法

free(p);

五、malloc/calloc/realloc 区别

函数 特点
malloc 申请空间但不初始化
calloc 申请空间并初始化为0
realloc 调整空间大小

六、C++ 动态内存管理

C++ 提供:

new
delete

用于动态内存管理。


1. new/delete 操作内置类型

int* p1 = new int;

int* p2 = new int(10);

int* p3 = new int[3];

delete p1;
delete p2;
delete[] p3;

注意:

  • 单个对象:

new/delete
  • 数组对象:

new[]/delete[]

必须配套使用。

int main()
{
	int* p1 = new int;
	int* p2 = new int[10];

	delete p1;
	delete[] p2;

	// +ʼ
	int* p3 = new int(0);
	int* p4 = new int[10]{ 0 };
	int* p5 = new int[10]{1,2,3,4,5};

	delete p3;
	delete[] p4;
	delete[] p5;

	return 0;
}

p1为单个对象

p2为多个对象

p3表示对单个对象进行初始化

p4表示对10个对象全部初始化为0

p5表示对10个对象中前5个初始化为1,2,3,4,5,后5个初始化为0


七、new/delete 与 malloc/free 区别

对比项 malloc/free new/delete
本质 函数 操作符
初始化 不会 可以
返回值 void* 对应类型指针
失败处理 返回NULL 抛异常
自定义类型 不调用构造析构 调用构造析构

八、自定义类型与构造析构

class A
{
public:
    A(int a = 0)
        :_a(a)
    {
        cout << "A()" << endl;
    }

    ~A()
    {
        cout << "~A()" << endl;
    }

private:
    int _a;
};

malloc/free

A* p1 = (A*)malloc(sizeof(A));

free(p1);

特点:

  • 只开空间

  • 不调用构造函数

  • 不调用析构函数


new/delete

A* p2 = new A(1);

delete p2;

特点:

  • 会调用构造函数

  • 会调用析构函数

int main()
{
	A* p1 = new A;
	A* p2 = new A(1);

	delete p1;
	delete p2;
}

这里对p1,p2会调用构造和析构

那么为什么我们要用new,看以下代码

struct ListNode
{
	int val;
	ListNode* next;

	ListNode(int x)
		:val(x)
		, next(nullptr)
	{}
};

int main()
{

	ListNode* n1 = new ListNode(1);
	ListNode* n2 = new ListNode(1);
	ListNode* n3 = new ListNode(1);
	ListNode* n4 = new ListNode(1);
	n1->next = n2;
	n2->next = n3;
	n3->next = n4;

	return 0;
}

在c语言中,我们要创建一个节点,还记得我们是怎么操作的吗?申请一个节点BuyNode()

SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

这里申请空间后还要自己手动赋值,非常的麻烦

而用new,我们既不用调用malloc函数,而且也不用手动赋值,因为new会帮我们调用构造函数,帮助我们赋值,非常的方便,也就是说我们可以用一句代码

ListNode* n1 = new ListNode(1);

直接代替掉上面一整个函数

然后下面是构造的一些方式

A(int a1 = 0, int a2 = 0)
	:_a1(a1)
	, _a2(a2)
{
	cout << "A(int a1 = 0, int a2 = 0)" << endl;
}


int main()
{
	A* p1 = new A(1);
	A* p2 = new A(2,2);

	A aa1(1, 1);
	A aa2(2, 2);
	A aa3(3, 3);
	A* p3 = new A[3]{aa1, aa2, aa3};

	A* p4 = new A[3]{ A(1,1), A(2,2), A(3,3)};

	//A aa1 = { 1, 1 };
	A* p5 = new A[3]{ {1,1}, {2,2}, {3,3} };

	return 0;
}

p1,p2就是正常的构造

p3就是有名对象构造,这里3个对象会调用3次(先构造,再拷贝构造,但是现在的编译器有的会优化,因为编译器会觉得先构造再拷贝构造太麻烦了,直接优化成构造)

p4为匿名对象构造,这里3个对象会调用3次(先构造,再拷贝构造,但编译器也会优化成构造)

p5为隐式类型转换,c++既支持单参数隐式类型转换,也支持多参数的,用花括号括起来

{1,1}会构造成一个A的临时对象,然后用这个临时对象拷贝构造(然后编译器可以优化),本质上跟p4是一样的

九、new申请空间失败

我们知道c语言中malloc函数申请空间失败会返回NULL指针,而new申请空间失败会抛异常

正常来说我们申请空间基本上不会失败,因为堆空间有4G左右内存,我们平常申请的三瓜两枣完全是够用的,如果我们申请了很大空间,new可能会抛异常

int main()
{
	try
	{
		// throw try/catch 
		void* p1 = new char[1024 * 1024 * 1024];
		cout << p1 << endl;

		void* p2 = new char[1024 * 1024 * 1024];
		cout << p2 << endl;

		void* p3 = new char[1024 * 1024 * 1024];
		cout << p3 << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

这里我们申请了3G内存,编译器会抛异常

我们也可以看看new能申请多少空间

void func()
{
	// throw try/catch 
	int n = 1;
	while (1)
	{
		void* p1 = new char[1024 * 1024];
		cout << p1 << "->"<< n<<endl;
		++n;
	}
}

int main()
{
	try
	{
		func();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

可以看见申请了1899MB空间后就不能再申请了


十、operator new 与 operator delete

   new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是
系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过
operator delete全局函数来释放空间。

实际上:

new

底层调用:

operator new

汇编指令

new会调用operator new,然后调用构造

而:

delete

底层调用:

operator delete

先调用析构,在调用operator delete

更多推荐