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