如果delete[] p 失败会发生什么?

发布于 2024-11-17 10:20:38 字数 302 浏览 5 评论 0原文

假设我有一个指向动态分配的包含 10 个元素的数组的指针:

T* p = new T[10];

稍后,我想释放该数组:

delete[] p;

如果 T 析构函数之一抛出异常,会发生什么情况?其他元素仍然会被破坏吗?内存会被释放吗?异常会被传播,还是程序会被终止?

同样,当 std::vector 被销毁并且 T 析构函数之一抛出时会发生什么?

Suppose I have a pointer to a dynamically allocated array of 10 elements:

T* p = new T[10];

Later, I want to release that array:

delete[] p;

What happens if one of the T destructors throws an exception? Do the other elements still get destructed? Will the memory be released? Will the exception be propagated, or will program execution be terminated?

Similarly, what happens when a std::vector<T> is destroyed and one of the T destructors throws?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

羁绊已千年 2024-11-24 10:20:38

我看不到它在标准中明确调用:

只是它们将以与创建相反的顺序调用

5.3.5 删除 [expr.delete]

6 如果删除表达式的操作数的值不是空指针值,则删除表达式将为要删除的对象或数组元素调用析构函数(如果有)。对于数组,元素将按地址递减的顺序销毁(即按照其构造函数完成的相反顺序;请参阅 12.6.2)。

即使抛出异常,内存释放也会完成:

7 如果删除表达式的操作数的值不是空指针值,则删除表达式将调用释放函数 (3.7.4.2)。否则,未指定是否会调用释放函数。 [ 注意:无论对象或数组的某些元素的析构函数是否抛出异常,都会调用释放函数。 ——尾注]

我在G++中尝试了以下代码,它表明在异常之后不再调用析构函数:

#include <iostream>
int id = 0;
class X
{
    public:
         X() {   me = id++; std::cout << "C: Start" << me << "\n";}
        ~X() {   std::cout << "C: Done " << me << "\n";
                 if (me == 5) {throw int(1);}
             }
    private:
        int me;
};

int main()
{
    try
    {
        X       data[10];
    }
    catch(...)
    {
        std::cout << "Finished\n";
    }
}

执行:

> g++ de.cpp
> ./a.out
C: Start0
C: Start1
C: Start2
C: Start3
C: Start4
C: Start5
C: Start6
C: Start7
C: Start8
C: Start9
C: Done 9
C: Done 8
C: Done 7
C: Done 6
C: Done 5
Finished

这一切都导致了这个(非常旧的答案):
从析构函数中抛出异常

I can not see it explicitly called out in the standard:

Just that they will be called in reverse order of creation

5.3.5 Delete [expr.delete]

6 If the value of the operand of the delete-expression is not a null pointer value, the delete-expression will invoke the destructor (if any) for the object or the elements of the array being deleted. In the case of an array, the elements will be destroyed in order of decreasing address (that is, in reverse order of the completion of their constructor; see 12.6.2).

And that the memory deallocation will be done even if the exception is thrown:

7 If the value of the operand of the delete-expression is not a null pointer value, the delete-expression will call a deallocation function (3.7.4.2). Otherwise, it is unspecified whether the deallocation function will be called. [ Note: The deallocation function is called regardless of whether the destructor for the object or some element of the array throws an exception. — end note ]

I tried the following code in G++ and it shows that that no more destructors get called after the exception:

#include <iostream>
int id = 0;
class X
{
    public:
         X() {   me = id++; std::cout << "C: Start" << me << "\n";}
        ~X() {   std::cout << "C: Done " << me << "\n";
                 if (me == 5) {throw int(1);}
             }
    private:
        int me;
};

int main()
{
    try
    {
        X       data[10];
    }
    catch(...)
    {
        std::cout << "Finished\n";
    }
}

Execute:

> g++ de.cpp
> ./a.out
C: Start0
C: Start1
C: Start2
C: Start3
C: Start4
C: Start5
C: Start6
C: Start7
C: Start8
C: Start9
C: Done 9
C: Done 8
C: Done 7
C: Done 6
C: Done 5
Finished

Which all leads back to this (very old answer):
throwing exceptions out of a destructor

如日中天 2024-11-24 10:20:38

5.3.5.7 如果操作数的值
删除表达式不为空
指针值,删除表达式
将调用释放函数
(3.7.3.2)。否则,未指定
释放函数是否会
称为。 [注:解除分配
函数被调用,无论
是否为对象的析构函数
或者数组的某些元素抛出一个
例外。 ——尾注]

找不到有关析构函数的任何内容,除了

对于数组,元素将是
按照地址递减的顺序销毁(即按照其构造函数完成的相反顺序;请参阅 12.6.2)。

我猜想抛出后不会再调用析构函数,但我不确定。

5.3.5.7 If the value of the operand of the
delete-expression is not a null
pointer value, the delete-expression
will call a deallocation function
(3.7.3.2). Otherwise, it is unspecified
whether the deallocation function will
be called. [ Note: The deallocation
function is called regardless of
whether the destructor for the object
or some element of the array throws an
exception. — end note ]

Couldn't find anything about destructors except for

In the case of an array, the elements will be
destroyed in order of decreasing address (that is, in reverse order of the completion of their constructor; see 12.6.2).

I guess that after throwing no more destructors are called, but I'm not sure.

痴者 2024-11-24 10:20:38

永远不要那样做。如果已经存在活动异常,则将调用 std::terminate“砰,你死定了”。你的析构函数必须。不是。扔。抵抗。


编辑:标准 (14882 2003) 的相关部分,15.2 构造函数和析构函数 [ except.dtor]

15.2.3为在从 try 块到 a 的路径上构造的自动对象调用析构函数的过程
throw 表达式称为“堆栈展开”。 [注意:如果在堆栈展开期间调用的析构函数因异常退出,则调用终止 (15.5.1)。所以析构函数通常应该捕获异常并且
不要让它们从析构函数中传播出来。 ——尾注]


用于玩耍的测试用例(在现实生活中,抛出从 std::exception 派生的东西,永远不要抛出 int 或其他东西!):

    #include <iostream>
    int main() {
        struct Foo {
            ~Foo() {
                throw 0; // ... fore, std::terminate is called.
            }
        };

        try {
            Foo f;
            throw 0; // First one, will be the active exception once Foo::~Foo()
                     // is executed, there- ...
        } catch (int) {
            std::cout << "caught something" << std::endl;
        }
    }

Never do that. If there is already an active exception, std::terminate will be called: "Bang, you're dead". Your destructor must. Not. Throw. Resist.


edit: Relevant section from the Standard (14882 2003), 15.2 Constructors and Destructors [except.dtor] :

15.2.3 The process of calling destructors for automatic objects constructed on the path from a try block to a
throw-expression is called “stack unwinding.” [Note: If a destructor called during stack unwinding exits with an exception, terminate is called (15.5.1). So destructors should generally catch exceptions and
not let them propagate out
of the destructor. —end note]


Testcase for playing around (in real life, throw something that is derived from std::exception, never throw int or something else!):

    #include <iostream>
    int main() {
        struct Foo {
            ~Foo() {
                throw 0; // ... fore, std::terminate is called.
            }
        };

        try {
            Foo f;
            throw 0; // First one, will be the active exception once Foo::~Foo()
                     // is executed, there- ...
        } catch (int) {
            std::cout << "caught something" << std::endl;
        }
    }
惜醉颜 2024-11-24 10:20:38

要回答你的第二个问题,如果你使用 std::vector ,则不需要调用删除,你没有使用指针(我相信向量类是内部的,但这不取决于你来管理)。

To answer your second question, if you used std::vector instead, there wouldn't be any need for a call to delete, you're not using pointers (the vector class is internally I believe, but this is not up to you to manage).

全部不再 2024-11-24 10:20:38

好的,这是一些实验代码:

#include <cstddef>
#include <cstdlib>
#include <new>
#include <iostream>

void* operator new[](size_t size) throw (std::bad_alloc)
{
    std::cout << "allocating " << size << " bytes" << std::endl;
    return malloc(size);
}

void operator delete[](void* payload) throw ()
{
    std::cout << "releasing memory at " << payload << std::endl;
    free(payload);
}

struct X
{
    bool throw_during_destruction;

    ~X()
    {
        std::cout << "destructing " << (void*)this << std::endl;
        if (throw_during_destruction) throw 42;
    }
};

int main()
{
    X* p = new X[10]();
    p[5].throw_during_destruction = true;
    p[1].throw_during_destruction = true;
    delete[] p;
}

在 g++ 4.6.0 上运行代码给出了以下输出:

allocating 14 bytes
destructing 0x3e2475
destructing 0x3e2474
destructing 0x3e2473
destructing 0x3e2472
destructing 0x3e2471
terminate called after throwing an instance of 'int'

This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.

因此,一旦第一个析构函数抛出,std::terminate 就会立即被调用。其他元素不会被破坏,并且内存也不会被释放。谁能证实这一点吗?

Okay, here is some experimental code:

#include <cstddef>
#include <cstdlib>
#include <new>
#include <iostream>

void* operator new[](size_t size) throw (std::bad_alloc)
{
    std::cout << "allocating " << size << " bytes" << std::endl;
    return malloc(size);
}

void operator delete[](void* payload) throw ()
{
    std::cout << "releasing memory at " << payload << std::endl;
    free(payload);
}

struct X
{
    bool throw_during_destruction;

    ~X()
    {
        std::cout << "destructing " << (void*)this << std::endl;
        if (throw_during_destruction) throw 42;
    }
};

int main()
{
    X* p = new X[10]();
    p[5].throw_during_destruction = true;
    p[1].throw_during_destruction = true;
    delete[] p;
}

Running the code gave the following output on g++ 4.6.0:

allocating 14 bytes
destructing 0x3e2475
destructing 0x3e2474
destructing 0x3e2473
destructing 0x3e2472
destructing 0x3e2471
terminate called after throwing an instance of 'int'

This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.

So it would seem that std::terminate is called immediately as soon as the first destructor throws. The other elements are not destructed, and the memory is not released. Can anyone confirm this?

微凉徒眸意 2024-11-24 10:20:38

如果抛出异常,则抛出异常。未能销毁的对象显然没有被正确销毁,数组中剩余的对象也没有被正确销毁。

如果您使用向量,问题是相同的,只是不在您的代码中。 :-)

所以,抛出析构函数只是一个坏主意(tm)。


就像@Martin 下面所示的那样,一旦我们进入析构函数,抛出的对象就正式不存在了。其他人或许也会恢复记忆。

然而,它显然包含一些复杂的东西,没有被冲洗者正确冲洗。如果该对象以及数组中跟随它的其他对象包含一些互斥锁、打开的文件、数据库缓存或共享指针,并且这些对象都没有运行它们的析构函数,那么我们可能会遇到大麻烦。

此时调用 std::terminate 来使程序摆脱困境,似乎是您所希望的!

If an exception is thrown, it is thrown. The object that failed to destruct is obviously not properly destroyed, and neither are the ones remaining in the array.

If you use a vector, the problem is the same, just not in your code. :-)

So, throwing destructors is just a Bad Idea(tm).


Like @Martin shows below, the object that did thrown is formally non-existent as soon as we enter the destructor. The others might have their memory reclaimed as well.

However, it obviously contained some complicated things that were not properly of flusher flushed. If that object, and the others following it in the array, contained some mutex locks, open files, database caches, or shared_ptrs, and none of those had their destructors run, we are likely in BIG trouble.

Having std::terminate called at that point, to put the program out of its misery, seems like something you would wish for!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文