2013-03-15 34 views
8

Hôm qua tôi gặp phải đau khổ khiến tôi mất 24 giờ. Sự cố được giải quyết xuống các sự cố bất ngờ xảy ra trên cơ sở ngẫu nhiên. Để làm phức tạp mọi thứ, việc gỡ lỗi các báo cáo hoàn toàn cũng có dạng ngẫu nhiên. Để làm phức tạp hơn nữa, tất cả các dấu vết gỡ lỗi đều dẫn đến ngẫu nhiên nguồn Qt hoặc DLL gốc, tức là mỗi lần vấn đề đó không đúng về phía tôi.Pimpl + QSharedPointer - Destructor = Disaster

Ở đây bạn là một vài ví dụ về các báo cáo đáng yêu như vậy:

Program received signal SIGSEGV, Segmentation fault. 
0x0000000077864324 in ntdll!RtlAppendStringToString() from C:\Windows\system32\ntdll.dll 
(gdb) bt 
#0 0x0000000077864324 in ntdll!RtlAppendStringToString() from C:\Windows\system32\ntdll.dll 
#1 0x000000002efc0230 in ??() 
#2 0x0000000002070005 in ??() 
#3 0x000000002efc0000 in ??() 
#4 0x000000007787969f in ntdll!RtlIsValidHandle() from C:\Windows\system32\ntdll.dll 
#5 0x0000000000000000 in ??() 

warning: HEAP: Free Heap block 307e5950 modified at 307e59c0 after it was freed 
Program received signal SIGTRAP, Trace/breakpoint trap. 
0x00000000778bf0b2 in ntdll!ExpInterlockedPopEntrySListFault16() from C:\Windows\system32\ntdll.dll 
(gdb) bt 
#0 0x00000000778bf0b2 in ntdll!ExpInterlockedPopEntrySListFault16() from C:\Windows\system32\ntdll.dll 
#1 0x000000007786fd34 in ntdll!RtlIsValidHandle() from C:\Windows\system32\ntdll.dll 
#2 0x0000000077910d20 in ntdll!RtlGetLastNtStatus() from C:\Windows\system32\ntdll.dll 
#3 0x00000000307e5950 in ??() 
#4 0x00000000307e59c0 in ??() 
#5 0x00000000ffffffff in ??() 
#6 0x0000000000220f10 in ??() 
#7 0x0000000077712d60 in WaitForMultipleObjectsEx() from C:\Windows\system32\kernel32.dll 
#8 0x0000000000000000 in ??() 

Program received signal SIGSEGV, Segmentation fault. 
0x0000000000a9678a in QBasicAtomicInt::ref (this=0x8) at ../../include/QtCore/../../../qt-src/src/corelib/arch/qatomic_x86_64.h:121 
121 : "memory"); 
(gdb) bt 
#0 0x0000000000a9678a in QBasicAtomicInt::ref (this=0x8) at ../../include/QtCore/../../../qt-src/src/corelib/arch/qatomic_x86_64.h:121 
#1 0x00000000009df08e in QVariant::QVariant (this=0x21e4d0, p=...) at d:/Distributions/qt-src/src/corelib/kernel/qvariant.cpp:1426 
#2 0x0000000000b4dde9 in QList<QVariant>::value (this=0x323bd480, i=1) at ../../include/QtCore/../../../qt-src/src/corelib/tools/qlist.h:666 
#3 0x00000000009ccff7 in QObject::property (this=0x3067e900, 
name=0xa9d042a <QCDEStyle::drawPrimitive(QStyle::PrimitiveElement, QStyleOption const*, QPainter*, QWidget const*) const::pts5+650> "_q_stylerect") 
at d:/Distributions/qt-src/src/corelib/kernel/qobject.cpp:3742 
#4 0x0000000000000000 in ??() 

Như bạn thấy công cụ này là khá khó chịu, nó mang lại một không có thông tin hữu ích . Nhưng, có một điều tôi không chú ý đến. Đó là một cảnh báo lạ quá trình biên dịch mà cũng rất khó để bắt với một mắt:

In file included from d:/Libraries/x64/MinGW-w64/4.7.2/Qt/4.8.4/include/QtCore/qsharedpointer.h:50:0, 
       from d:/Libraries/x64/MinGW-w64/4.7.2/Qt/4.8.4/include/QtCore/QSharedPointer:1, 
       from ../../../../source/libraries/Project/sources/Method.hpp:4, 
       from ../../../../source/libraries/Project/sources/Slot.hpp:4, 
       from ../../../../source/libraries/Project/sources/Slot.cpp:1: 
d:/Libraries/x64/MinGW-w64/4.7.2/Qt/4.8.4/include/QtCore/qsharedpointer_impl.h: In instantiation of 'static void QtSharedPointer::ExternalRefCount<T>::deref(QtSharedPointer::ExternalRefCount<T>::Data*, T*) [with T = Project::Method::Private; QtSharedPointer::ExternalRefCount<T>::Data = QtSharedPointer::ExternalRefCountData]': 
d:/Libraries/x64/MinGW-w64/4.7.2/Qt/4.8.4/include/QtCore/qsharedpointer_impl.h:336:11: required from 'void QtSharedPointer::ExternalRefCount<T>::deref() [with T = Project::Method::Private]' 
d:/Libraries/x64/MinGW-w64/4.7.2/Qt/4.8.4/include/QtCore/qsharedpointer_impl.h:401:38: required from 'QtSharedPointer::ExternalRefCount<T>::~ExternalRefCount() [with T = Project::Method::Private]' 
d:/Libraries/x64/MinGW-w64/4.7.2/Qt/4.8.4/include/QtCore/qsharedpointer_impl.h:466:7: required from here 
d:/Libraries/x64/MinGW-w64/4.7.2/Qt/4.8.4/include/QtCore/qsharedpointer_impl.h:342:21: warning: possible problem detected in invocation of delete operator: [enabled by default] 
d:/Libraries/x64/MinGW-w64/4.7.2/Qt/4.8.4/include/QtCore/qsharedpointer_impl.h:337:28: warning: 'value' has incomplete type [enabled by default] 

Thực ra, tôi quay sang cảnh báo này chỉ như là một phương sách cuối cùng bởi vì trong một sự theo đuổi tuyệt vọng như vậy để tìm ra lỗi mã là đã bị nhiễm với việc đăng nhập để chết theo nghĩa đen.

Sau khi đọc nó một cách cẩn thận, tôi nhắc lại rằng, ví dụ, nếu một người sử dụng std::unique_ptr hoặc std::scoped_ptr cho Pimpl - một chắc chắn sẽ cung cấp desctructor, nếu không mã thậm chí sẽ không biên dịch. Tuy nhiên, tôi cũng nhớ rằng std::shared_ptr không quan tâm đến destructor và hoạt động tốt mà không có nó. Đó là một lý do khác khiến tôi không chú ý đến cảnh báo kỳ lạ này. Câu chuyện dài ngắn, khi tôi thêm vào destructor, vụ tai nạn ngẫu nhiên này dừng lại. Có vẻ như số QSharedPointer của Qt có một số lỗi thiết kế so với std::shared_ptr. Tôi đoán nó sẽ tốt hơn, nếu các nhà phát triển Qt chuyển đổi cảnh báo thành lỗi này vì gỡ lỗi marathon như thế đơn giản là không xứng đáng với thời gian, công sức và dây thần kinh.

Câu hỏi của tôi là:

  1. Có gì sai với QSharedPointer? Tại sao destructor là rất quan trọng?
  2. Tại sao sự cố xảy ra khi không có destructor? Các đối tượng này (đang sử dụng Pimpl + QSharedPointer) được tạo trên ngăn xếp và không có đối tượng nào khác có quyền truy cập vào chúng sau khi chết. Tuy nhiên, sự cố xảy ra trong một số khoảng thời gian ngẫu nhiên sau khi chết.
  3. Có ai gặp phải các vấn đề như vậy trước đây không? Xin vui lòng, chia sẻ kinh nghiệm của bạn.
  4. Có những cạm bẫy nào khác như vậy trong Qt - cái mà tôi phải biết chắc chắn để giữ an toàn cho trong tương lai?

Hy vọng rằng, những câu hỏi này và bài đăng của tôi nói chung sẽ giúp những người khác tránh được địa ngục tôi đã làm trong 24 giờ qua.

+2

Tôi thường khuyên bạn nên sử dụng cờ -Werror để chuyển tất cả các cảnh báo thành lỗi. Nó đòi hỏi một số công việc để sắp xếp tất cả các cảnh báo, nhưng tôi đã tìm thấy nó vô giá để cảnh báo cho tôi các vấn đề tiềm năng (cho dù bạn sử dụng Qt hay không). – cgmb

+1

Sự khác biệt giữa QSharedPtr và shard_ptr là cách thức và thời điểm mã thực hiện xóa T * được khởi tạo - và mã đó tất nhiên cần phải biết khai báo T để gọi dtor. Việc chữa bệnh là để cảnh báo nghiêm túc (như đề nghị bởi Slavik81). –

Trả lời

3

Vấn đề này đã được làm việc xung quanh trong Qt 5, xem https://codereview.qt-project.org/#change,26974

Trình biên dịch gọi destructor sai hoặc giả định một cách bố trí bộ nhớ khác nhau có thể dẫn đến một số loại tham nhũng bộ nhớ. Tôi muốn nói một trình biên dịch nên cung cấp cho một lỗi cho vấn đề này và không phải là một cảnh báo.

1

Bạn sẽ gặp sự cố tương tự với std::unique_ptr, điều này cũng có thể gây ra các trình phá hủy bị hỏng nếu được sử dụng với loại không đầy đủ. Việc sửa chữa là khá tầm thường, tất nhiên - Tôi tuyên bố một constructor cho lớp, sau đó xác định nó trong file thi hành kể

MyClass::~MyClass() = default; 

Lý do rằng đây là một vấn đề đối với std::unique_ptr nhưng không std::shared_ptr là destructor là một phần thuộc loại cũ, nhưng là thành viên của nhóm thứ hai.