9

当使用 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++ 生成代码似乎由于分配内存而被破坏在为其构造函数收集参数之前的异常对象,并且这在某种程度上是编译器错误。我是对的,还是我的一些根本错误的想法?

4

2 回答 2

1

请注意,这__cxa_allocate_exception是在实际调用构造函数之前完成的。

  32:std_errno.cpp ****     throw oserror( errno );
 352 0007 BF100000      movl    $16, %edi
 ;;;; Exception space allocation:
 355 000c E8000000      call    __cxa_allocate_exception
 356 0011 4889C3        movq    %rax, %rbx
 ;;;; "errno" evaluation:
 357 0014 E8000000      call    __errno_location
 358 0019 8B00          movl    (%rax), %eax
 359 001b 89C6          movl    %eax, %esi
 360 001d 4889DF        movq    %rbx, %rdi
 ;;;; Constructor called here:
 362 0020 E8000000      call    _ZN7oserrorC1Ei

所以这是有道理的。__cxa_allocate_exception只是为异常分配空间,但不构造它(libc++abi 规范)。

当您的异常对象被构建时,errno就会被评估。

我举了你的例子并实施了errnotostr

//在 C++ (g++) 中使用 errno 作为异常参数的意外控制流(编译器错误?)

#include    <cerrno>
#include    <stdexcept>
#include    <string>

#include    <iostream>
#include    <cstring>
#include    <sstream>

class oserror : public std::runtime_error
{
private:
    static std::string errnotostr(int errno_)
    {
        std::stringstream   ss;

        ss << "[" << errno_ << "] " << std::strerror( errno_ );

        return  ss.str( );
    }

public:
    explicit oserror( int errno_ )
    :    std::runtime_error( errnotostr( errno_ ) )
    {
    }
};

void test( )
{
    throw oserror( errno );
}

int main( )
{
    try
    {
        std::cout << "Enter a value to errno: ";
        std::cin >> errno;

        std::cout << "Test with errno = " << errno << std::endl;
        test( );
    }
    catch ( oserror &o )
    {
        std::cout << "Exception caught: " << o.what( ) << std::endl;
        return  1;
    }

    return  0;
}

-O0然后我用and编译-O2,运行并得到相同的结果,一切都符合预期:

> ./std_errno
Enter a value to errno: 1
Test with errno = 1Exception caught: [1] Operation not permitted

> ./std_errno
Enter a value to errno: 11
Test with errno = 11
Exception caught: [11] Resource temporarily unavailable

> ./std_errno
Enter a value to errno: 111
Test with errno = 111
Exception caught: [111] Connection refused

(在 64 位 Opensuse 12.1、G++ 4.6.2 上运行)

于 2012-04-03T12:01:46.837 回答
1

这基本上意味着 errno 作为 C++ 异常的参数几乎没有用,因为在调用 __errno_location 之前调用了 __cxa_allocate_exception (这是 errno 的宏内容),前者调用 std::malloc 而不是保存 errno 状态(至少据我了解 libstdc++ 的 eh_alloc.cc 中 __cxa_allocate_exception 的来源)。

这不是真的。据我检查源代码,里面__cxa_allocate_exception唯一可以改变的“东西”errnomalloc(). 可能会出现两种情况:

  • malloc()成功,则errno不变;
  • malloc()失败,然后std::terminate()被调用并且你oserror()的永远不会被构造。

因此,由于_cxa_allocate_exception在调用构造函数之前调用不会在功能上改变你的程序,我相信 g++ 有权这样做。

于 2012-04-03T12:15:10.910 回答