2012-04-02 15 views
9

Khi sử dụng ngoại lệ C++ để chuyển trạng thái errno, mã được biên dịch được tạo ra bởi g ++ (4.5.3) cho mã như sauLuồng kiểm soát bất ngờ (trình biên dịch lỗi?) Sử dụng errno làm đối số ngoại lệ trong C++ (g ++)

#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); 
} 

là khá bất ngờ (trên 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 

Điều này về cơ bản có nghĩa là errno như một tham số để một C++ ngoại lệ là khá nhiều vô ích do các cuộc gọi đến __cxa_allocate_exception trước cuộc gọi đến __errno_location (đó là nội dung macro của errn o), nơi mà trước đây gọi std :: malloc và không lưu trạng thái errno (ít nhất là theo như tôi hiểu các nguồn của __cxa_allocate_exception trong eh_alloc.cc của libstdC++).

Điều này có nghĩa là trong trường hợp phân bổ bộ nhớ không thành công, số lỗi thực sự được chuyển vào đối tượng ngoại lệ sẽ bị ghi đè bằng số lỗi std :: malloc setup. std :: malloc không đảm bảo lưu trạng thái errno hiện tại, dù sao, ngay cả trong trường hợp thoát thành công - vì vậy mã trên chắc chắn bị hỏng trong trường hợp chung.

On Cygwin, x86, mã đó được biên dịch (cũng sử dụng g ++ 4.5.3) cho thử nghiệm() là okay, mặc dù:

.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 

Điều này có nghĩa rằng cho mã thư viện để quấn đúng errno nhà nước trong một ngoại lệ, tôi sẽ luôn luôn phải sử dụng một macro mà mở rộng đến một cái gì đó giống như

int curerrno_ = errno; 
    throw oserror(curerrno_); 

tôi thực sự dường như không thể tìm thấy những phần tương ứng của tiêu chuẩn C++ mà nói bất cứ điều gì về trật tự đánh giá trong trường hợp ngoại lệ, nhưng với tôi có vẻ như mã g ++ được tạo trên x86_64 (o n Linux) bị hỏng do cấp phát bộ nhớ cho đối tượng ngoại lệ trước khi thu thập các tham số cho hàm tạo của nó và đây là lỗi trình biên dịch theo một cách nào đó. Tôi có đúng không, hay đây là một số suy nghĩ cơ bản sai về phía tôi?

+0

'errnotostr()' là một hàm (tĩnh) của 'lớp oserror' về cơ bản gọi' strerror_r() 'và trả về một dạng' std :: string'-ified của kết quả. Tôi đã không bao gồm một phần của mã, bởi vì nó không liên quan cho ví dụ. – modelnine

+0

Có, brainfart, xin lỗi. Xem thường. – irobot

Trả lời

1

Điều này về cơ bản có nghĩa là errno như một tham số để một C++ ngoại lệ là khá nhiều vô ích do các cuộc gọi đến __cxa_allocate_exception trước lời kêu gọi __errno_location (đó là nội dung vĩ mô của errno), nơi mà các cựu gọi std :: malloc và không lưu trạng thái errno (ít nhất là theo như tôi hiểu các nguồn của __cxa_allocate_exception trong eh_alloc.cc của libstdC++).

Điều này không đúng. Theo như tôi đã kiểm tra mã nguồn, "điều" duy nhất bên trong __cxa_allocate_exception có thể thay đổi errnomalloc(). Hai trường hợp có thể xảy ra:

  • malloc() thành công, sau đó errno không thay đổi;
  • malloc() không thành công, sau đó std::terminate() được gọi và oserror() của bạn không bao giờ được tạo.

Do đó, kể từ khi gọi _cxa_allocate_exception trước khi gọi nhà xây dựng của bạn không thay đổi chức năng chương trình của bạn, tôi tin rằng g ++ có quyền làm như vậy.

+0

Đầu tiên, có bất kỳ đảm bảo rằng std :: malloc() không thay đổi trạng thái errno khi phân bổ thành công không? Nó có thể là hoang tưởng để suy nghĩ, nhưng AFAIK từ SuSv4 chỉ có đảm bảo rằng họ không đặt nó bằng không, không phải là nó không thay đổi. Và, anway, trong trường hợp std :: malloc() không thành công, std :: terminate() không nhất thiết phải được gọi; có một bộ đệm tĩnh được sử dụng để xây dựng các ngoại lệ trong điều kiện bộ nhớ thấp, do đó errno có thể được thiết lập mặc dù ngoại lệ được nâng lên. – modelnine

+0

Đầu tiên, nếu tôi giải thích [đặc tả POSIX] (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) một cách chính xác, 'malloc()' sẽ đặt 'errno' chỉ khi một lỗi xảy ra. Thứ hai, mã giao dịch với điều kiện bộ nhớ thấp chỉ gọi các hàm pthread, phát ra các mã lỗi là các giá trị trả về và không sử dụng 'errno'. – user1202136

+0

Đó là điều - đặc tả của malloc() chỉ đưa ra một ví dụ tích cực ("Nếu không, nó sẽ trả về một con trỏ null và đặt errno để chỉ ra lỗi"), trong trường hợp errno đã bị ghi đè và mã errno ban đầu mà tôi đã muốn chuyển vào oserror() bị mất (và không quan trọng là các phương thức pthread _ * - trên đường dẫn lowmem không thay đổi trong trường hợp này), nhưng đối với trường hợp không có lỗi, không cung cấp được thực hiện trong spec đó, và một lần nữa, AFAIK SuSv4, không có điều khoản nào nói rằng các hàm _must not_ thay đổi errno thành công, chỉ là chúng không đặt nó bằng 0. – modelnine

1

Xin lưu ý rằng __cxa_allocate_exception được thực hiện trước khi hàm tạo của bạn thực sự được gọi.

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 

Vì vậy, điều đó có ý nghĩa. __cxa_allocate_exception chỉ phân bổ không gian cho một ngoại lệ, nhưng không xây dựng nó (libc++abi Specification).

Khi đối tượng ngoại lệ của bạn được tạo, errno được đánh giá arleady.

tôi lấy ví dụ của bạn và thực hiện errnotostr:

// Unexpected flow of control (compiler-bug?) using errno as argument for exception in 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; 
} 

Sau đó, tôi biên soạn với -O0-O2, chạy và nhận được kết quả tương tự, tất cả theo sự mong đợi:

> ./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 

(Chạy trên 64 bit Opensuse 12.1, G ++ 4.6.2)

+0

Tôi không phàn nàn về thực tế là errno chưa được đánh giá khi nhập hàm khởi tạo (tôi biết nó), nhưng thay vào đó '__cxa_allocate_exception' được gọi trước khi errno được tìm nạp, và như mã cơ sở hạ tầng _may_ thay đổi trạng thái errno (xem các cuộc thảo luận về std :: malloc() được gọi) trước khi mã để truy vấn nó được một cơ hội để chạy và sau đó vượt qua được đánh giá errno cho hàm constructor. – modelnine

+0

Tôi hiểu. Hãy để tôi rõ ràng hơn. Xem xét rằng ngoại lệ chính nó có thể thay đổi 'errno', câu trả lời cho câu hỏi ban đầu của bạn là: Có, bạn cần phải lấy giá trị của' errno' trước khi ném. Hy vọng đã giúp. – j4x

+0

Đọc trả lời Tôi vừa đánh dấu câu trả lời và câu hỏi - câu hỏi không phải về hàm tạo thay đổi 'errno', mà đúng hơn là mã được tạo cho câu lệnh' throw' có thể thay đổi errno trước khi giá trị của nó có thể được truyền cho hàm tạo. Cảm ơn! – modelnine