当使用 C++ 异常传输 errno 状态时,由 g++ (4.5.3) 生成的编译代码如下所示
#include <cerrno>
#include <stdexcept>
#include <string>
class oserror : public std::runtime_error {
private:
static std::string errnotostr(int errno_);
public:
explicit oserror(int errno_) :
std::runtime_error(errnotostr(errno_)) {
}
};
void test() {
throw oserror(errno);
}
相当意外(在 Linux,x86_64 上)
.type _Z4testv, @function
...
movl $16, %edi
call __cxa_allocate_exception
movq %rax, %rbx
movq %rbx, %r12
call __errno_location
movl (%rax), %eax
movl %eax, %esi
movq %r12, %rdi
call _ZN7oserrorC1Ei
这基本上意味着 errno 作为 C++ 异常的参数几乎没有用,因为在调用 __errno_location 之前调用了 __cxa_allocate_exception (这是 errno 的宏内容),前者调用 std::malloc 而不是保存 errno 状态(至少据我了解 libstdc++ 的 eh_alloc.cc 中 __cxa_allocate_exception 的来源)。
这意味着在内存分配失败的情况下,实际传递给异常对象的错误号会被 std::malloc 设置的错误号覆盖。无论如何,std::malloc 不保证保存现有的 errno 状态,即使在成功退出的情况下也是如此 - 所以上面的代码在一般情况下肯定会被破坏。
在 Cygwin,x86 上,为 test() 编译的代码(也使用 g++ 4.5.3)是可以的,但是:
.def __Z4testv; .scl 2; .type 32; .endef
...
call ___errno
movl (%eax), %esi
movl $8, (%esp)
call ___cxa_allocate_exception
movl %eax, %ebx
movl %ebx, %eax
movl %esi, 4(%esp)
movl %eax, (%esp)
call __ZN7oserrorC1Ei
这是否意味着要使库代码正确地将 errno 状态包装在异常中,我将始终必须使用扩展为类似的宏
int curerrno_ = errno;
throw oserror(curerrno_);
实际上,我似乎找不到 C++ 标准的相应部分,该部分说明了在异常情况下评估顺序的任何内容,但对我来说,x86_64(在 Linux 上)上的 g++ 生成代码似乎由于分配内存而被破坏在为其构造函数收集参数之前的异常对象,并且这在某种程度上是编译器错误。我是对的,还是我的一些根本错误的想法?