简体   繁体   English

当构造函数抛出异常并使用自定义new时,C++如何释放内存

[英]How does C++ free the memory when a constructor throws an exception and a custom new is used

I see the following constructs:我看到以下结构:

  • new X will free the memory if X constructor throws.如果X构造函数抛出, new X将释放内存。

  • operator new() can be overloaded. operator new()可以重载。

The canonical definition of an operator new overload is void *operator new(size_t c, heap h) and the corresponding operator delete .运算符 new 重载的规范定义是void *operator new(size_t c, heap h)和相应的operator delete

The most common operator new overload is placement new, which is void *operator new(void *p) { return p; }最常见的 operator new 重载是placement new,即void *operator new(void *p) { return p; } void *operator new(void *p) { return p; }

You almost always cannot call delete on the pointer given to placement new .您几乎总是不能在给定new的指针上调用delete

This leads to a single question: How is memory cleaned up when X constructor throws and an overloaded new is used?这导致了一个问题:当X构造函数抛出并使用重载的new时,如何清理内存?

Fundamentally, if there is no delete operator which corresponds to the new operator, then nothing is done.从根本上说,如果没有对应于new操作符的delete操作符,那么什么都不做。 Nothing is done also in the case of placement new, because the corresponding placement delete operator is a no-op.在放置新的情况下也不做任何事情,因为相应的放置删除操作符是空操作。 The exception is not diverted: it continues its course, so the caller of the new has the opportunity (and responsibility) for freeing the memory allocated.异常没有转移:它继续它的进程,所以 new 的调用者有机会(和责任)释放分配的内存。

Placement new is called that because it is used to place the object in memory otherwise acquired;之所以称为 Placement new,是因为它用于将对象放置在以其他方式获取的内存中; since the memory was not acquired by the new operator, it would be unlikely that it could be released by the delete operator.由于内存不是由 new 操作符获取的,因此它不太可能被删除操作符释放。 In practice, the question is moot because (since C++03, at least) it is not permitted to replace the placement new operator (which has prototype operator new(size_t, void*) or delete ( operator delete(void*, void*) ). The supplied placement new operator returns its second argument, and the supplied placement delete operator is a no-op.实际上,这个问题没有实际意义,因为(至少从 C++03 开始​​)不允许替换放置 new 运算符(它具有原型operator new(size_t, void*)或 delete ( operator delete(void*, void*) ). 提供的placement new 操作符返回它的第二个参数,并且提供的placement delete 操作符是一个空操作。

Other new and delete operators may be replaced, either globally or for a specific class.其他newdelete运算符可能会被替换,无论是全局的还是特定的类。 If a custom new operator is called, and the constructor throws an exception, and there is a corresponding delete operator, then that delete operator will be called to clean up before the exception is propagated.如果调用自定义的new运算符,并且构造函数抛出异常,并且存在相应的delete运算符,则将在传播异常之前调用该 delete 运算符进行清理。 However, it is not an error if there is no corresponding delete operator.但是,如果没有相应的delete运算符,则不会出错。

First, an example:先举个例子:

#include <cstddef>
#include <iostream>

struct S
{
    S(int i) { if(i > 42) throw "up"; }

    static void* operator new(std::size_t s, int i, double d, char c)
    {
        std::cout << "allocated with arguments: "
                  <<i<<", "<<d<<", "<<c<<std::endl;
        return new char[s];
    }

    static void operator delete(void* p, int i, double d, char c)
    {
        std::cout << "deallocated with arguments: "
                  <<i<<", "<<d<<", "<<c<<std::endl;
        delete[] (char*)p;
    }

    static void operator delete(void* p)
    {
        std::cout << "deallocated w/o arguments"<<std::endl;
        delete[] (char*)p;
    }
};

int main()
{
    auto p0 = new(1, 2.0, '3') S(42);

    S* p1 = nullptr;
    try
    {
        p1 = new(4, 5.0, '6') S(43);
    }catch(const char* msg)
    {
        std::cout << "exception: "<<msg<<std::endl;
    }

    delete p1;
    delete p0;
}

Output:输出:

allocated with arguments: 1, 2, 3
allocated with arguments: 4, 5, 6
deallocated with arguments: 4, 5, 6
exception: up
deallocated w/o arguments

The canonical definition of an operator new overload is void *operator new(std::size_t, heap h)运算符 new 重载的规范定义是void *operator new(std::size_t, heap h)

I don't see how this is canonical, since it's not allowed: Ok, now it's a valid placement-form of new :) 我不明白这是如何规范的,因为它是不允许的: 好的,现在它是new的有效放置形式:)

[basic.stc.dynamic.allocation]/1 [basic.stc.dynamic.allocation]/1

An allocation function shall be a class member function or a global function;分配函数应为类成员函数或全局函数; a program is ill-formed if an allocation function is declared in a namespace scope other than global scope or declared static in global scope.如果分配函数在全局范围以外的命名空间范围中声明或在全局范围内声明为静态,则程序格式错误。 The return type shall be void* .返回类型应为void* The first parameter shall have type std::size_t .第一个参数的类型应为std::size_t The first parameter shall not have an associated default argument.第一个参数不应有关联的默认参数。 The value of the first parameter shall be interpreted as the requested size of the allocation.第一个参数的值应解释为请求的分配大小。

[emphasis mine] [强调我的]

You can overload the allocation function to be called for the placement-form of new , see [expr.new] (it's not explicitly allowed in [basic.stc.dynamic.allocation] for non-template functions, but also not forbidden).您可以重载要为new的放置形式调用的分配函数,请参阅 [expr.new] (在 [basic.stc.dynamic.allocation] 中未明确允许非模板函数,但也不禁止)。 The placement given in new(placement) is generalized here to an expression-list . new(placement)给出的放置在这里被推广到一个expression-list Each expression in the expression-list for a specific new-expression is passed as an additional arguments to the allocation function.特定表达式表达式列表中的每个表达式都作为附加参数传递给分配函数。 If the deallocation function is called (eg because the called ctor throws an exception), the same arguments plus a leading void* (the return value of the allocation function) are passed to the deallocation function.如果调用释放函数(例如,因为被调用的构造函数抛出异常),则将相同的参数加上前导void* (分配函数的返回值)传递给释放函数。

[expr.new]/18 states: [expr.new]/18 指出:

If any part of the object initialization described above terminates by throwing an exception, storage has been obtained for the object, and a suitable deallocation function can be found, the deallocation function is called to free the memory in which the object was being constructed, after which the exception continues to propagate in the context of the new-expression .如果上面描述的对象初始化的任何部分因抛出异常而终止,已经为对象获得了存储空间,并且可以找到合适的释放函数,则调用释放函数释放正在构造对象的内存,之后异常继续在new-expression的上下文中传播。 If no unambiguous matching deallocation function can be found, propagating the exception does not cause the object's memory to be freed.如果找不到明确匹配的释放函数,则传播异常不会导致对象的内存被释放。 [ Note: This is appropriate when the called allocation function does not allocate memory; [注意:这适用于被调用的分配函数不分配内存的情况; otherwise, it is likely to result in a memory leak.否则很可能导致内存泄漏。 end note ] 尾注]

and /21和 /21

If a new-expression calls a deallocation function, it passes the value returned from the allocation function call as the first argument of type void* .如果new 表达式调用释放函数,它将从分配函数调用返回的值作为void*类型的第一个参数传递。 If a placement deallocation function is called, it is passed the same additional arguments as were passed to the placement allocation function, that is, the same arguments as those specified with the new-placement syntax.如果调用放置解除分配函数,则传递与传递给放置分配函数相同的附加参数,即与使用 new-placement 语法指定的参数相同的参数。

and /20和 /20

A declaration of a placement deallocation function matches the declaration of a placement allocation function if it has the same number of parameters and, after parameter transformations, all parameter types except the first are identical.如果放置释放函数的声明具有相同数量的参数,并且在参数转换后,除第一个参数类型之外的所有参数类型都相同,则放置释放函数的声明与放置分配函数的声明匹配。 Any non-placement deallocation function matches a non-placement allocation function.任何非布局解除分配函数都与非布局分配函数相匹配。 If the lookup finds a single matching deallocation function, that function will be called;如果查找找到单个匹配的解除分配函数,则将调用该函数; otherwise, no deallocation function will be called.否则,不会调用任何释放函数。 If the lookup finds the two-parameter form of a usual deallocation function and that function, considered as a placement deallocation function, would have been selected as a match for the allocation function, the program is ill-formed.如果查找找到了一个普通释放函数的双参数形式,并且该函数被视为放置释放函数,将被选为分配函数的匹配项,则程序格式错误。 [ Example: [示例:

 struct S { // Placement allocation function: static void* operator new(std::size_t, std::size_t); // Usual (non-placement) deallocation function: static void operator delete(void*, std::size_t); }; S* p = new (0) S; // ill-formed: non-placement deallocation function matches // placement allocation function

end example ] 结束示例]

Going back to [basic.stc.dynamic.deallocation]:回到[basic.stc.dynamic.deallocation]:

1 Deallocation functions shall be class member functions or global functions; 1 释放函数应为类成员函数或全局函数; a program is ill-formed if deallocation functions are declared in a namespace scope other than global scope or declared static in global scope.如果在全局范围以外的命名空间范围内声明释放函数或在全局范围内声明为静态,则程序格式错误。

2 Each deallocation function shall return void and its first parameter shall be void* . 2 每个解除分配函数应返回void且其第一个参数应为void* A deallocation function can have more than one parameter.一个释放函数可以有多个参数。

When a constructor throws an exception the matching delete is called.当构造函数抛出异常时,将调用匹配的删除。 The destructor is not called for the class that threw but any components of the class that have successfully had their constructors called will have their destructors called.不会为抛出的类调用析构函数,但成功调用其构造函数的类的任何组件都将调用其析构函数。

'placement new' is not an overloaded version of new, but one of the variants of operator new , and also one that cannot be overloaded. 'placement new' 不是 new 的重载版本,而是 operator new 的一种变体,也是一种不能重载的变体。

See can see the list of new operators here along with a description on how overloading them works. See 可以在此处查看新运算符的列表以及有关重载它们如何工作的描述。

If a constructor throws an exception when using placement new the compiler knows what new operator was used and call placement delete.如果构造函数在使用placement new 时抛出异常,编译器就会知道使用了什么new 运算符并调用placement delete。

When the construction of the object being constructed as a part of the new-expression fails, a corresponding deallocation function -- if there is one -- will be called.当作为new 表达式的一部分构造的对象的构造失败时,将调用相应的解除分配函数(如果有的话)。 For instance例如

new X;

will use the following pair of allocation/deallocation functions.将使用以下一对分配/解除分配函数。

void * operator new(std::size_t);
void operator delete(void *);

Similarly, for a placement new of the form同样,对于表单的新展示位置

new(&a) X;

the placement versions of operator new and operator delete function will be used.将使用operator newoperator delete函数的放置版本。

void * operator new(std::size_t, void *);
void operator delete(void *, void *);

Note that the last function intentionally performs no action.请注意,最后一个函数故意不执行任何操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM