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

これが基本的に意味するのは、C ++例外の引数としてのerrnoは、__ errno_location(errnoのマクロコンテンツ)の呼び出しに先行する__cxa_allocate_exceptionの呼び出しのため、ほとんど役に立たないということです。 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

これが基本的に意味することは、C++ 例外の引数としての errno は、__errno_location (errno のマクロ コンテンツ) への呼び出しの前に __cxa_allocate_exception への呼び出しがあるため、ほとんど役に立たないということです。 errno 状態を保存します (少なくとも libstdc++ の eh_alloc.cc の __cxa_allocate_exception のソースを理解している限り)。

本当じゃない。ソースコードを確認した限り、内部__cxa_allocate_exceptionで変更できる唯一の「もの」errnomalloc(). 次の 2 つのケースが発生する可能性があります。

  • malloc()成功し、errno変更されません。
  • malloc()失敗すると、std::terminate()呼び出され、あなたoserror()は決して構築されません。

したがって、_cxa_allocate_exceptionコンストラクターを呼び出す前に呼び出しても機能的にプログラムは変更されないため、g++ にはそうする権利があると思います。

于 2012-04-03T12:15:10.910 に答える
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 に答える