2010-03-08 8 views
21

Trong đoạn mã sau, biến dựa trên ngăn xếp 'ex' được ném và bị bắt trong một hàm vượt quá phạm vi mà từ đó đã được khai báo. Điều này có vẻ hơi lạ đối với tôi, vì các biến dựa trên stack (AFAIK) không thể được sử dụng bên ngoài phạm vi mà chúng đã được khai báo (ngăn xếp được bỏ trống).Ngoại lệ được phân bổ như thế nào trên ngăn xếp vượt quá phạm vi của họ?

void f() { 
    SomeKindOfException ex(...); 
    throw ex; 
} 

void g() { 
    try { 
     f(); 
    } catch (SomeKindOfException& ex) { 
     //Handling code... 
    } 
} 

Tôi đã thêm một tuyên bố in để destructor SomeKindOfException và nó cho thấy rằng cựu được destructed khi nó đi ra khỏi phạm vi trong f() nhưng sau đó nó bị bắt trong g() và destructed một lần nữa một khi nó đi ra khỏi phạm vi đó là tốt.

Bất kỳ trợ giúp nào?

+0

Có đúng không khi sử dụng tham chiếu tại đây? 'catch (SomeKindOfException & ex)' Tôi nghĩ điều này là nguy hiểm, vì nó không gọi hàm tạo bản sao và bạn truy cập vào vùng bộ nhớ thuộc về chồng f() thỏa thuận! Tôi đoán rằng điều này phải chính xác thay vào đó: 'bắt (SomeKindOfException ex)' – Dacav

+0

Đúng (và thậm chí tốt hơn - xem http://www.parashift.com/c++-faq-lite/exceptions.html phần 17.7) để bắt bằng tham chiếu. Khi câu trả lời cho trạng thái câu hỏi của tôi, ngoại lệ bị bắt không phải là đối tượng dựa trên chồng được ném, mà là một bản sao của nó nằm ở một nơi khác có thể tồn tại ngăn xếp thư giãn, do đó không có rủi ro như vậy. –

+0

Vâng, tôi chạy một số thử nghiệm tối hôm qua và có, Đó là cách tốt hơn sử dụng các tài liệu tham khảo. Xem tại đây: http: // pastebin.com/8YQuNAux Nếu bạn thực hiện nó, bạn có thể nhận thấy rằng ngoại lệ được phân bổ động (theo nghĩa 'mới') ở mọi điểm bắt mà không cần tham khảo: Nếu bạn sử dụng tham chiếu, thay vào đó, nó chỉ được phân bổ một lần và tự động bị hủy khi phạm vi bị chấm dứt. Ngoài ra tôi nghĩ hành vi này phụ thuộc hoàn toàn vào trình biên dịch. – Dacav

Trả lời

18

Đối tượng ngoại lệ được sao chép vào một vị trí đặc biệt để tồn tại ngăn xếp thư giãn. Lý do bạn thấy hai sự phá hủy là bởi vì khi bạn thoát f() ngoại lệ ban đầu bị hủy và khi bạn thoát khỏi g() bản sao bị hủy.

+0

Ồ, đây là những gì tôi mong đợi một cách trực quan, tôi rất vui vì nó tương ứng với sự thật. Ngoài ra các nhà xây dựng bản sao nên được gọi, vì vậy làm thế nào để bạn đối phó đúng với điều này? Giả sử lớp ngoại lệ của bạn có một biến cá thể có chứa thông báo lỗi: tại mỗi lần 'ném' tiếp theo, bạn có bản sao chép mới của biến đó! Nói về thực tiễn tốt nhất, bạn nghĩ gì về việc đảm bảo bản sao nông chỉ cho tất cả các trường hợp ngoại lệ? – Dacav

8

Đối tượng được sao chép vào một đối tượng ngoại lệ ngoại lệ tồn tại ngăn xếp thư. Trường hợp bộ nhớ cho đối tượng đó đến từ không xác định. Đối với đối tượng lớn, nó có thể là malloc 'ed, và đối với các đối tượng nhỏ hơn, việc thực hiện có thể có một bộ đệm được cấp phát trước (tôi có thể tưởng tượng điều này có thể được sử dụng cho một ngoại lệ bad_alloc).

Tham chiếu ex sau đó được ràng buộc với đối tượng ngoại lệ ngoại lệ, tạm thời (không có tên).

+1

Cảm ơn bạn đã làm rõ vị trí bộ nhớ của ngoại lệ, tôi đã chỉnh sửa nhận xét ở trên để xóa câu hỏi đó. –

1

Do đặc tả rõ ràng nêu rõ rằng đối tượng tạm thời được tạo thay cho toán hạng throw.

8

C++ chuẩn 15,1/4:

Các bộ nhớ cho các bản sao tạm thời của ngoại lệ được ném được phân bổ một cách không xác định, trừ như đã nêu trong 3.7.3.1. Tạm thời tồn tại miễn là có một trình xử lý được thực hiện cho trường hợp ngoại lệ đó. Đặc biệt, nếu một trình xử lý thoát ra bằng cách thực hiện một cú ném; tuyên bố, điều đó chuyển quyền kiểm soát cho trình xử lý khác cho cùng một ngoại lệ, do đó, phần còn lại tạm thời. Khi xử lý cuối cùng được thực hiện cho các trường hợp ngoại lệ ngoại lệ bằng bất kỳ phương tiện nào khác ngoài việc ném; đối tượng tạm thời bị hủy và việc triển khai có thể giải quyết bộ nhớ cho đối tượng tạm thời; bất kỳ thỏa thuận như vậy được thực hiện theo một cách không xác định. Sự hủy diệt xảy ra ngay lập tức sau khi sự phá hủy của đối tượng được khai báo trong tuyên bố ngoại lệ trong trình xử lý.

Không có gì để nói nữa.

5

Khi bạn ném ex, nó sẽ được sao chép ở một vị trí bộ nhớ đặc biệt được sử dụng cho các đối tượng ngoại lệ được ném. Bản sao như vậy được thực hiện bởi các nhà xây dựng bản sao bình thường.

Bạn có thể thấy điều này một cách dễ dàng từ ví dụ này:

#include <iostream> 

void ThrowIt(); 

class TestException 
{ 
    public: 
    TestException() 
    { 
     std::cerr<<this<<" - inside default constructor"<<std::endl; 
    } 

    TestException(const TestException & Right) 
    { 
     (void)Right; 
     std::cerr<<this<<" - inside copy constructor"<<std::endl; 
    } 

    ~TestException() 
    { 
     std::cerr<<this<<" - inside destructor"<<std::endl;  
    } 
}; 

int main() 
{ 
    try 
    { 
     ThrowIt(); 
    } 
    catch(TestException & ex) 
    { 
     std::cout<<"Caught exception ("<<&ex<<")"<<std::endl; 
    } 
    return 0; 
} 

void ThrowIt() 
{ 
    TestException ex; 
    throw ex; 
} 

Mẫu đầu ra:

[email protected]:~/cpp/test$ g++ -O3 -Wall -Wextra -ansi -pedantic ExceptionStack.cpp -o ExceptionStack.x 
[email protected]:~/cpp/test$ ./ExceptionStack.x 
0xbf8e202f - inside default constructor 
0x9ec0068 - inside copy constructor 
0xbf8e202f - inside destructor 
Caught exception (0x9ec0068) 
0x9ec0068 - inside destructor 

Bằng cách này, bạn có thể thấy ở đây rằng vị trí bộ nhớ sử dụng cho các đối tượng ném (0x09ec0068) chắc chắn là cách xa đối tượng ban đầu (0xbf8e202f): ngăn xếp, như thường lệ, có địa chỉ cao, trong khi bộ nhớ được sử dụng cho đối tượng được ném xuống khá thấp trong không gian địa chỉ ảo. Tuy nhiên, đây là một chi tiết thực hiện, vì, như các câu trả lời khác chỉ ra, tiêu chuẩn không nói bất cứ điều gì về nơi bộ nhớ cho đối tượng được ném nên được phân bổ như thế nào.

+0

Sử dụng VC++, vị trí bộ nhớ của đối tượng được ném rất gần với đối tượng gốc (nghĩa là, trên ngăn xếp). Điều này thực sự chứng minh quan điểm của Johannes và Kirill, rằng vị trí bộ nhớ trong không xác định. –

+0

Bạn nói đúng, đây thực sự là một chi tiết thực hiện. Tôi sẽ làm rõ câu trả lời. –

3

Ngoài những gì tiêu chuẩn nói trong 15.1/4 ("Xử lý ngoại lệ/Ném ngoại lệ") - bộ nhớ cho bản sao tạm thời của ngoại lệ được ném được phân bổ theo cách không xác định - một vài bit khác đố về cách thức đối tượng ngoại lệ được phân bổ như sau:

  • 3.7.3.1/4 ("chức năng phân bổ") của tiêu chuẩn chỉ ra rằng các đối tượng ngoại lệ không thể được phân bổ bởi một biểu thức new hoặc một cuộc gọi đến một 'chức năng phân bổ toàn cầu' (ví dụ: thay thế operator new()). Lưu ý rằng malloc() không phải là 'chức năng phân bổ toàn cầu' như được xác định theo tiêu chuẩn, do đó, malloc() chắc chắn là một tùy chọn để phân bổ đối tượng ngoại lệ.

  • "Khi một ngoại lệ được ném ra, các đối tượng ngoại lệ được tạo ra và một đặt chung trên một số loại ngoại lệ dữ liệu ngăn xếp" (Stanley Lippman, "Bên trong C++ mô hình đối tượng" - 7.2 xử lý ngoại lệ)

  • Từ "Ngôn ngữ lập trình C++ của Stroustrup, phiên bản thứ ba": "Cần phải thực hiện C++ để có đủ bộ nhớ dự phòng để có thể ném bad_alloc trong trường hợp hết bộ nhớ. Tuy nhiên, có thể ném một số ngoại lệ khác sẽ làm cạn kiệt bộ nhớ. "(14.4.5 Tình trạng cạn kiệt tài nguyên); và "Việc triển khai có thể áp dụng nhiều chiến lược để lưu trữ và truyền ngoại lệ. Tuy nhiên, đảm bảo có đủ bộ nhớ để cho phép new ném ngoại lệ ngoài bộ nhớ tiêu chuẩn, bad_alloc" (14.3 Ghi ngoại lệ) .

Lưu ý rằng dấu ngoặc kép từ Stroustrup là chuẩn. Tôi thấy thú vị là tiêu chuẩn dường như không đảm bảo rằng Stroustrup nghĩ đủ quan trọng để đề cập đến hai lần.