在 C++ 的动态内存分配(堆)中,“删除”运算符实际上是如何在幕后工作的?

2023-12-31

我不明白“删除”运算符在 C++ 中是如何在幕后实际实现的。例如:

class Node{
  int i;
  Node *left,*right;
};

int main()    {
  Node* a = new Node; // somehow the object 'a' is initialised with its data members
  delete a;
}

到底是做什么的delete a;做幕后?就像是 有没有调用任何默认析构函数或者什么?另外,如a包含左指针和右指针,是对象a->left and a->right也删除了? 发生了什么核心机级别?


命名法

根据C++标准§12.4 析构函数/p4,p6,p7,p8,p12 [class.dtor] (强调我的):

4 If a class has no user-declared destructor, a destructor is implicitly declared as defaulted (8.4). An implicitly declared destructor is an inline public member of its class.

6 A destructor is trivial if it is not user-provided and if:

(6.1) — the destructor is not virtual,

(6.2) — all of the direct base classes of its class have trivial destructors, and

(6.3) — for all of the non-static data members of its class that are of class type (or array thereof), each such class has a trivial destructor.

否则,析构函数就不重要了。

7 A destructor that is defaulted and not defined as deleted is implicitly defined when it is odr-used (3.2) or when it is explicitly defaulted after its first declaration.

12 A destructor is invoked implicitly

(12.1) — for a constructed object with static storage duration (3.7.1) at program termination (3.6.4),

(12.2) — for a constructed object with thread storage duration (3.7.2) at thread exit,

(12.3) — for a constructed object with automatic storage duration (3.7.3) when the block in which an object is created exits (6.7),

(12.4) — for a constructed temporary object when its lifetime ends (4.4, 12.2). In each case, the context of the invocation is the context of the construction of the object. A destructor is also invoked implicitly through use of a delete-expression (5.3.5) for a constructed object allocated by a new-expression (5.3.4); the context of the invocation is the delete-expression. [ Note: An array of class type contains several subobjects for each of which the destructor is invoked. — end note ] A destructor can also be invoked explicitly. A destructor is potentially invoked if it is invoked or as specified in 5.3.4, 12.6.2, and 15.1. A program is ill-formed if a destructor that is potentially invoked is deleted or not accessible from the context of the invocation.

DR;TL

在 C++ 中如果class, struct or union没有用户声明的析构函数,那么编译器将始终为其隐式声明一个简单的析构函数。也就是说,虽然您还没有为您的析构函数声明Node根据 C++ 标准,编译器有义务为您声明一个简单的类。

这个隐式声明的简单析构函数将在您的类创建后定义odr-used。也就是说,当程序中任何位置的任何表达式获取类的对象的地址或直接将引用绑定到类的对象时。

Calling delete upon a Node先前分配的对象new将调用其隐式定义的析构函数和为该对象分配的堆存储new将被回收(即释放)。

由于隐式声明的析构函数很简单,因此成员指针指向的任何存储left and right根本不会被触动。这意味着,如果您分配了仅由left and righta 的成员指针Node目的。对该对象调用删除后,成员指针所指向的内存left and right将是孤儿(即,您将发生内存泄漏)。

核心机器级别发生了什么

核心机器级别发生的情况可能因供应商、操作系统和机器而异,因为 C++ 标准未指定删除表达式的核心行为。只要可观察的行为符合 C++ 标准,任何编译器供应商都可以做任何想做的事情(例如,优化)。

不过,供应商或多或少也在做类似的事情。例如,让我们考虑以下代码:

class Node {
  int i;
  Node *left, *right;
};

int main() {
  Node *n = new Node;
  delete n;
}

上述代码为 GCC 6.2 版编译器生成的汇编代码为:

main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     edi, 24
        call    operator new(unsigned long)
        mov     QWORD PTR [rbp-8], rax
        mov     rax, QWORD PTR [rbp-8]
        mov     esi, 24
        mov     rdi, rax
        call    operator delete(void*, unsigned long)
        mov     eax, 0
        leave
        ret

在为我们的简单示例生成的汇编代码中,构造了n对象由下面的代码片段表示:

sub     rsp, 16
mov     edi, 24
call    operator new(unsigned long)
mov     QWORD PTR [rbp-8], rax

由于该对象是可简单构造的,因此编译器所做的唯一事情就是通过调用隐式声明的全局运算符为该对象分配内存new.

对象的销毁过程由下面的代码片段表示:

rax, QWORD PTR [rbp-8]
mov     esi, 24
mov     rdi, rax
call    operator delete(void*, unsigned long)

请注意,销毁过程是按照与构造过程中所采取的步骤相反的顺序完成的。最后隐式声明的全局运算符delete被调用以释放先前分配的内存。

在我们的示例中,构造函数和析构函数都没有被调用,因为在我们的例子中,如果不调用它们,程序的可观察行为不会改变。

现在正确的问题是到底在哪里operator delete is?

为了保留这个答案的命名风格,让我们引用 C++ 标准§3.7.4 Dynamic storage duration [basic.stc.dynamic]\p1, p2 (重点我的):

1 Objects can be created dynamically during program execution (1.9), using new-expressions (5.3.4), and destroyed using delete-expressions (5.3.5). A C++ implementation provides access to, and management of, dynamic storage via the global allocation functions operator new and operator new[] and the global deallocation functions operator delete and operator delete[]. [ Note: The non-allocating forms described in 18.6.2.3 do not perform allocation or deallocation. — end note ]

2 The library provides default definitions for the global allocation and deallocation functions. Some global allocation and deallocation functions are replaceable (18.6.2). A C++ program shall provide at most one definition of a replaceable allocation or deallocation function. Any such function definition replaces the default version provided in the library (17.5.4.6). The following allocation and deallocation functions (18.6) are implicitly declared in global scope in each translation unit of a program.

void* operator new(std::size_t); 
void* operator new(std::size_t, std::align_val_t); void operator delete(void*) noexcept; 
void operator delete(void*, std::size_t) noexcept;
void operator delete(void*, std::align_val_t) noexcept;
void operator delete(void*, std::size_t, std::align_val_t) noexcept;
void* operator new[](std::size_t);
void* operator new[](std::size_t, std::align_val_t);
void operator delete[](void*) noexcept;
void operator delete[](void*, std::size_t) noexcept;
void operator delete[](void*, std::align_val_t) noexcept;
void operator delete[](void*, std::size_t, std::align_val_t) noexcept;

这些隐式声明仅引入函数名称运算符new, operator new[], operator delete, and operator delete[]。 [ 注意:隐式声明不引入名称std, std::size_t, std::align_val_t,或图书馆认为的任何其他名称 用于声明这些名称。因此,新表达式、删除表达式 或引用这些函数之一的函数调用,而无需 包括标题格式良好。但是,参考标准 或 std::size_t 或 std::align_val_t 格式错误,除非名称具有 通过包含适当的标头来声明。 ——尾注] 分配和/或释放函数也可以被声明和 为任何类定义(12.5)。

答案是删除运算符在程序的每个翻译单元的全局范围内隐式声明。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 C++ 的动态内存分配(堆)中,“删除”运算符实际上是如何在幕后工作的? 的相关文章

  • Web 客户端和 Expect100Continue

    使用 WebClient C NET 时设置 Expect100Continue 的最佳方法是什么 我有下面的代码 我仍然在标题中看到 100 continue 愚蠢的 apache 仍然抱怨 505 错误 string url http
  • 在结构中使用 typedef 枚举并避免类型混合警告

    我正在使用 C99 我的编译器是 IAR Embedded workbench 但我认为这个问题对于其他一些编译器也有效 我有一个 typedef 枚举 其中包含一些项目 并且我向该新类型的结构添加了一个元素 typedef enum fo
  • 嵌套接口:将 IDictionary> 转换为 IDictionary>?

    我认为投射一个相当简单IDictionary
  • 用于登录 .NET 的堆栈跟踪

    我编写了一个 logger exceptionfactory 模块 它使用 System Diagnostics StackTrace 从调用方法及其声明类型中获取属性 但我注意到 如果我在 Visual Studio 之外以发布模式运行代
  • OleDbDataAdapter 未填充所有行

    嘿 我正在使用 DataAdapter 读取 Excel 文件并用该数据填充数据表 这是我的查询和连接字符串 private string Query SELECT FROM Sheet1 private string ConnectStr
  • 堆栈溢出:堆栈空间中重复的临时分配?

    struct MemBlock char mem 1024 MemBlock operator const MemBlock b const return MemBlock global void foo int step 0 if ste
  • 在 ASP.NET 5 中使用 DI 调用构造函数时解决依赖关系

    Web 上似乎充斥着如何在 ASP NET 5 中使用 DI 的示例 但没有一个示例显示如何调用构造函数并解决依赖关系 以下只是众多案例之一 http social technet microsoft com wiki contents a
  • 使用 WebClient 时出现 System.Net.WebException:无法创建 SSL/TLS 安全通道

    当我执行以下代码时 System Net ServicePointManager ServerCertificateValidationCallback sender certificate chain errors gt return t
  • 创建链表而不将节点声明为指针

    我已经在谷歌和一些教科书上搜索了很长一段时间 我似乎无法理解为什么在构建链表时 节点需要是指针 例如 如果我有一个节点定义为 typedef struct Node int value struct Node next Node 为什么为了
  • 将多个表映射到实体框架中的单个实体类

    我正在开发一个旧数据库 该数据库有 2 个具有 1 1 关系的表 目前 我为每个定义的表定义了一种类型 1Test 1Result 我想将这些特定的表合并到一个类中 当前的类型如下所示 public class Result public
  • SolrNet连接说明

    为什么 SolrNet 连接的容器保持静态 这是一个非常大的错误 因为当我们在应用程序中向应用程序发送异步请求时 SolrNet 会表现异常 在 SolrNet 中如何避免这个问题 class P static void M string
  • 使用 x509 证书签署 json 文档或字符串

    如何使用 x509 证书签署 json 文档或字符串 public static void fund string filePath C Users VIKAS Desktop Data xml Read the file XmlDocum
  • 如何使用 C# / .Net 将文件列表从 AWS S3 下载到我的设备?

    我希望下载存储在 S3 中的多个图像 但目前如果我只能下载一个就足够了 我有对象路径的信息 当我运行以下代码时 出现此错误 遇到错误 消息 读取对象时 访问被拒绝 我首先做一个亚马逊S3客户端基于我的密钥和访问配置的对象连接到服务器 然后创
  • 如何从两个不同的项目中获取文件夹的相对路径

    我有两个项目和一个共享库 用于从此文件夹加载图像 C MainProject Project1 Images 项目1的文件夹 C MainProject Project1 Files Bin x86 Debug 其中有project1 ex
  • 将控制台重定向到 .NET 程序中的字符串

    如何重定向写入控制台的任何内容以写入字符串 对于您自己的流程 Console SetOut http msdn microsoft com en us library system console setout aspx并将其重定向到构建在
  • C# 成员变量继承

    我对 C 有点陌生 但我在编程方面有相当广泛的背景 我想做的事情 为游戏定义不同的 MapTiles 我已经像这样定义了 MapTile 基类 public class MapTile public Texture2D texture pu
  • 基于 OpenCV 边缘的物体检测 C++

    我有一个应用程序 我必须检测场景中某些项目的存在 这些项目可以旋转并稍微缩放 更大或更小 我尝试过使用关键点检测器 但它们不够快且不够准确 因此 我决定首先使用 Canny 或更快的边缘检测算法 检测模板和搜索区域中的边缘 然后匹配边缘以查
  • 混合 ExecutionContext.SuppressFlow 和任务时 AsyncLocal.Value 出现意外值

    在应用程序中 由于 AsyncLocal 的错误 意外值 我遇到了奇怪的行为 尽管我抑制了执行上下文的流程 但 AsyncLocal Value 属性有时不会在新生成的任务的执行范围内重置 下面我创建了一个最小的可重现示例来演示该问题 pr
  • 如何防止用户控件表单在 C# 中处理键盘输入(箭头键)

    我的用户控件包含其他可以选择的控件 我想实现使用箭头键导航子控件的方法 问题是家长控制拦截箭头键并使用它来滚动其视图什么是我想避免的事情 我想自己解决控制内容的导航问题 我如何控制由箭头键引起的标准行为 提前致谢 MTH 这通常是通过重写
  • 对来自流读取器的过滤数据执行小计

    编辑问题未得到解答 我有一个基于 1 个标准的过滤输出 前 3 个数字是 110 210 或 310 给出 3 个不同的组 从流阅读器控制台 问题已编辑 因为第一个答案是我给出的具体示例的字面解决方案 我使用的实际字符串长度为 450 个

随机推荐