在动态内存分配(堆)中,“删除”运算符实际上如何在C ++的幕后工作? [英] How does the 'delete' operator actually work behind the scenes in C++ in dynamic memory allocation (heap)?

查看:69
本文介绍了在动态内存分配(堆)中,“删除”运算符实际上如何在C ++的幕后工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我不了解在C ++中如何在后台实际实现删除运算符。例如:

I am not getting how the "delete" operator is actually implemented behind the scenes in C++. For example:

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

删除a到底是什么?在后台执行?就像
一样,调用了任何默认析构函数还是什么?此外,由于 a 包含左右指针,因此对象 a-> left a->对也删除了吗?
核心计算机级别会发生什么?

What exactly does delete a; do behind the scenes? Like is there any default destructor called upon or what? Also, as a contains left and right pointers, is the object a->left and a->right also deleted? What happens at the core machine level?

推荐答案

命名



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


4 如果类没有用户声明的析构函数,则
析构函数隐式声明为默认值
(8.4)。隐式声明的
析构函数是该类的内联公共成员。

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 如果析构函数是不是用户提供的
,并且如果:

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

(6.1) —析构函数不是虚拟的,

(6.1) — the destructor is not virtual,

(6.2)-该类中的所有直接基类都具有
个琐碎的析构函数,而

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

(6.3) —对于其类
中所有属于类类型(或其数组)的所有非静态数据成员,每个此类都有一个
琐碎的析构函数。

(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.

否则,析构函数并不简单。

Otherwise, the destructor is non-trivial.

7 默认使用但未定义为删除的
的析构函数在被odr-使用
(3.2)或在第一次声明后显式默认
时被隐式定义。

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 隐式调用析构函数

(12.1 ) —对于在程序终止(3.6.4)时具有静态存储
持续时间(3.7.1)的构造对象,

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

(12.2) —对于在线程出口具有线程存储
持续时间(3.7.2)的构造对象,

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

(12.3) —对于具有自动存储
持续时间(3.7.3)的构造对象,当在其中创建对象的块退出
(6.7)时,

(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) —对于构造的临时对象,当其
的生存期结束时(4.4,12.2)。在每种情况下,调用
的上下文都是对象构造的上下文。 析构函数
也通过对
使用delete-expression(5.3.5)隐式调用,该对象由new-expression(5.3.4)分配;调用的
上下文是delete-expression。
[注意:类类型的数组
包含多个子对象,每个子对象都调用
析构函数。 —结束说明]还可以显式调用析构函数
。如果析构函数被调用或作为5.3.4、12.6.2和15.1中指定的
调用,则可能会调用它。如果从调用的上下文中删除了可能被调用的
析构函数或无法访问
,则程序的格式不正确。

(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 ++中,如果 struct union 没有用户声明的析构函数,那么编译器将始终隐式声明一个琐碎的析构函数。也就是说,尽管您尚未为 Node 类声明一个析构函数,但C ++标准有义务为编译器为您声明一个琐碎的变量。

In C++ if a class, struct or union doesn't have a user declared destructor, then the compiler will always implicitly declare one trivial destructor for it. That is, although you haven't declared a destructor for your Node class the compiler is obliged by the C++ standard to declare a trivial one for you.

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

This implicitly declared trivial destructor will be defined once upon your class is odr-used. That is, when any expression anywhere in the program takes the address of or binds a reference directly to an object of your class.

调用 delete 在先前分配有 new Node 对象上将唤起其隐式定义的析构函数,并且通过 new 为该对象分配的堆存储将被回收(即释放)。

Calling delete upon a Node object that was previously allocated with new will evoke its implicitly defined destructor and the heap storage allocated for that object by new will be reclaimed (i.e., freed).

因为隐式声明了析构函数这很简单,成员指针 left right 指向的任何存储都不会被触及。这意味着,如果您分配了仅由a的 left right 成员指针指向的任何内存 Node 对象。在对此对象调用delete之后,由成员指针 left right 指向的内存将是孤立的(即,

Since the implicit declared destructor is trivial, any storage pointed by member pointers left and right will not be touched at all. Meaning that, if you've allocated any memory that is being pointed only by the left and right member pointers of a Node object. After calling delete upon this object the memory that was being pointed by member pointers left and right will be orphan (i.e., you'll have a memory leak).

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

What happens at core machine level may vary from vendor to vendor, from operating system to operating system and from machine to machine as the core behaviour of a delete expression is not specified by the C++ standard. Any compiler vendor can do anything it feels like it to (e.g., optimizations), as long as the observable behaviour conforms with the C++ standard.

尽管如此,供应商或多或少在做类似的事情。例如,让我们考虑以下代码段:

More or less though, vendors are doing similar things. For example lets take into account the following piece of code:

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

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

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

The generated assembly code for the above piece of code for GCC version 6.2 compiler is:

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 对象由下面的代码片段表示:

In the assembly code generated for our simple example, the construction of the n object is represented by the code snippet below:

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

由于对象是微不足道的可构造对象编译器所做的就是通过调用隐式声明的全局运算符 new 为对象分配内存。

Since the object is trivially constructible the only thing the compiler does is allocation of memory for the object by calling the implicitly declared global operator new.

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

The destruction process of the object is represented by the code snippet below:

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

注意,销毁过程的执行顺序与建造过程中的步骤相反。最后,调用隐式声明的全局运算符 delete 释放先前分配的内存。

Notice, that the destruction process is done in reverse order with respect the steps taken in the construction process. At the end the implicitly declared global operator delete is called to free the previously allocated memory.

构造函数或析构函数都将被调用,因为在我们这种情况下,如果不调用它们,程序的可观察行为就不会改变。

In our example neither a constructor nor a destructor is called since in our case the observable behaviour of the program doesn't change if they are not called.

现在,正确的问题是在哪里运算符删除是吗?

Now the rightfull question is where in heck operator delete is?

为了保留该答案的命名法,我们引用<$中的C ++标准c $ c>§3.7.4动态存储持续时间[basic.stc.dynamic] \p1,p2 重点矿):

In order to preserve the nomenclaturstic style of this answer lets quote the C++ standard from §3.7.4 Dynamic storage duration [basic.stc.dynamic]\p1, p2 (Emphasis Mine):


1 可以在程序
执行期间动态创建对象(1.9),使用new表达式(5.3.4) ,并使用
delete-expressions(5.3.5)销毁。 C ++实现通过全局分配功能
运算符new和operator new []以及全局释放功能
运算符delete和operator delete []提供对
的访问以及对动态存储的管理。
[注意:18.6.2.3中描述的非分配
表格不执行分配或取消分配。
—尾注]

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 该库为全局
分配和释放功能提供了默认定义
。某些全局分配和
解除分配功能是可替换的(18.6.2)。 C ++程序
最多提供一个可替换分配或
释放函数的定义。任何此类函数定义都将替换库(17.5.4.6)中提供的
默认版本。 以下
分配和释放函数(18.6)在程序的每个翻译单元中在全局范围中隐式声明

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 运算符新[] 运算符删除运算符删除[] 。 [
注意:隐式声明不引入名称 std
std :: size_t std :: align_val_t
库用来声明这些名称的任何其他名称。因此,一个新表达式,删除表达式
或引用了其中一个函数但没有
(包括头文件)的函数调用格式正确。但是,引用std
或std :: size_t或std :: align_val_t格式不正确,除非通过包含适当的标头声明了名称
。 —尾注]
也可以声明分配和/或释放函数,并为任何类(12.5)定义

These implicit declarations introduce only the function names operator new, operator new[], operator delete, and operator delete[]. [ Note: The implicit declarations do not introduce the names std, std::size_t, std::align_val_t, or any other names that the library uses to declare these names. Thus, a new-expression, delete-expression or function call that refers to one of these functions without including the header is well-formed. However, referring to std or std::size_t or std::align_val_t is ill-formed unless the name has been declared by including the appropriate header. — end note ] Allocation and/or deallocation functions may also be declared and defined for any class (12.5).

答案是delete运算符在程序的每个翻译单元中的全局作用域中隐式声明。

The answer is the delete operator is implicitly declared in global scope in each translation unit of your program.

这篇关于在动态内存分配(堆)中,“删除”运算符实际上如何在C ++的幕后工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆