【C++】—— 内存管理

news2024/11/23 22:05:29

【C++】—— 内存管理

  • 1 C/C++ 的内存划分
    • 1.1 C/C++ 的内存分布
    • 1.2 C/C++ 的内存分布练习
  • 2 C语言 中动态内存管理方式:malloc/calloc/realloc/free
  • 3 C++ 内存管理方式
    • 3.1 new / delete 操作内置类型
    • 3.2 new 和 delete 操作自定义类型
      • 3.2.1 new 和 delete 操作自定义类型基础
      • 3.2.2 自动调用构造和析构的好处
      • 3.2.3 多参数自定义类型
      • 3.2.4 不存在默认构造
      • 3.2.5 空间开辟失败
  • 4 operater new 与 operator delete 函数
  • 5 new 和 delete 的实现原理
    • 5.1 内置类型
    • 5.2 自定义类型
  • 6 不匹配使用的情况
    • 6.1 new / free
      • 6.1.1 内置类型
      • 6.1.2 自定义类型
    • 6.2 new [ ] / delete
      • 6.2.1 内置类型
      • 6.2.2 自定义类型
  • 7 定位 new 表达式(placement-new)(了解)
    • 7.1 定位 new 用法
    • 7.2 定位 new 用途
  • 8 malloc/free 和 new/delete 的区别

1 C/C++ 的内存划分

1.1 C/C++ 的内存分布

  一个程序的数据存储是需要分区的,C/C++ 中常见的区域有:静态区(数据段)常量区(代码段)

在这里插入图片描述

  

说明:

  • :又叫堆栈,非静态局部变量/函数参数/返回值等,栈是向下增长
  • 内存映射段:是高效的 I/O 映射方式,用于转载一个共享的动态内存库,用户可使用系统接口创建共享内存,做进程通信。
  • :用于程序运行时动态内存分配,堆是向上增长
  • 数据段:存储全局数据静态数据
  • 代码段:可执行的代码/只读常量

  
  

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


题目:

  • 选项:A.栈   B.堆   C.数据段(静态区)   D.代码段(常量区)
  1. g l o b a l V a r globalVar globalVar 在哪里?______C
  2. s t a t i c G l o b a l V a r staticGlobalVar staticGlobalVar 在哪里?______C
  3. s t a t i c V a r staticVar staticVar 在哪里?______C
  4. l o c a l V a r localVar localVar 在哪里?______A
  5. n u m 1 num1 num1 在哪里?______A
  6. c h a r 2 char2 char2 在哪里?______A
  7. ∗ c h a r 2 *char2 char2 在哪里?______A
  8. p C h a r 3 pChar3 pChar3 在哪里?______A
  9. ∗ p C h a r 3 *pChar3 pChar3 在哪里?______D
  10. p t r 1 ptr1 ptr1 在哪里?______A
  11. ∗ p t r 1 *ptr1 ptr1 在哪里?______B


答案详解:

  1. globalVar全局变量,全局变量放在静态区。  C
  2. staticGlobalVar 也是全局静态变量,放在静态区。与 g l o b a l V a r globalVar globalVar 的区别是: s t a t i c G l o b a l V a r staticGlobalVar staticGlobalVar s t a t i c static static 修饰,只能在当前文件使用。 C
  3. staticVar局部静态变量,局部静态变量虽然访问作用域在函数中,但它与全局变量一样,存放在静态区。  C
  4. localVar局部变量,局部变量放在A
  5. num1数组名,数组名表示的是首元素的地址,也可以表示整个数组。这里是后者,即开辟出的10个 i n t int int 空间,静态数组开辟的空间都是在栈数开辟的。  A

  6. char2 与上述 n u m 1 num1 num1 类似,表示的是开辟出的 5 个 c h a r char char 空间(别忘了‘\0’)。  A
  7. *char2 这里表示的是数组首元素的地址,解引用即表示第一个元素 ‘ a a a’。虽然我们知道常量字符串是存储在常量区,但是,数组中存的 “ a b c d abcd abcd” 是从常量区中 拷贝 过来存储在数组中,依然是在上。  A
  8. pChar3指针变量,是局部变量,是在栈上开辟 4/8 个空间存储 “ a b c d abcd abcd” 首字符的地址的变量。  A
  9. *pChar3 指向 “ a b c d abcd abcd” 首元素的地址 ‘ a a a’,解引用即问 ‘ a a a’ 存储在哪。“ a b c d abcd abcd” 是只读常量,放在常量区。  D
  10. ptr1 p C h a r 3 pChar3 pChar3 同理,都是存储在上。  A
  11. ptr1 指向动态开辟的空间,而 *ptr1 则是问动态开辟出的空间存储在哪,存储在上。  B

在这里插入图片描述

 某种程度来说,分区分的是什么呢?分的是生命周期!不同的对象,声明周期是不一样的
 我需要一次性的,就是局部变量,我需要长期使用的就是静态的,我需要动态申请释放的,就是上的,我需要不修改的,就是常量区
 我们程序员主要关注的就是堆区,只有堆区是交给程序员自己控制的,其他都是编译器在管

2 C语言 中动态内存管理方式:malloc/calloc/realloc/free

  关于上述 4 个函数,在 C语言 的学习中已详细讲解,这里就不再赘述,感兴趣的小伙伴可跳转至此:【C语言】—— 动态内存管理
  
  

3 C++ 内存管理方式

3.1 new / delete 操作内置类型

  C语言 内存管理方式在 C++ 中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此 C++ 又提出了自己的内存管理方式:通过 n e w new new d e l e t e delete delete 操作符进行动态内存管理。

int main()
{
	//动态申请一个int类型空间
	int* p1 = new int;

	//动态申请10个int类型空间
	int* p2 = new int[10];

	//动态申请一个int类型空间,并初始化为10
	int* p3 = new int(10);

	//动态申请10个int类型空间,并将前面3个初始化为1,后面默认初始化为0
	int* p4 = new int[5] {1, 1, 1,};

	//释放空间
	//new匹配delete,new[]匹配delete[]
	delete p1;
	delete[] p2;
	delete p3;
	delete[] p4;

	return 0;
}

在这里插入图片描述

  
  可以看到,与 m a l l o c malloc malloc 不同, n e w new new支持初始化的。
  未给值时,默认不初始化。给了值则初始化,只给了部分值,后面默认初始化为0
  

在这里插入图片描述

  
  

3.2 new 和 delete 操作自定义类型

3.2.1 new 和 delete 操作自定义类型基础

  C++ 抛弃 m a l l o c malloc malloc 而自己创建 n e w new new d e l e t e delete delete 仅仅是为了上述能够初始化吗?肯定不是的,谜底出现在自定义类型

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

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

	p1->Print();
	p2->Print();

	delete p1;
	delete p2;

	return 0;
}

运行结果:

在这里插入图片描述

   n e w / d e l e t e new/delete new/delete m a l l o c / f r e e malloc/free malloc/free 最大区别是 : n e w / d e l e t e new/delete new/delete 对于自定义类型除了开空间还会自动调用其构造函数和析构函数
  
  

3.2.2 自动调用构造和析构的好处

  那有小伙伴问:自动调用构造和析构函数真的那么重要吗?
  
我们以链表节点来感受一下:

struct ListNode
{
	int val;
	ListNode* next;

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

};

  有做过 C++ 相关 OJ 的小伙伴都知道,C++ 中链表是这样给的

  这时,我们初始化节点就很方便啦

int main()
{
	ListNode* p1 = new ListNode;
	ListNode* p2 = new ListNode(1);
	ListNode* p3 = new ListNode(2);
	p1->next = p2;
	p2->next = p3;

	delete p1;
	delete p2;
	delete p3;

	return 0;
}

  想一下,如果是 C语言 是怎么实现的?是不是还要自己写一个创建节点的函数,而销毁节点是不是也要自己写一个函数。现在好了 n e w new new d e l e t e delete delete 自动调用构造和析构,活都帮你干了。
  
  

3.2.3 多参数自定义类型

  对于多参数的自定义类型, n e w / d e l e t e new/delete new/delete 也同样会调用其构造与析构函数,但如何传参呢?

class A
{
public:
	A(int a = 0, int b = 0)
		:_a(a)
		,_b(b)
	{
		cout << "A(int a)" << endl;
	}
	A(const A& a)
	{
		_a = a._a;
		_b = a._b;
		cout << "A(const A& a)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
	void Print()
	{
		cout << "_a = " << _a << endl;
	}
private:
	int _a = 0;
	int _b = 0;
};


int main()
{
	A* p1 = new A(1, 1);
	cout << endl;

	//数组多参数给值方法
	
	//法一:严格来说这不是调用构造,而是拷贝构造
	A a1(1, 1);
	A a2(2, 2);
	A a3(3, 3);
	A* p2 = new A[3]{ a1,a2,a3 };
	cout << endl;

	//法二:匿名对象(编译器优化成直接构造)
	A* p3 = new A[3]{ A(1,1), A(2,2), A(3,3) };
	cout << endl;

	//法三:隐式类型转换(编译器优化成直接构造)
	A* p4 = new A[3]{ {1,1},{2,2}, {3,3} };
	cout << endl;

	return 0;
}


运行结果:

在这里插入图片描述

  

在这里插入图片描述

  
  

3.2.4 不存在默认构造

  但如果自定义类型不存在默认构造就需要注意啦

class A
{
public:
	A(int a)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
	void Print()
	{
		cout << "_a = " << _a << endl;
	}
private:
	int _a = 0;
};
int main()
{
	A* p = new A;
	return 0;
}

在这里插入图片描述

  如果自定义类型不存在默认构造函数,那么编译器会报错
  
  一般情况下,我们不再使用 m a l l o c / f r e e malloc/free malloc/free因为 n e w / d e l e t e new/delete new/delete 兼容了他们所有的用法,还能自动调用构造和析构
  
  

3.2.5 空间开辟失败

  用 m a l l o c malloc malloc 开辟空间,要对返回值进行检查,检查返回值是否为空指针。那用 n e w new new 开辟空间要不要检查呢?
  也是的, n e w new new 失败是 抛异常,这里我们简单提一下,更多知识将会后面学习
  
:一般情况下,空间开辟不会失败,一般都是空间开的太大才会失败
  
  抛一共有个关键字: t h r o w throw throw t r y try try c a t c h catch catch
  当发生异常, t h r o w throw throw 抛出一个对象,对异常进行处理要引入 t r y try try(尝试) / c a t c h catch catch(捕获)
  
这里我们申请 2G 的空间试试:

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

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


	return 0;
}

1 G = 1024 M ; 1 M = 1024 K ; 1 K = 1024 B y t e ; 1 B y t e = 8 b i t 1G = 1024M ;1M = 1024K; 1K = 1024Byte ;1Byte = 8bit 1G=1024M1M=1024K1K=1024Byte1Byte=8bit
  
运行结果:

在这里插入图片描述

  申请 第一G 还是没问题的,申请 第二G 程序中断

  我们对其进行捕获(先不用管怎么写,知道这是在捕获就行)

int main()
{
	try
	{
		void* p1 = new char[1024 * 1024 * 1024];
		void* p2 = new char[1024 * 1024 * 1024];
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	
	return 0;
}

运行结果:

在这里插入图片描述

  打印: b a d a l l o c a t i o n bad allocation badallocation,表示已经没有你需要的那么大的内存了
  
  我们还可以来看一下在 32 位平台下可以申请到多大空间:

void func()
{
	int n = 0;
	while (1)
	{
		n++;
		void* p = new char[1024 * 1024];//一次向堆申请一字节的内存
		cout << p << ":->" << n << endl;
	}
}

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

运行结果:

在这里插入图片描述

  我们可以看到,申请了 1898M,大概是 1.85G 。要知道,32 位下的虚拟内存总共也就 4G( 2 32 b i t 2^{32} bit 232bit)。其中内核空间就占 1G,栈才分 8M。因此堆空间给了 1.85G,已经很大方了。

  在外面平时的练习中,一般是不会申请控价失败的,空间开辟失败一般是因为申请的空间太大。我们平时练习可以不捕获异常,但是在项目中都是需要捕获异常的。
  
  

4 operater new 与 operator delete 函数

   n e w new new d e l e t e delete delete 是用户进行动态内存申请和释放的操作符
  C++ 提供了两个全局函数: o p e r a t o r operator operator n e w new new o p e r a t o r operator operator d e l e t e delete delete
   n e w new new 在底层调用 o p e r a t o r operator operator n e w new new 来申请空间; d e l e t e delete delete 在底层调用 o p e r a t o r operator operator d e l e t e delete delete 来释放空间
  下面是 o p e r a t o r operator operator n e w new new o p e r a t o r operator operator d e l e t e delete delete 的源码,我们简单来看一下:

/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
*/
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
	// try to allocate size bytes
	void* p;
	while ((p = malloc(size)) == 0)
		if (_callnewh(size) == 0)
		{
			// report no memory
			// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
			static const std::bad_alloc nomem;
			_RAISE(nomem);
		}
	return (p);
}

  对于 o p e r a t o r operator operator n e w new new,我们可以看到在第五行:当malloc = 0,则后面的 i f if if 语句就抛异常。 o p e r a t o r operator operator n e w new new 的本质其实就是 调用 m a l l o c malloc malloc 函数
  

/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void* pUserData)
{
	_CrtMemBlockHeader* pHead;
	RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
	if (pUserData == NULL)
		return;
	_mlock(_HEAP_LOCK);  /* block other threads */
	__TRY
		/* get a pointer to memory block header */
		pHead = pHdr(pUserData);
	/* verify block type */
	_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
	_free_dbg(pUserData, pHead->nBlockUse);
	__FINALLY
		_munlock(_HEAP_LOCK);  /* release other threads */
	__END_TRY_FINALLY
		return;
}
/*
free的实现
*/
#define   free(p)               _free_dbg(p, _NORMAL_BLOCK)//free其实是一个宏函数

  对于 o p e r a t o r operator operator d e l e t e delete delete,我们看到 第16行,调用了一个_free_dbg_free_dbg是什么呢?我们来看到最下面 f r e e free free,其实 f r e e free free 是一个宏,其本质就是 _free_dbg。因此 o p e r a t o r operator operator d e l e t e delete delete 本质就是调用 f r e e free free 函数

  通过上述两个全局函数的实现知道, o p e r a t o r operator operator n e w new new 实际也是通过 m a l l o c malloc malloc 来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。 o p e r a t o r operator operator d e l e t e delete delete 最终是通过 f r e e free free 来释放空间的

  其实, n e w new new d e l e t e delete delete 并没有那么神奇,他们并没有单独搞一套新的申请空间方式,
   n e w new new 其本质上也是调用 m a l l o c malloc malloc,只是它没有直接调,而是调用套着 m a l l o c malloc malloc 马甲的 o p e r a t o r operator operator n e w new new
  为什么要套 o p e r a t o r operator operator n e w new new 呢?因为 m a l l o c malloc malloc 失败是直接返回空,C++ 是面向对象的语言,它希望它是按新的一种机制来走,即抛异常。所以抛异常是 o p e e r a t o r opeerator opeerator n e w new new 抛出来的
  
  

5 new 和 delete 的实现原理

5.1 内置类型

  如果申请的是内置类型的空间, n e w new new m a l l o c malloc malloc d e l e t e delete delete f r e e free free 基本类似,不同的地方是: n e w / d e l e t e e new/deletee new/deletee 申请和释放的是单个元素的空间, n e w new new[] 和 d e l e t e delete delete[] 申请的是连续的空间,而且 n e w new new 在申请空间失败时会抛异常 m a l l o c malloc malloc 会返回 NULL
  
  

5.2 自定义类型

  • n e w new new 的原理
    1、 调用 operator new 函数申请空间
    2、 在申请的空间上执行构造函数,完成对象的构造

  • d e l e t e delete delete 的原理
    1、 在空间上执行析构函数,完成对象中资源的清理工作
    2、 调用 operator delete 函数释放对象的空间

  • n e w new new T [ N ] T[N] T[N] 的原理
    1、调用 operator new[] 函数,在 o p e r a t o r operator operator n e w new new[] 中实际调用 o p e r a t o r operator operator n e w new new 函数完成 N 个对象空间的申请
    2、在申请的空间上执行 N 次构造函数

  • d e l e t e delete delete[] 的原理
    1、在释放的对象空间上执行N次析构函数,完成 N 个对象中的资源清理
    2、调用 o p e r a t o r operator operator d e l e t e delete delete[] 释放空间,实际 o p e r a t o r operator operator d e l e t e delete delete[] 中调用 o p e r a t o r operator operator d e l e t e delete delete 来释放空间

我们可以通过汇编代码来看一下 n e w new new 的底层

int main()
{
	A* p = new A(1);
	delete p;
	return 0;
}

在这里插入图片描述

在这里插入图片描述

  
  

6 不匹配使用的情况

6.1 new / free

6.1.1 内置类型

int main()
{
	int* p1 = new int;
	free(p1);
	return 0;
}

  对内置类型,如果我 n e w new new 出来,再对 f r e e free free 释放,会发生什么?

  无事发生
  这里我们要看他的本质 n e w new new 本质是调用 o p e r a t o r operator operator n e w new new 加调用构造, o p e r a t o r operator operator n e w new new 本质是调用 m a l l o c malloc malloc,而内置类型没有构造函数的概念
   d e l e t e delete delete 本质是调用 o p e r a t o r operator operator d e l e t e delete delete 加调用析构, o p e r a t o r operator operator d e l e t e delete delete 本质是 f r e e free free,而对内置类型没有调用析构的概念。
  因此,程序不会报错,也不会存在内存泄漏的情况

  
  

6.1.2 自定义类型

  那对自定义类型呢

int main()
{
	A* p1 = new A;
	free(p1)$;
	return 0;
}

  首先程序是不会报错的,但相比用 d e l e t e delete delete,少调用了一个析构函数,有可能会造成资源没有释放,有内存泄漏的风险
  
  

6.2 new [ ] / delete

  现在,我用 n e w new new[] 申请多个空间,再用 d e l e t e delete delete 释放会有问题吗

6.2.1 内置类型

int main()
{
	int* p1 = new int[10];
	delete p1;
	return 0;
}

  是没有问题的,因为 n e w new new[] 本质是调用 o p e r a t o r operator operator n e w new new[], o p e r a t o r operator operator n e w new new[] 中又调用 o p e r a t o r operator operator n e w new new,最终是 m a l l o c malloc malloc
   d e l e t e delete delete[] 本质是调用 o p e r a t o r operator operator d e l e t e delete delete[], o p e r a t o r operator operator d e l e t e delete delete[] 内调用 o p e r a t o r operator operator d e l e t e delete delete,本质是 f r e e free free,不涉及什么析构
   d e l e t e delete delete[] 只不过是 d e l e t e delete delete 多套了一层壳而已。
  内置类型不会报错,也不会造成内存泄漏。
  
  

6.2.2 自定义类型

  那如果我是自定义类型呢?

class B
{
private:
	int _b1 = 1;
	int _b2 = 2;
};

int main()
{
	B* p1 = new B[10];
	delete p1;
	return 0;
}

运行结果:

在这里插入图片描述

 可以看到,也是没问题的
  
 但对类型A呢?

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
	void Print()
	{
		cout << "_a = " << _a << endl;
	}
private:
	int _a = 0;
	int _b = 1;
};

int main()
{
	A* p1 = new A[10];
	delete p1;
	return 0;
}

在这里插入图片描述

崩溃了!
  为什么呢?同样是自定义类型怎么就区别对待呢?

  先问一下,对 类A 和 类B, n e w new new[] 应该申请多少空间呢?应该是 80

我们先看类B:
  通过汇编代码,我们知道 o p e r a t o r operator operator n e w new new[] 申请了 80 字节

在这里插入图片描述

  
再看看类A:

在这里插入图片描述

发现A多开了4个字节!

  为什么呢?

  编译器对于开辟多个对象的数组时,一般会在头上多开辟 4 个字节,用来存储对象的个数,以便 d e l e t e delete delete[] 知道需要调用几次析构函数,就像上述开 A 的空间一样。但是编译器对 B 并没有额外开 4 个字节

在这里插入图片描述

  但是,对类A,编译器返回的是 pa2 位置的地址;对类B是正常返回pb1位置地址

  接着,用 d e l e t e delete delete 将 类A 和 类B 释放,类B 的释放是没有问题的;但是 类A 就有大问题了: d e l e t e delete delete 收到的是 pa2 位置的地址,便从 pa2 开始释放空间,但一整块申请的空间只能整块释放,不能从中间释放,因此报错

  而用 d e l e t e delete delete[] 释放,它会往前偏移4个字节再释放,就不会出现这样的问题。

  那为什么 类B 不多开 4 个字节呢?因为编译器对B进行了优化,编译器看到 类B 没有去写析构函数,认为没有资源需要去释放,。所以编译器直接就不调用析构函数了,既然不用调用析构函数,那头上也就不用多开 4 个字节存个数了。

  如果 B 将析构函数加上,也会多开 4 个字节存个数,也会崩溃
  
  

7 定位 new 表达式(placement-new)(了解)

7.1 定位 new 用法

   o p e r a t o r operator operator n e w new new 是一个全局函数,我们可以显示的去调用

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

	A* p2 = (A*)operator new(sizeof(A));
	return 0;
}

   o p e r a t o r operator operator n e w new new 底层就是 m a l l o c malloc malloc,因此除了函数名不一样,用法基本是一样的。区别是它不用检查返回值,因为它是抛异常

在这里插入图片描述

  区别是 n e w new new自动调用构造函数 o p e r a t o r operator operator n e w new new 只开了空间

  那如果我现在想对一块已经存在的空间显示去调用构造函数该怎么做呢?
使用定位new表达式(placement-new)

格式

  • n e w ( p l a c e new (place new(place_ a d d r e s s ) t y p e address) type address)type 或者 n e w ( p l a c e new (place new(place_ a d d r e s s ) t y p e ( i n i t i a l i z e r address) type(initializer address)type(initializer - l i s t ) list) list)
  • p l a c e place place_ a d d r e s s address address 必须是一个指针 i n i t i a l i z e r initializer initializer - l i s t list list 是类型的初始化列表
int main()
{
	A* p1 = (A*)operator new(sizeof(A));
	new(p1)A(1);//定位new,初始化

	return 0;
}

  那析构函数怎么调用呢?

  对已经分配空间的对象,想构造函数只能通过定位new,但析构函数是可以我们显式调用的

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

	p1->~A();
	operator delete p1;

	return 0;
}

  
  

7.2 定位 new 用途

  我们要用 o p e r a t o r operator operator n e w new new 呢?直接用 n e w new new 不好吗,这主要是用于内存池
  编程中有一种技术叫池化技术,其包括内存池、线程池、连接池……
  池化技术都是为了提高效率

  那内存池是什么呢?
  假设我现在有一个工程,需要频繁地申请释放内存,这样效率难免会较低,毕竟实际中往往是多个任务同时进行。这时,就可以从内存(堆)中专门取出一部分空间,专供这个项目使用。申请从内存池中取空间,释放将空间换给内存池

在这里插入图片描述

  但现在还有一个问题:内存池中申请的只有空间,并没有对其初始化,这时 定位new 就有用武之地了。释放空间时,也是我们自己先调用析构函数在释放。
  
  

8 malloc/free 和 new/delete 的区别

  1. m a l l o c / f r e e malloc/free malloc/free f r e e free free函数 n e w / d e l e t e new/delete new/delete操作符
  2. m a l l o c malloc malloc 申请的空间不会初始化, n e w new new 可以初始化
  3. m a l l o c malloc malloc 申请空间时,需要手动计算空间大小并传递 n e w new new 只需在其后跟上空间的类型即可,如果是多个对象 [] 中指定对象个数即可
  4. m a l l o c malloc malloc 的返回值为 v o i d ∗ void* void ,在使用时必须强转 n e w new new 不需要,因为 n e w new new 后跟的是空间的类型
  5. m a l l o c malloc malloc 申请空间失败时,返回的是 NULL,因此使用时必须判空 n e w new new 不需要,但是 n e w new new 需要捕获异常
  6. 申请自定义类型对象时, m a l l o c / f r e e malloc/free malloc/free 只会开辟空间,不会调用构造函数和析构函数,而 n e w new new 在申请空间后会调用构造函数完成对对象的初始化 d e l e t e delete delete释放空间前会调用析构函数完成空间中资源的清理释放

  
  
  
  
  


  好啦,本期关于内存管理的知识就介绍到这里啦,希望本期博客能对你有所帮助。同时,如果有错误的地方请多多指正,让我们在C语言的学习路上一起进步!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2077061.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

layui栅格布局设置列间距不起作用

layui栅格布局支持设置列间距&#xff0c;只需使用预置类layui-col-space*即可。不过实际使用时却始终看不到效果。   根据layui官网文档的说明&#xff0c;只需要在行所在div元素的class属性中增加layui-col-space*即可出现列间距。如下图所示&#xff1a;   但是实际使用…

【MySQL】MySQL 表的增删改查(进阶)

欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 目录 约束类型 not null 非空 unique 唯一 default 指定默认值 primary key 主键 foreign key 外键 check字句 检查 表设计 确定实体 实体之间的关系 聚合查询 聚合函数…

基于SSM的学生信息管理系统的设计与实现 (含源码+sql+视频导入教程+文档+VISIO图)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于SSM的学生信息管理系统12拥有三种角色&#xff1a;学生、教师、管理员 学生&#xff1a;选课、查看已选课程、查看成绩 教师&#xff1a;成绩管理 管理员&#xff1a;课程管理、学生…

ai变声:视频怎么变音?分享6个语音变声器,视频变声不再难!

想过如何让自己的直播内容更吸引人吗&#xff1f;你是否希望通过变声器来打造独特的声音效果&#xff1f;或者&#xff0c;如何用创意声音提升观众的互动体验呢&#xff1f;随着直播行业的不断发展&#xff0c;每位主播都在努力寻找吸引观众的独特方式&#xff0c;而变声器正是…

【电脑使用耳机录音注意事项】

文章目录 电脑音设置 电脑音设置 打开声音设置&#xff1a;右键小喇叭 → 选择“声音(S)”→选择 “录制”&#xff1a; 选择 “阵列麦克风” 调整声音大小&#xff1a; 音频增强设置

AI大模型编写多线程并发框架(六十二):限流和并发度优化

系列文章目录 文章目录 系列文章目录前言一、项目背景二、第三轮对话-补充异步执行代码三、第四轮对话-增加限流器四、第五轮对话-抽取限流器接口五、第六轮对话-修改并发度三、参考文章 前言 在这个充满技术创新的时代&#xff0c;AI大模型正成为开发者们的新宠。它们可以帮助…

何为MethodHandles?

最近在梳理ThreadPoolExecutor&#xff0c;无意间看到其内部类Worker实现了一个名字叫做AbstractQueuedSynchronizer的抽象类。看到它&#xff0c;我便想起当年为了面试而疯狂学习这个知识点的场景。不过这种临时抱佛脚的行为&#xff0c;并未给我带来即时的收益。也是这次的疯…

基于Java的高校学生工作系统的设计与实现(论文+源码)_kaic

基于Java的高校学生工作系统的设计与实现(论文源码)_kaic 摘 要 本系统为高校学生工作管理系统&#xff0c;系统能够为高校提供便捷的学生信息管理功能。该系统采用 Java 语言编写&#xff0c;系统采用MVC架构进行设计&#xff0c;通过Servlet和JSP等技术实现前后端数据交互和…

【漏洞复现】SuiteCRM responseEntryPoint Sql注入漏洞

免责声明&#xff1a; 本文内容旨在提供有关特定漏洞或安全漏洞的信息&#xff0c;以帮助用户更好地了解可能存在的风险。公布此类信息的目的在于促进网络安全意识和技术进步&#xff0c;并非出于任何恶意目的。阅读者应该明白&#xff0c;在利用本文提到的漏洞信息或进行相关测…

基于UDS的Flash 刷写——BootLoad刷写流程详解

从0开始学习CANoe使用 从0开始学习车载测试 相信时间的力量 星光不负赶路者&#xff0c;时光不负有心人。 目录 流程概述UDS流程详解释前编程①诊断会话控制 - 切换到扩展会话&#xff08;10 03&#xff09;②例程控制-预编程条件检查&#xff08;31 01 02 03&#xff09;③DTC…

ClickHouse分布式部署搭建单分片二副本集群

搭建单分片二副本集群,使用MergeTree引擎测试数据同步 服务器: 127.0.0.1 clickhouse 127.0.0.2 clickhouse + keeper 结构图 1.修改hosts vi /etc/hosts 添加需要部署的ip和名字 127.0.0.1 node1 127.0.0.2 node2 2. node1配置文件修改 2.1 修改/etc/clickhouse-se…

Excel中使用VBS自定义函数将中文转为拼音首字母

1、在“开发工具”中&#xff0c;点击“Visual Basic”。如果没有“开发工具”&#xff0c;则添加。 2、添加“模块”&#xff0c;在窗口中添加自定义函数。 Function MyGetPYChar(char) MyCodeNumber 65536 Asc(char) If (MyCodeNumber > 45217 And MyCodeNumber <…

【网络安全】缓存配置错误导致授权绕过

未经许可,不得转载。 文章目录 正文复现正文 一个电子商务网站,它有 2 个资产:target.com和admin.target.com target.com是面向用户的门户,用户可以去那里购买物品。admin.target.com是卖家的管理门户,卖家可以在其中列出他们的物品,跟踪订单、客户信息等。 我正在测试…

有希带你深入理解指针(3)

前言 本篇文章是对指针知识的进一步讲解&#xff0c;如果对部分知识有不了解的地方可以移步前文进行学习&#xff01; 1.字符指针变量 该内容我们在前面的文章中已提到过&#xff0c;想必大家对它应该不陌生吧&#xff01;这里我们会对它进行详细的介绍。 一般情况下&#xf…

FPGA开发——IIC实现简单的串口回环

一、概述 在我们进行日常开发时&#xff0c;不管是进行MCU、单片机、还是FPGA&#xff0c;都会使用到IIC通信协议。采用串行总线可以简化系统硬件结构、减小系统体积、提高系统可靠性。常 用的串行总线有单总线&#xff08;1-Wire Bus&#xff09;、IIC&#xff08;Inter-Integ…

Codeforces Round 926 (Div. 2) C. Sasha and the Casino (博弈论*1400)

这里的意思是想让我们求得是否是能够实现不停地无上限的赚钱。 这里注意避开一个思维误区&#xff0c;如果你想的是前x次一直用1枚硬币然后吃第x1次保底&#xff0c;那么就是错误的。你应该考虑到如果前x次里面出现了胜利呢&#xff1f;这时候你拿着一枚硬币根本赚不回本。 所…

全志H616系统启动和登录

一、系统启动 刷完机烧入镜像&#xff0c;直接用MobaXterm软件串口登陆 约定固定的波特率115200。 默认登录&#xff1a; 用户&#xff1a;orangepi 密码&#xff1a;orangepi 或用户&#xff1a;root 密码&#xff1a;orangepi 在输入密码时…

YOLO 单目测距:原理、方法与代码

一、原理 单目测距的一个常见方法是假设物体的尺寸已知。通过测量物体在图像中的高度&#xff08;或宽度&#xff09;&#xff0c;并结合物体的实际高度&#xff08;或宽度&#xff09;&#xff0c;最简单的一种方式就是利用相似三角形的原理来计算物体的距离。 二、相似三角…

使用深度学习来进行击剑动作识别的裁判工作

在击剑比赛中&#xff0c;当双方几乎同时击中对方时&#xff0c;记分板两边都会亮起。这时裁判需要决定哪一方得分。一般而言&#xff0c;谁更主动或控制了局势就会得分。我尝试训练了一个模型来辅助裁判做这样的判断&#xff01;目前该模型在花剑测试集上的准确率大约为60%&am…

Vue开发者工具安装详细教程

欢迎大家订阅【Vue2Vue3】入门到实践 专栏&#xff0c;开启你的 Vue 学习之旅&#xff01; 文章目录 前言一、下载二、安装三、调试 前言 Vue 是一个框架&#xff0c;也是一个生态&#xff0c;其功能覆盖了大部分前端开发常见的需求。本文详细讲解了 Vue 开发者工具的安装。 …