我相信这是 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?
当 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?