当使用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
基本上,这意味着作为C++异常参数的errno几乎是无用的,因为在调用__errno_location(即errno的宏内容)之前调用了__cxa_allocate_exception,后者调用std::malloc并且不保存errno状态(至少根据我所理解的libstdc++中eh_alloc.cc的__cxa_allocate_exception源代码内容如此)。这意味着在内存分配失败的情况下,实际应该传递给异常对象的错误号被覆盖为std::malloc设置的错误号。无论如何,std::malloc甚至在成功退出的情况下也不能保证保存现有的errno状态 - 因此上述代码在一般情况下肯定是错误的。在Cygwin、x86上,使用g++ 4.5.3编译test()的代码没问题。
.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++ 代码由于在收集其构造函数参数之前为异常对象分配内存而导致无法正常工作,并且这在某种程度上是编译器的错误。我是正确的吗?还是我的想法基本上是错误的?
errnotostr()
是oserror
类的(静态)函数,基本上调用了strerror_r()
并返回结果的std::string
形式。我没有包含代码的那部分,因为它对示例不相关。 - modelnine