从C语言到C++⑨(第三章_C&C++内存管理)详解new和delete+面试题笔试题(中)

简介: 从C语言到C++⑨(第三章_C&C++内存管理)详解new和delete+面试题笔试题

从C语言到C++⑨(第三章_C&C++内存管理)详解new和delete+面试题笔试题(上):https://developer.aliyun.com/article/1513661

3. operator newoperator delete函数详解

3.1 operator newoperator delete函数

new delete 是用户进行 动态内存申请和释放的操作符

operator new 和 operator delete是 系统提供的全局函数,(不是运算符重载)

new 在底层调用 operator new 全局函数来申请空间,

delete 在底层通过 operator 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);
}
/*
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)

通过上述两个全局函数的实现可以知道:

① operator new 实际上也是通过 malloc 来申请空间的。

② operator delete 最终也是通过 free 来释放空间的。

如果 malloc 申请空间成功就直接返回,否则执行用户提供的空间不足的应对措施,

如果用户提供该措施就继续申请,否则就抛异常。

面向过程的语言处理错误的方式:

返回值 + 错误码解决(这个之前博客讲过):

#include <stdio.h>
#include <stdlib.h>
 
int main()
{
  char* p1 = (char*)malloc(1024u * 1024u * 1024u * 2u);
  if (p1 == nullptr) 
  {
    printf("%d\n", errno);
    perror("malloc fail");
    exit(-1);
  }
  else 
  {
    printf("%p\n", p1);
  }
 
  return 0;
}

而面向对象语言处理错误的方式:

一般是抛异常,C++中也要求出错抛异常 —— try catch(后面会细讲)。

#include <iostream>
using namespace std;
 
int main()
{
  char* p2 = nullptr;
  try
  {
    char* p2 = new char[1024u * 1024u * 1024u * 2u - 1];
  }
  catch (const exception& e) 
  {
    cout << e.what() << endl;
  }
  printf("%p\n", p2);
 
  return 0;
}

C++ 提出 new 和 delete,主要是解决两个问题:

① 自定义类型对象自动申请的时候,初始化合清理的问题。

    new / delete 会调用构造函数和析构函数。

② new 失败了以后要求抛异常,这样才符合面向语言的出错处理机制。

(delete 和 free 一般不会失败,如果失败了,就是释放空间上存在越界或者释放指针位置不对)


3.2  重载operator new 与 operator delete(了解)

默认情况下operator new 与 operator delete使用全局库里面

如果我们自己重载operator new 与 operator delete了,

那么编译器就会调我们自己重载的,而不会调原来的:

#include<iostream>
using namespace std;
 
class A
{
public:
  A(int a = 0)
    : _a(a)
  {
    cout << "A():" << this << endl;
  }
 
  ~A()
  {
    cout << "~A():" << this << endl;
  }
 
private:
  int _a;
};
 
// 重载operator delete,在申请空间时:打印在哪个文件、哪个函数、第多少行,申请了多少个字节
void* operator new(size_t size, const char* fileName, const char* funcName, size_t lineNo)
{
  void* p = ::operator new(size);
  cout << "new:" << fileName << "||" << funcName << "||" << lineNo << "||" << p << "||" << size << endl;
  return p;
}
 
 重载operator delete,在释放空间时:打印再那个文件、哪个函数、第多少行释放,不实现这个,不然使用要加()
//void operator delete(void* p, const char* fileName, const char* funcName, size_t lineNo)
//{
//  cout << "delete:" << fileName << "||" << funcName << "||" << lineNo << "||" << p << endl;
//  ::operator delete(p);
//}
 
// 重载operator delete
void operator delete(void* p)
{
  cout << "delete:" << endl;
  free(p);
}
 
#ifdef _DEBUG
     #define new new(__FILE__, __FUNCTION__, __LINE__)
     // #define delete(p) operator delete(p, __FILE__, __FUNCTION__, __LINE__) 不实现这个宏,不然使用要加()
#endif
 
int main()
{
  A* p1 = new A;
  delete p1;
 
  A* p2 = new A[4];
  delete[] p2;
 
  A* p3 = new A;
  delete p3;
 
  A* p4 = new A;
  delete p4;
 
  A* p5 = new A;
  delete p5;
 
    return 0;
}

这里为了好看就不把文件名打印出来了:


类内重载operator new 与 operator delete:

我们知道:new -> operator new + 构造函数,默认情况下operator new使用全局库里面

每个类可以去实现自己专属operator new  new这个类对象,它就会先调自己实现这个operator new

上面我们提到:C语言内存管理方式在有些地方无能为力,而且使用起来比较麻烦

下面代码演示了,针对链表的节点 ListNode 通过重载类专属 operator new / operator delete,

实现链表节点使用内存池申请和释放内存,提高效率:

// new -> operator new + 构造函数
// 默认情况下operator new使用全局库里面
// 每个类可以去实现自己专属operator new  new这个类对象,他就会调自己实现这个operator new
 
// 实现一个类专属的operator new  -- 了解一下
 
#include<iostream>
using namespace std;
 
class A
{
public:
  A(int a = 0)
    : _a(a)
  {
    cout << "A():" << this << endl;
  }
 
  ~A()
  {
    cout << "~A():" << this << endl;
  }
 
private:
  int _a;
};
 
struct ListNode
{
  int _val;
  ListNode* _next;
 
  static allocator<ListNode> alloc;// 内存池
 
  void* operator new(size_t n)
  {
    cout << "operator new -> STL内存池allocator申请" << endl;
    void* obj = alloc.allocate(1);
    return obj;
  }
 
  void operator delete(void* ptr)
  {
    cout << "operator delete -> STL内存池allocator申请" << endl;
 
    alloc.deallocate((ListNode*)ptr, 1);
  }
 
  struct ListNode(int val)
    :_val(val)
    , _next(nullptr)
  {}
};
 
// allocator以后会讲,现在先了解即可
allocator<ListNode> ListNode::alloc;
 
int main()
{
  // 频繁申请ListNode. 想提高效率 -- 申请ListNode时,不去malloc,而是自己定制内存池
  ListNode* node1 = new ListNode(1);
  ListNode* node2 = new ListNode(2);
  ListNode* node3 = new ListNode(3);
 
  delete node1;
  delete node2;
  delete node3;
 
  A* p1 = new A;
 
  return 0;
}


4. new 和 delete 的实现原理

4.1 对于内置类型

如果申请的是内置类型的空间,new 和 malloc,delete 和 free 基本相似。

不同的地方是,new / delete 申请和释放的是单个元素的空间,

new[ ] 和 delete[ ] 申请的是连续空间。

而且 new 再申请空间失败时会抛异常,malloc会返回NULL。

operator new 和 operator delete 就是对 malloc 和 free 的封装。

operator new 中调用 malloc 后申请内存,失败以后,改为抛异常处理错误,

这样符合C++面向对象语言处理错误的方式。

4.2 对于自定义类型

new 的原理:

① 调用 operator new 函数申请空间。

② 在申请空间上执行构造函数,完成对象的构造。

delete 的原理:

① 在空间上执行析构函数,完成对象中资源的清理工作。

② 调用 operator delete 函数释放对象的空间。


new T[N] 的原理:

① 调用 operator new[] 函数,在 operator new[] 中实际调用

operator new 函数完成 N 个对象空间的申请。

② 在申请的空间上调用 N 次构造函数,对它们分别初始化。

delete[] 的原理:

① 在释放的对象空间上执行 N 次析构函数,完成 N 个对象中资源的清理。

② 调用 operator delete[] 释放空间,实际在 operator delete[] 中调用

operator delete 来释放空间。

从C语言到C++⑨(第三章_C&C++内存管理)详解new和delete+面试题笔试题(下):https://developer.aliyun.com/article/1513663?spm=a2c6h.13148508.setting.23.5e0d4f0eimuY68

目录
相关文章
|
5月前
|
安全 C语言 C++
比较C++的内存分配与管理方式new/delete与C语言中的malloc/realloc/calloc/free。
在实用性方面,C++的内存管理方式提供了面向对象的特性,它是处理构造和析构、需要类型安全和异常处理的首选方案。而C语言的内存管理函数适用于简单的内存分配,例如分配原始内存块或复杂性较低的数据结构,没有构造和析构的要求。当从C迁移到C++,或在C++中使用C代码时,了解两种内存管理方式的差异非常重要。
202 26
|
5月前
|
安全 C语言
C语言中的字符、字符串及内存操作函数详细讲解
通过这些函数的正确使用,可以有效管理字符串和内存操作,它们是C语言编程中不可或缺的工具。
325 15
|
存储 编译器 程序员
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
在C语言中,内存布局是程序运行时非常重要的概念。内存布局直接影响程序的性能、稳定性和安全性。理解C程序的内存布局,有助于编写更高效和可靠的代码。本文将详细介绍C程序的内存布局,包括代码段、数据段、堆、栈等部分,并提供相关的示例和应用。
420 5
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
1146 13
|
存储 C语言 开发者
C 语言指针与内存管理
C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
395 11
|
传感器 人工智能 物联网
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发,以及面临的挑战和未来趋势,旨在帮助读者深入了解并掌握这些关键技术。
319 6
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
431 1
|
算法 编译器 C语言
【C语言】C++ 和 C 的优缺点是什么?
C 和 C++ 是两种强大的编程语言,各有其优缺点。C 语言以其高效性、底层控制和简洁性广泛应用于系统编程和嵌入式系统。C++ 在 C 语言的基础上引入了面向对象编程、模板编程和丰富的标准库,使其适合开发大型、复杂的软件系统。 在选择使用 C 还是 C++ 时,开发者需要根据项目的需求、语言的特性以及团队的技术栈来做出决策。无论是 C 语言还是 C++,了解其优缺点和适用场景能够帮助开发者在实际开发中做出更明智的选择,从而更好地应对挑战,实现项目目标。
445 0