我相信这是 clang 中的一个错误,与构造函数抛出的放置新表达式有关

I believe this is a bug in clang related to a placement new-expression whose constructor throws

new(std::nothrow) C; 形式的 new-expression 时出现问题,其中 C 是构造函数抛出的 class 名称。请参阅下面的代码和 live example 使用 g++:

#include <iostream>

void* operator new(std::size_t size, const std::nothrow_t&) noexcept
{
    void* p;
    p = malloc(size);
    std::cout << "operator new(std::nothrow)" << '\n';
    return p;
}

void operator delete(void* p, const std::nothrow_t&) noexcept
{
    free(p);
    std::cout << "operator delete(std::nothrow)" << '\n';
    std::cout << p << '\n';
}

class T{};

class C {
    int i;
public:
    C(int i) : i{i} { std::cout << "C()" << '\n'; throw T{}; }
    ~C() { std::cout << "~C()" << '\n'; }
};

int main()
{
    C* c;
    try { c = new(std::nothrow) C(3); }
    catch (T&)
    {
        std::cout << "exception thrown in C(int) was caught" << '\n';
        std::cout << c << '\n';
    }
}

g++ 打印以下内容,似乎是正确的:

operator new(std::nothrow)
C()
operator delete(std::nothrow)
0x13f9c20
exception thrown in C(int) was caught
0

然而,如果您使用 clang,您将得到以下输出:

operator new(std::nothrow)
C()
exception thrown in C(int) was caught
0x7fffecdeed00

也就是说,clang 似乎是 而不是 调用程序中定义的 operator delete(void*, std::nothrow_t&),而是调用标准中的运算符图书馆。

奇怪的是,通过删除代码中定义的 operator delete(void*, std::nothrow_t&) 中的表达式 std::cout << p << '\n';clangs 似乎可以正确执行,打印:

operator new(std::nothrow)
C()
operator delete(std::nothrow)
exception thrown in C(int) was caught
0x7fffc0ffc000

编辑

回应@T.C 的评论。下面以及其他那些说上面的代码具有未定义行为的人,我在下面展示了另一个代码,它显示了编译器应该如何操作,以使用@T.C 提供的伪代码正确编译上面的代码片段。 here. See also this live example。需要注意的重要一点是,此代码 不使用 new-expression new(nothrow).

#include <iostream>

void * operator new(std::size_t n)
{
    void* p;
    try { p = malloc(n); }
    catch (std::bad_alloc&) { throw; }
    std::cout << "operator new" << '\n';
    return p;
}

void operator delete(void *p) noexcept
{
    free(p);
    std::cout << "operator delete" << '\n';
}

void* operator new(std::size_t size, const std::nothrow_t&) noexcept
{
    void* p = malloc(size);
    std::cout << "operator new(std::nothrow)" << '\n';
    return p;
}

void operator delete(void* p, const std::nothrow_t&) noexcept
{
    free(p);
    std::cout << "operator delete(std::nothrow)" << '\n';
    std::cout << p << '\n';
}

class T {};

class C {
    int i;
public:
    C(int i) : i{ i } { std::cout << "C()" << '\n'; throw T{}; }
    ~C() { std::cout << "~C()" << '\n'; }
};

int main()
{
    C *c;
    try
    {
        c = (C*)operator new(sizeof(C), std::nothrow);
        struct cleanup
        {
            void* p;
            bool active;
            ~cleanup() { if (active) operator delete(p, std::nothrow); }
            void dismiss() { active = false; }
        } guard = { (void*)c, true };
        new(c) C{1};
        guard.dismiss();
    }
    catch ( std::bad_alloc& ) { c = nullptr; }
    catch (T&)
    {
        std::cout << "exception thrown in C() was caught" << '\n';
        std::cout << c << '\n';
    }
}

g++ 为该代码打印以下内容:

operator new(std::nothrow)
C()
operator delete(std::nothrow)
0x10c3c20
exception thrown in C() was caught
0x10c3c20

令人惊讶的是,clang 似乎可以正确使用此代码,它没有使用新表达式 new(nothrow),这清楚地表明 clang 具有编译此 new-expression.

时出现错误

在我的系统 OS X 10.11.1 中,std::lib 提供的 operator delete 在 /usr/lib/libc++abi.dylib 中。在类 Unix 系统上,这个签名可以通过给它 "weak linkage" 来替换。当链接器看到两个相同的签名,并且其中之一的链接较弱时,它会更喜欢没有链接的那个。

我可以确认在我的系统上,operator delete(void*, std::nothrow_t const&) 与以下命令的链接较弱:

$ nm -gm /usr/lib/libc++abi.dylib |c++filt |grep nothrow_t
0000000000024406 (__TEXT,__text) weak external operator delete[](void*, std::nothrow_t const&)
00000000000243fc (__TEXT,__text) weak external operator delete(void*, std::nothrow_t const&)
00000000000243c0 (__TEXT,__text) weak external operator new[](unsigned long, std::nothrow_t const&)
000000000002437e (__TEXT,__text) weak external operator new(unsigned long, std::nothrow_t const&)

你能在你的系统上做类似的分析并报告结果吗?

更新

感谢 T.C. 下面关于如何复制症状的说明,在我看来这是一个 clang 编译器代码生成错误,在 3.7 中引入,仍然存在于 tip-of-主干,并且只能在 -O2 重现(不是 -O1 或更低,也不是 -O3)。

我认为错误报告是有序的,它应该对如何重现错误有很好的说明(除非你希望他们给予低优先级)。

PS

并设置C *c = nullptr;,这样他们就不会浪费时间追逐无关的UB。

第二次更新

我仍然无法使用 clang tip-of-trunk 在本地重现此内容。但是我可以在以下网站上看到它:

http://melpon.org/wandbox/permlink/5zIRyPJpq32LfU0t

我还没有对这种差异的解释。也许我的树干尖比他们的新?也许他们没有使用 libc++abi?