Неожиданный поток управления (ошибка компилятора?) С использованием errno в качестве аргумента для исключения в C ++ (g ++) - PullRequest
9 голосов
/ 03 апреля 2012

При использовании исключений C ++ для передачи состояния ошибки, скомпилированный код, сгенерированный 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 ++ в значительной степени бесполезен из-за вызова __cxa_allocate_exception, предшествующего вызову __errno_location (который является макросодержанием errno), где первый вызывает std :: malloc и не сохраняет ошибочное состояние (по крайней мере, насколько я понял, источники __cxa_allocate_exception в eh_alloc.cc из libstdc ++).

Это означает, что в случае сбоя выделения памяти номер ошибки, который фактически должен был быть передан в объект исключения, перезаписывается номером ошибки, установленным 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 ++, в котором что-то говорится о порядке вычисления в случае исключений, но мне кажется, что сгенерированный g ++ код на x86_64 (в Linux) не работает из-за выделение памяти для объекта исключения до сбора параметров для его конструктора, и что это в некотором роде ошибка компилятора. Я прав или это какое-то принципиально неправильное мышление с моей стороны?

Ответы [ 2 ]

1 голос
/ 03 апреля 2012

Что это в основном означает, что errno в качестве аргумента исключения C ++ в значительной степени бесполезен из-за вызова __cxa_allocate_exception, предшествующего вызову __errno_location (который является макросодержанием errno), где первый вызывает std :: malloc и не сохраняет ошибочное состояние (по крайней мере, насколько я понял источники __cxa_allocate_exception в eh_alloc.cc из libstdc ++).

Это не правда. Насколько я проверил исходный код, единственная «вещь» внутри __cxa_allocate_exception, которая может измениться errno, это malloc(). Возможны два случая:

  • malloc() успешно, затем errno не изменяется;
  • malloc() терпит неудачу, тогда вызывается std::terminate() и ваш oserror() никогда не создается.

Поэтому, поскольку вызов _cxa_allocate_exception перед вызовом вашего конструктора не меняет вашу программу функционально, я считаю, что g ++ имеет право сделать это.

1 голос
/ 03 апреля 2012

Обратите внимание, что __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:

// Неожиданный поток управления (ошибка компилятора?) С использованием errno в качестве аргумента для исключения в C ++ (g ++)

#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 и -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)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...