2009-12-23 17 views
6

Gần đây tôi đã chuyển ứng dụng của mình từ VC++ 7 sang VC++ 9. Bây giờ đôi khi nó bị treo khi thoát - thời gian chạy bắt đầu gọi các đối tượng hủy diệt toàn cục và vi phạm truy cập xảy ra ở một trong số chúng."Động" trong "destructor atexit động" nghĩa là gì?

Bất cứ khi nào tôi quan sát cuộc gọi ngăn xếp các bộ phận chức năng bao gồm:

CMyClass::~CMyClass() <- crashes here 
dynamic atexit destructor for 'ObjectName' 
_CRT_INIT() 
some more runtime-related functions follow 

Câu hỏi đặt ra là ý nghĩa của từ "năng động" trong "destructor atexit động" là gì? Nó có thể cung cấp thêm thông tin cho tôi không?

Trả lời

7

khó khăn để xác định vấn đề chính xác mà không cần mã thực tế, nhưng có lẽ bạn có thể tìm thấy nó cho mình sau khi đọc bài viết này của nó:

từ http://www.gershnik.com/tips/cpp.asp (liên kết tại là chết xem dưới đây)

atexit() và thư viện động/chia sẻ

Thư viện chuẩn C và C++ bao gồm chức năng hữu ích đôi khi: atexit(). Nó cho phép người gọi đăng ký một cuộc gọi lại sẽ được gọi khi ứng dụng thoát (thông thường). Trong C++ nó cũng được tích hợp với cơ chế gọi destructors của các đối tượng toàn cầu, do đó mọi thứ được tạo trước khi một lệnh gọi tới atexit() sẽ bị hủy trước khi gọi lại và ngược lại. Tất cả điều này nên được biết đến và nó hoạt động hoàn toàn tốt đẹp cho đến khi DLL hoặc thư viện chia sẻ nhập hình ảnh.

Vấn đề là, tất nhiên, các thư viện động có tuổi thọ riêng của mình, nói chung, có thể kết thúc trước khi ứng dụng chính là một. Nếu một mã trong một DLL đăng ký một trong các hàm riêng của nó như là một lệnh gọi lại atexit() thì gọi lại này nên được gọi trước khi DLL bị dỡ bỏ. Nếu không, sự cố hoặc sự cố tồi tệ hơn sẽ xảy ra trong quá trình thoát ứng dụng chính. (Để làm cho mọi thứ gặp sự cố khó chịu trong quá trình thoát ra thì rất khó để gỡ lỗi vì nhiều trình gỡ rối có vấn đề đối với các quá trình chết).

Vấn đề này được biết đến nhiều hơn trong ngữ cảnh của các destructors của các đối tượng toàn cục C++ (như đã đề cập ở trên, là anh em của atexit()). Rõ ràng là việc thực hiện C++ trên một nền tảng hỗ trợ các thư viện động phải giải quyết vấn đề này và giải pháp nhất trí là gọi các destructor toàn cục hoặc khi thư viện chia sẻ bị dỡ hoặc khi thoát ứng dụng, tùy theo điều kiện nào đến trước.

Cho đến nay rất tốt, ngoại trừ một số triển khai "quên" để mở rộng cùng một cơ chế cho đồng bằng cũ atexit(). Vì chuẩn C++ không nói bất cứ điều gì về các thư viện động như vậy, nó không giúp cho lập trình viên nghèo vì lý do này hay lý do khác cần gọi atexit() truyền một cuộc gọi lại nằm trong một DLL.

Trên nền tảng tôi biết về tình huống như sau. MSVC trên Windows, GCC trên Linux và Solaris và SunPro trên Solaris đều có một "đúng" atexit() hoạt động theo cách tương tự như destructors toàn cầu. Tuy nhiên, GCC trên FreeBSD tại thời điểm của văn bản này có một "bị hỏng" một trong đó luôn luôn đăng ký callbacks được thực hiện trên ứng dụng chứ không phải là thoát khỏi thư viện được chia sẻ. Tuy nhiên, như đã hứa, những kẻ hủy diệt toàn cầu làm việc tốt ngay cả trên FreeBSD.

Bạn nên làm gì trong mã di động? Một giải pháp là, tất nhiên, để tránh atexit() hoàn toàn. Nếu bạn cần chức năng của nó, thật dễ dàng để thay thế nó bằng trình phá hủy C++ theo cách sau đây

//Code with atexit() 

void callback() 
{ 
    //do something 
} 

... 
atexit(callback); 
... 

//Equivalent code without atexit() 

class callback 
{ 
public: 
    ~callback() 
    { 
     //do something 
    } 

    static void register(); 
private: 
    callback() 
    {} 

    //not implemented 
    callback(const callback &); 
    void operator=(const callback &); 
}; 

void callback::register() 
{ 
    static callback the_instance; 
} 

... 
callback::register(); 
... 

Điều này làm việc với chi phí nhiều giao diện gõ và không trực quan.Điều quan trọng cần lưu ý là không có mất chức năng nào so với phiên bản atexit(). Trình phá hủy gọi lại không thể ném ngoại lệ nhưng do đó các hàm được gọi bởi atexit. Hàm callback :: register() có thể không phải là luồng an toàn trên một nền tảng nhất định, nhưng như vậy là atexit() (C++ standard hiện đang im lặng trên luồng để cho dù thực hiện atexit() theo cách an toàn chỉ là thực hiện)

Điều gì sẽ xảy ra nếu bạn muốn tránh tất cả việc nhập ở trên? Thường có một cách và nó dựa trên một mẹo đơn giản. Thay vì gọi atexit bị hỏng(), chúng ta cần làm bất cứ trình biên dịch C++ nào để đăng ký các destructor toàn cầu. Với GCC và các trình biên dịch khác thực hiện cái gọi là Itanium ABI (được sử dụng rộng rãi cho các nền tảng Itanium không) câu thần chú được gọi là __cxa_atexit. Đây là cách sử dụng nó. Trước tiên, hãy đặt mã bên dưới vào một số tiêu đề tiện ích

#if defined(_WIN32) || defined(LINUX) || defined(SOLARIS) 

    #include <stdlib.h> 

    #define SAFE_ATEXIT_ARG 

    inline void safe_atexit(void (*p)(SAFE_ATEXIT_ARG)) 
    { 
     atexit(p); 
    } 

#elif defined(FREEBSD) 

    extern "C" int __cxa_atexit(void (*func) (void *), void * arg, void * dso_handle); 
    extern "C" void * __dso_handle;  


    #define SAFE_ATEXIT_ARG void * 

    inline void safe_atexit(void (*p)(SAFE_ATEXIT_ARG)) 
    { 
     __cxa_atexit(p, 0, __dso_handle); 
    } 

#endif 
And then use it as follows 


void callback(SAFE_ATEXIT_ARG) 
{ 
    //do something 
} 

... 
safe_atexit(callback); 
... 

Cách thức __cxa_atexit hoạt động như sau. Nó đăng ký các cuộc gọi lại trong một danh sách toàn cầu duy nhất giống như cách thức không biết DLL atexit(). Tuy nhiên nó cũng liên kết hai tham số khác với nó. Tham số thứ hai chỉ là một điều tốt đẹp. Nó cho phép gọi lại được thông qua một số bối cảnh (như một số đối tượng này) và do đó, một cuộc gọi lại duy nhất có thể được tái sử dụng cho nhiều dọn dẹp. Tham số thứ ba là thông số chúng ta thực sự cần. Nó chỉ đơn giản là một "cookie" xác định thư viện được chia sẻ cần được liên kết với cuộc gọi lại. Khi bất kỳ thư viện được chia sẻ nào được giải mã, mã dọn dẹp của nó sẽ duyệt qua danh sách gọi lại ngoại tuyến và gọi (và loại bỏ) bất kỳ cuộc gọi lại nào có cookie khớp với cookie được liên kết với thư viện đang được tải. Giá trị của cookie là gì? Nó không phải là địa chỉ bắt đầu DLL và không phải là xử lý dlopen() của nó. Thay vào đó, xử lý được lưu trữ trong một biến toàn cục đặc biệt __dso_handle được duy trì bởi thời gian chạy C++.

Hàm safe_atexit phải là nội dòng. Bằng cách này, nó chọn bất cứ thứ gì __dso_handle được sử dụng bởi mô-đun gọi điện, đó chính xác là những gì chúng ta cần.

Bạn có nên sử dụng phương pháp này thay vì phương pháp tiếp cận tiết kiệm hơn và dễ di chuyển hơn không? Có lẽ không, mặc dù ai biết được những yêu cầu nào bạn có thể có. Tuy nhiên, ngay cả khi bạn không bao giờ sử dụng nó, nó giúp để nhận thức được cách mọi thứ hoạt động như vậy, đây là lý do tại sao nó được bao gồm ở đây.

+0

Điều này có nghĩa là "động" là từ "thư viện tải động" không? – sharptooth

+0

Không có thuật ngữ nào đề cập đến đăng ký động của hàm gọi lại atexit trong thời gian chạy, điều đó trái ngược với việc đăng ký tĩnh được thực hiện trong thời gian biên dịch. nó có ích cho các thư viện tải động vì bạn có thể loại bỏ một hàm gọi lại ra khỏi danh sách atexit nếu tại một số điểm tùy ý mà ứng dụng của bạn quyết định dỡ bỏ một dll đã tải trước đó, trong trường hợp đó bạn cũng có thể gọi thủ công bất kỳ mã dọn dẹp nào mà không cần phải chuyển tiếp ateixt. Liên kết – Alon

+0

đã chết. Bạn nên cân nhắc việc sao chép thông tin liên quan vào câu trả lời của mình. –

Các vấn đề liên quan