Trang 1 trên tổng số 2 12 Cuối cùngCuối cùng
Từ 1 tới 10 trên tổng số 13 kết quả

Đề tài: Memory - Bộ nhớ | Thảo luận về cách chống leakMemory

  1. #1
    Ngày gia nhập
    01 2008
    Nơi ở
    Gameloft Studio
    Bài viết
    294

    Mặc định Memory - Bộ nhớ | Thảo luận về cách chống leakMemory

    Leak Memory là một thuật ngữ khá quan thuộc với những bạn lập trình C++. Đây có thể xem là 1 bug, mặc dù nó không làm chương trình error tức thời nhưng nó lại làm tổn hao bộ nhớ rất nhiều.

    Thực tế thì sau khi chương trình của bạn kết thúc thì Hệ Điều Hành cũng sẽ dọn rác những memory bị leak này tuy nhiên nếu 1 chương trình C++ mà khi kết thúc để bị leak thì sẽ không hay lắm. Đặc biệt để tìm ra lỗ hổng để leak memory cũng không phải là đơn giản nhất là với những project lớn.

    Trong tuturial này mình không phải trình bày giải pháp có thể khống chế hoàn toàn leak memory tuy nhiên mình nghĩ sau bài viết này của mình thì bạn có thể giúp bạn giảm độ phức tạp trong việc quản lý bộ nhớ của ngôn ngữ C++ đến gần 60%.

    Còn nếu đọc xong bài viết này mà bạn không biết mình đang nói nhảm về cái gì thì cũng hết sức bình thường thôi .


    Mình sẽ bắt đầu vấn đề nhé.
    Thứ nhất là 1 chương trình mình cố tình để leakMemory
    C++ Code:
    1. #include <iostream>
    2. #include <memory>
    3.  
    4. using namespace std;
    5.  
    6. class uiTestObject
    7. {
    8. protected:
    9. public:
    10.     uiTestObject()
    11.     {
    12.         cout << "Doi tuong uiTestObject khoi tao" << endl;
    13.     }
    14.  
    15.     virtual ~uiTestObject()
    16.     {
    17.         cout << "Doi tuong uiTestObject da bi pha huy" << endl;
    18.     }
    19. };
    20.  
    21. int main(int argc, char* argv[])
    22. {
    23.    
    24.     uiTestObject* p = new uiTestObject();
    25.     return 0;
    26. }

    Và kết quả chương trình sẽ như sau:
    Code:
    Doi tuong uiTestObject khoi tao
    Press any key to continue . . .
    Bạn thấy rõ ràng là uiTestObject được khởi tạo nhưng nó không bị hủy đi.
    Tuy nhiên không phải lúc nào ta cũng có thể in ra log để biết đối tượng nào in ra và hủy đi như vậy.

    Mình sẽ sử dụng thêm 1 macro UIDEBUG_DUMPLEAK để khảo sát tiếp
    C++ Code:
    1. #include <iostream>
    2. #include <memory>
    3.  
    4. /////////////////////////////////////////////////////////////////
    5. #ifdef _DEBUG  
    6.  
    7.     // Ho tro Leak
    8.     #define _CRTDBG_MAP_ALLOC
    9.     #include <crtdbg.h>
    10.  
    11.     // UIDEBUG_DUMPLEAK
    12.     // In danh sach Memory bi LEAK
    13.     #define UIDEBUG_DUMPLEAK() _CrtDumpMemoryLeaks()
    14. #else  
    15.     #define UIDEBUG_DUMPLEAK()
    16. #endif
    17. /////////////////////////////////////////////////////////////////
    18.  
    19.  
    20. using namespace std;
    21.  
    22. class uiTestObject
    23. {
    24. protected:
    25. public:
    26.     uiTestObject()
    27.     {
    28.         cout << "Doi tuong uiTestObject khoi tao" << endl;
    29.     }
    30.  
    31.     virtual ~uiTestObject()
    32.     {
    33.         cout << "Doi tuong uiTestObject da bi pha huy" << endl;
    34.     }
    35. };
    36.  
    37. int main(int argc, char* argv[])
    38. {
    39.     uiTestObject* p = new uiTestObject();
    40.     UIDEBUG_DUMPLEAK();
    41.     return 0;
    42. }

    Lúc này bạn để ý ở của sổ output trình biên dịch sẽ được UIDEBUG_DUMPLEAK() in LOG như sau:

    Code:
    Detected memory leaks!
    Dumping objects ->
    {118} normal block at 0x00354720, 4 bytes long.
     Data: <(wA > 28 77 41 00 
    Object dump complete.
    The program '[3284] leakMemory.exe: Native' has exited with code 0 (0x0).
    Bạn đã để phí 4bytes không được giải phóng?

    Vấn đề bây giờ là chúng ta hoàn toàn có thể quên delete, thậm chí cả những programer chuyên nghiệp cũng có thể mắc lỗi này, tuy nhiên họ luôn biết được họ quên ở chỗ nào? Nhưng với 1 project lớn thì rõ ràng bạn sẽ không thể biết các bạn trong nhóm của mình để bị leak chỗ nào được

    Một giải pháp mà thư viện STL hỗ trợ là sử dụng smart pointer.
    Ví dụ:
    C++ Code:
    1. #include <iostream>
    2. #include <memory>
    3.  
    4. /////////////////////////////////////////////////////////////////
    5. #ifdef _DEBUG  
    6.  
    7.     // Ho tro Leak
    8.     #define _CRTDBG_MAP_ALLOC
    9.     #include <crtdbg.h>
    10.  
    11.     // UIDEBUG_DUMPLEAK
    12.     // In danh sach Memory bi LEAK
    13.     #define UIDEBUG_DUMPLEAK() _CrtDumpMemoryLeaks()
    14. #else  
    15.     #define UIDEBUG_DUMPLEAK()
    16. #endif
    17. /////////////////////////////////////////////////////////////////
    18.  
    19.  
    20. using namespace std;
    21.  
    22. class uiTestObject
    23. {
    24. protected:
    25. public:
    26.     uiTestObject()
    27.     {
    28.         cout << "Doi tuong uiTestObject khoi tao" << endl;
    29.     }
    30.  
    31.     virtual ~uiTestObject()
    32.     {
    33.         cout << "Doi tuong uiTestObject da bi pha huy" << endl;
    34.     }
    35.  
    36.     void printMe()
    37.     {
    38.         cout << "uiTestObject::printMe" << endl;
    39.     }
    40. };
    41.  
    42. void test()
    43. {
    44.     cout << "test() Function" << endl;
    45.     // Su dung smart pointer
    46.     auto_ptr<uiTestObject> p( new uiTestObject );
    47.     p->printMe();
    48. }
    49.  
    50. int main(int argc, char* argv[])
    51. {
    52.     test();
    53.     cout << "main() Function" << endl;
    54.  
    55.     UIDEBUG_DUMPLEAK();
    56.     return 0;
    57. }

    Biên dịch lại thì bạn thấy chương trình chạy rất tổt. Ko để memory bị leak.

    Kết quả
    Code:
    test() Function
    Doi tuong uiTestObject khoi tao
    uiTestObject::printMe
    Doi tuong uiTestObject da bi pha huy
    main() Function
    Đối tượng uiTestObject đã bị hủy khi kết thúc hàm test. Rõ ràng với smart pointer thì nó đã tự động biết đối tượng uiTestObject khi nào không còn sử dụng và delete.

    Tuy nhiên còn có quá nhiều vấn đề với cái smart pointer. Smart pointer muốn chạy ổn định thì nó cần phải duy trì các đối tượng template auto_ptr<T> tham chiếu tới nó.

    Ví dụ như:
    auto_ptr<uiTestObject> p( new uiTestObject );
    auto_ptr<uiTestObject> e = p;

    Khi không có ai tham chiếu tới nó (con trỏ lạc) thì tự động nó sẽ delete.

    Và code mà khi sử dụng smart pointer thì xấu vô cùng, lúc nào đối tượng thật của chúng ta cũng bị bao bọc bởi smart pointer luôn đi theo đối tượng khi truyền vào hàm xử lý, gán... và lúc này nó lại làm chương trình khó đọc hơn. Chưa kể bạn còn có thể tạo ra những lỗi nguy hiểm và rất khó sửa nếu sử dụng ko quen.

    Và theo mình thì giải pháp sử dụng smart pointer không được hay lắm.
    Đã được chỉnh sửa lần cuối bởi ZCoder87 : 08-03-2009 lúc 01:26 PM.

  2. #2
    Ngày gia nhập
    01 2008
    Nơi ở
    Gameloft Studio
    Bài viết
    294

    Mình đã giới thiệu qua về smart pointer tuy nhiên mình đang cố tìm 1 giải pháp khác để giảm độ rối trong code.

    Trước mắt thì chúng ta sẽ giải quyết sao cho ko bị leakMemory:

    Có 1 giải pháp là sử dụng 1 linklist toàn cục. Tất cả các đối tượng khi new ra sẽ được push vào linklist này. Mình sẽ override lại 2 operator new và delete mặc định của IDE

    Khi 1 đối tượng được "new" ra thì mình sẽ gắn kèm cho nó 1 header (gần 10 bytes) phía trước:
    C++ Code:
    1. struct uiMemoryHeader
    2. {
    3.         bool            bIsInGlobalList;
    4.         bool            bIsObject;
    5.         unsigned long   dwSizeMemory;
    6.         void*           pData;
    7. };
    Trong đó:
    Code:
    bIsInGlobalList: Cho biết vùng nhớ này có được save ở GlobalList chưa
    bIsObject: Cho biết vùng nhớ này là đối tượng hay không phải đối tượng
    dwSizeMemory: Kích thước cấp phát
    pData: Con trỏ tới NODE của linklist
    Xem hình nhé:


    Còn đây là code mà mình override lại 2 toán tử new và delete...
    C++ Code:
    1. void* __cdecl operator new (size_t size)   
    2. {
    3.         // Tinh kích thước header
    4.         unsigned char sizeHeader = sizeof(uiMemoryHeader);
    5.  
    6.         // Kích thước cấp phát sẽ tăng thêm phần header
    7.         unsigned char *p = (unsigned char*) malloc(size + sizeHeader); 
    8.  
    9.         // Ghi một số thông tin lên header
    10.         uiMemoryHeader *header = (uiMemoryHeader*) p;
    11.            
    12.         header->bIsInGlobalList = false;
    13.         header->dwSizeMemory    = (unsigned long) size;
    14.         header->bIsObject       = 0;
    15.  
    16.         // Nếu cho phép lưu vào list -> lưu void* vào list
    17.         if ( !uiMemoryManager::isLockPush() )
    18.         {      
    19.             uiList<void*>::uiListNode* pNode = uiMemoryManager::push( p );
    20.  
    21.             // pData của header là con trỏ node trên list
    22.             header->pData = pNode;
    23.             header->bIsInGlobalList = true;
    24.         }
    25.  
    26.         // Dịch tới con trỏ của Object -> trả về kích thước đó
    27.         p += sizeHeader;   
    28.         return (void*) p;
    29. }
    30.  
    31. void __cdecl operator delete (void *p)
    32. {
    33.         if (p != 0)
    34.         {      
    35.             unsigned char sizeHeader = sizeof(uiMemoryHeader);
    36.             unsigned char *b = (unsigned char*) p;
    37.  
    38.             // Dich nguoc để bổ sung thêm phần header
    39.             b -= sizeHeader;
    40.  
    41.             uiMemoryHeader *header = (uiMemoryHeader*) b;
    42.  
    43.             // Neu header co trong globalList
    44.             if ( header->bIsInGlobalList )
    45.             {
    46.                 // Neu cho phep xoa node tren link list        
    47.                 if ( !uiMemoryManager::isLockPush() )
    48.                 {  
    49.                     uiList<void*>::uiListNode* pNode = (uiList<void*>::uiListNode*) header->pData;
    50.                     uiMemoryManager::remove( pNode );
    51.                 }
    52.             }
    53.  
    54.             // Neu header co trong globalList
    55.             free( (void*) b );     
    56.         }
    57. }
    Đã được chỉnh sửa lần cuối bởi ZCoder87 : 09-03-2009 lúc 09:47 PM.

  3. #3
    Ngày gia nhập
    01 2008
    Nơi ở
    Gameloft Studio
    Bài viết
    294

    Bây giờ sẽ tới thằng cốt lõi của vấn đề là uiMemoryManager.

    C++ Code:
    1. //////////////////////////////////////////////////
    2. // uiMemoryManager
    3. //////////////////////////////////////////////////
    4.     class uiMemoryManager
    5.     {
    6.     protected:
    7.         static uiList<void*>*   g_listMemory;
    8.         static bool         g_bLockPush;
    9.     public:
    10.  
    11.         // Dua 1 con tro void vao list
    12.         static uiList<void*>::uiListNode* push( void* p );
    13.        
    14.         // Khi lockPush = true -> new se khong duoc dua vao list
    15.         static void setLockPush( bool b );     
    16.         static bool isLockPush();
    17.  
    18.         // Don tat cac cac memory leak
    19.         static void destroy();
    20.     };

    Nó bao gồm:
    - 1 list memory g_listMemory
    - Và 1 flag g_bLockPush ( khi flag này bật thì đối tượng new ra sẽ không được đưa vào list ) -> bị lock

    Flag này để tránh hiện tượng dump Stack do đệ quy không dừng.

    Các thao tác push node vào linklist là cái cơ bản của cấu trúc dữ liệu nên mình sẽ không bàn tới.

    Hàm cần thiết cần mà ta quan tâm sẽ là Destroy -> Hủy các memory bị leak

    C++ Code:
    1. void uiMemoryManager::destroy()
    2. {
    3.             uiList<void*>::uiListNode* p;
    4.             uiMemoryHeader  *pHeader;
    5.  
    6.             unsigned char *pLeakData;
    7.            
    8.             g_bLockPush = true;
    9.            
    10.             // Lấy node đầu tiên và xóa dần lên trên
    11.             p = g_listMemory->getFirst();          
    12.             while (p)
    13.             {
    14.                 // Lấy giá trị của vùng memory
    15.                 pLeakData = (unsigned char*)p->getValue();
    16.  
    17.                 // Nếu nó chưa xóa (!=NULL)
    18.                 if ( pLeakData )
    19.                 {
    20.                     pHeader = (uiMemoryHeader*) pLeakData;
    21.  
    22.                     // Nếu nó là object -> delete -> chạy Destructor             
    23.                     if ( pHeader->bIsObject )
    24.                     {
    25.                         uiObject *pObject = (uiObject*) ( pLeakData+sizeof(uiMemoryHeader) );
    26.                         delete pObject;
    27.                     }
    28.                     else
    29.                     {
    30.                         delete (void*)(pLeakData + sizeof(uiMemoryHeader));
    31.                     }
    32.                 }
    33.  
    34.                 // Lấy con trỏ phía trước
    35.                 p = p->getNext();
    36.             }
    37.  
    38.             g_bLockPush = false;
    39.            
    40.             // Hủy global list
    41.             delete g_listMemory;
    42.             g_listMemory = 0;
    43. }

    Và một vấn đề nữa là tất cả các object phải kế thừa từ lớp uiObject của mình chỉ vì 1 constructor đánh dấu memory là object

    C++ Code:
    1. uiObject::uiObject()
    2. {
    3.             // Kich thuoc header
    4.             unsigned char sizeHeader = sizeof(uiMemoryHeader);
    5.  
    6.             // Lay vi tri của object
    7.             unsigned char *p = (unsigned char*) this;
    8.            
    9.             // Chuyen thanh vi tri header
    10.             p -= sizeHeader;
    11.             uiMemoryHeader* pHeader = (uiMemoryHeader*)p;
    12.            
    13.             // Danh dau memory la object
    14.             pHeader->bIsObject = 1;
    15. }



    Mình sẽ test lại chương trình
    C++ Code:
    1. #include <iostream>
    2. #include "uiMemoryManager.h"
    3.  
    4. using namespace std;
    5. using namespace uiClass;
    6.  
    7. // Macro DumpMemoryLeak cho visual C++ ///////////////////////////
    8.     #ifdef _DEBUG  
    9.  
    10.         // Ho tro Leak
    11.         #define _CRTDBG_MAP_ALLOC
    12.         #include <crtdbg.h>
    13.  
    14.         // UIDEBUG_DUMPLEAK
    15.         // In danh sach Memory bi LEAK
    16.         #define UIDEBUG_DUMPLEAK() _CrtDumpMemoryLeaks()  
    17.     #else
    18.         #define UIDEBUG_DUMPLEAK()
    19.     #endif
    20. /////////////////////////////////////////////////////////////////
    21.  
    22. class uiTestObject: public uiObject
    23. {
    24. protected:
    25. public:
    26.     uiTestObject()
    27.     {
    28.         cout << "uiTestObject khoi tao!" << endl;
    29.     }
    30.  
    31.     ~uiTestObject()
    32.     {
    33.         cout << "uiTestObject pha huy!" << endl;
    34.     }
    35.  
    36.     void printMe()
    37.     {
    38.         cout << "uiTestObject::printMe" << endl;
    39.     }
    40. }; 
    41.  
    42. void test()
    43. {
    44.     uiTestObject* p = new uiTestObject();  
    45.  
    46.     p->printMe();
    47.    
    48.     int *k = new int[300];
    49.     int *j = new int[100];
    50.  
    51.     delete k;
    52. }
    53.  
    54. int main()
    55. {
    56.     test();
    57.    
    58.     uiMemoryManager::destroy();
    59.     UIDEBUG_DUMPLEAK();
    60. }

    Kết quả của chương trình:
    Code:
    uiTestObject khoi tao!
    uiTestObject::printMe
    uiTestObject pha huy!
    Press any key to continue . . .

    Hàm uiMemoryManager::destroy() để ở cuối chương trình sẽ dọn tất cả => Chương trình ko hề leak Memory mặc dù mình cố ý không xóa đối đối tượng p và mảng j.

    Tuy nhiên vấn đề ở đây là ta chỉ mới làm dùm công việc của Hệ Điều Hành. Còn việc hao tốn bộ nhớ vẫn xảy ra. Và mình sẽ thảo luận tiếp về đề này ở project sau.

    Project đính kèm về uiMemoryManager

    Còn tiếp... Cập nhật sau vài giờ...
    Attached Files Attached Files
    Đã được chỉnh sửa lần cuối bởi ZCoder87 : 09-03-2009 lúc 09:55 PM.

  4. #4
    Ngày gia nhập
    01 2008
    Nơi ở
    Gameloft Studio
    Bài viết
    294

    Như vậy thì mình đã giải quyết vấn đề trước mắt là cho chương trình của ta không bị leak rồi.

    Tuy nhiên rõ ràng nó chỉ giúp chúng ta xóa bỏ những rác rưởi mà ta không biết dọn khi chương trình kết thúc. Chứ không thể đủ thông minh mà xác định được Object có còn sử dụng hay không? để xóa runtime như smart pointer được.

    C/C++ vẫn là C/C++ chứ ko thể là Java hay C#. Tuy nhiên không phải ko có cách.

    Đây là 1 ý tưởng mà mình nghỉ ra cũng trên chính cấu trúc thư mục của Windows.



    Khi mình xóa thư mục CDriver V3751AU thì rõ ràng HDH nó sẽ dọn rác luôn toàn bộ các thư mục con của nó bởi vì HDH nó biết chúng ta ko cần sử dụng các thư mục này nữa.

    Quay lại vấn đề memory.
    Giả sử bạn có đối tượng Lớp Học và đối tượng Học Sinh.

    Thực tế thì với cách quản lý thông thường thì ta phải hủy từng học sinh và lớp học thì chúng ta mới giải phóng hết bộ nhớ. Chính đây là sự phức tạp của C/C++. Nếu chúng ta chỉ xóa lớp học thôi thì học sinh vẫn còn tồn tại trên memory, điều này quá dở.

    Như vậy công việc của ta là khi Lớp Học bị hủy thì ta tự động hủy Học Sinh.

    Qua nhiều lần lập trình thì mình thấy rõ ràng trong ứng dụng thì memory đều được tổ chức theo dạng cây chứ ko riêng gì trường hợp của Lớp Học <-> Học sinh.

    Một ví dụ nữa nhé.
    Code:
    Application (Lớp ứng dụng)
         +-- Frame
                  +--- Button
                  +--- Checkbox
                  .
                  .
                  +--- Dialog
                            +--- Textbox
                            +--- DataGrip
                            .
                            .
                            .
    Như vậy ở trường hợp trên khi mình xóa Dialog thì nó sẽ tự động del hết TextBox, Datagrip và toàn bộ các memory được cấp phát trên trong lớp Dialog.

    Nếu kết hợp điều này thì ta sẽ tiết kiệm được thời gian đi delete đối tượng tới gần 80%.

    Vấn đề bây giờ là ta sẽ cài đặt như thế nào??? Xin hãy chờ đợi một ngày đẹp trời nào đó nếu mình có đủ hứng thú vì ngày hôm nay mình cày mệt quá rùi.. hic hic
    Đã được chỉnh sửa lần cuối bởi ZCoder87 : 09-03-2009 lúc 10:39 PM.

  5. #5
    Ngày gia nhập
    09 2007
    Bài viết
    724

    Đọc từ hôm qua tới nay mình học được rất nhiều từ bài viết này của zc thank cậu .

    Xin hãy chờ đợi một ngày đẹp trời nào đó nếu mình có đủ hứng thú
    Làm nốt luôn đi zc đang đọc tới đây mà chờ thì .... :((

  6. #6
    Ngày gia nhập
    04 2007
    Bài viết
    134

    Mặc định Memory - Bộ nhớ | Thảo luận về cách chống leakMemory

    C/C++ thì thường là ai malloc/new thì người đó free/delete, như kiểu ZCoder gọi là dạng cây gì đó. Thằng cha tạo ra thằng con thì sau này nó phải lo hậu sự cho thằng con (hoặc cùng lắm là kéo thằng con theo chết chung khi sắp qua đời).
    Còn như anh Java/C# thì có GC rồi, new thoải mái, pass reference qua lại lung tung, con đi đằng con, cha đi đằng cha. Nên nhiều lúc cái programming style ở 2 anh này nó khác C/C++.

    Cái uiMemoryManager cũng hay, học dc nhiều technique. Nhưng thực tế là ko dùng dc, như đã nói là nó chỉ làm dùm công việc của hệ điều hành.
    Còn giải quyết vụ này thì mình thấy smartpointer là dc người ta nói nhiều. Nhưng cách tốt nhất vẫn new/malloc thì ...nhớ free/delete

  7. #7
    Ngày gia nhập
    01 2008
    Nơi ở
    Gameloft Studio
    Bài viết
    294

    Trích dẫn Nguyên bản được gửi bởi Lugia
    Cái uiMemoryManager cũng hay, học dc nhiều technique. Nhưng thực tế là ko dùng dc, như đã nói là nó chỉ làm dùm công việc của hệ điều hành.
    Mình ko nghỉ là thực tế nó không sử dụng được mà nói chính xác hơn là bạn không biết dùng nó vào trường hợp nào?

    Còn việc dùng smart pointer thì nó chỉ là 1 giải pháp tạm thời thôi, chứ thật sự mình ko cho rằng nó có thể đơn giản công việc cho bạn.

    Trích dẫn Nguyên bản được gửi bởi zkday2686
    Làm nốt luôn đi zc đang đọc tới đây mà chờ thì .... :((
    Mình cố để dừng lại vài ngày để xem ý kiến của các bạn thôi. Và mình xem thử khi mình nói tới cách tổ chức memory theo dạng cây này thì có ai nảy sinh ra ý tưởng hay không ko mà??

    Mình sẽ tiếp tục project nhé.

    Thực ra thì cài đặt nó không khó, mà nói thẳng ra là mình đã cài đặt nó ở trên rồi, bây giờ chỉ có nâng cấp lên thôi.

    Cái ví dụ của mình là mình sử dụng 1 global List duy nhất. Bây giờ thì mình có nhiều cái global list như vậy thì sẽ chia nhỏ ra và phân bổ theo từng cấp.

    Lúc này thì trên mỗi đối tượng sẽ phải có 1 linkList -> kế thừa Object

    Mình sẽ cải tiến thêm ở uiMemoryManager ở chỗ nó có thể thay đổi được LinkList khi chúng ta new.
    Ví dụ như:
    C++ Code:
    1. // Lưu linklist hiện hành lại
    2. uiMemoryManager::PushLinkList();
    3.  
    4. // Sử dụng linkList của object này -> lúc này các đối tượng new sẽ gắn vào nó
    5. uiMemoryManager::UseLinkList( uiObject *pObject );
    6. // uiObject có phương thức getLinkList để lấy danh sách liên kết
    7. // pObject->getLinkList
    8.  
    9. new
    10. ...
    11. new
    12. ...
    13. new
    14. ...
    15.  
    16. // Sử dụng lại LinkList mặc định đã lưu
    17. uiMemoryManager::PopLinkList();

    Đại khái là vậy mà code thì cũng ko có gì khó. Tuy nhiên nó lại có vấn đề đó là không khéo coder lại quên gọi hàm uiMemoryManager::PopLinkList() để trả về linklist cũ (Ai lập trình ASM hay OpenGL chắc chết vì lỗi này nhiều lắm).

    Lúc này thì tránh được leaks memory thì lại nảy sinh ra lỗi khác mà chắc là lỗi này còn dễ chết hơn nữa.

    Trích dẫn Nguyên bản được gửi bởi ZCoder87
    Nên thôi! Mình không chơi cơ chế tự động này nữa mà dùng giải pháp là tạo cấu trúc cây object bằng tay luôn.

    Thôi thì tạm thời gác chuyện memory qua 1 bên để nhảm 1 tý.

    Mình sẽ đặt 1 câu hỏi: "Tại sao các đối tượng của .NET hay JAVA đều kế thừa lớp Object ?"

    Nó có rất nhiều ý nghĩa:
    - Cái ý nghĩa dễ thấy nhất đó chính là nó giúp ta lập trình nhanh với các đối tượng Collections.
    - Dễ dàng nâng cấp đối tượng.
    - Đóng gói đối tượng qua các interface class để giúp đoạn code chúng ta không bị ràng buộc lớp đối tượng -> Dễ nâng cấp, bảo trì...

    Tuy nhiên đó là cái mà dễ thấy nhất. Còn cái mà mình muốn nói nó không nằm ở bên trên.

    Bây giờ bạn hãy lật cuốn sách Design Pattern và xem lại cái Composite

    Hoặc liếc qua cái tut của NHC cũng được
    - [DesignPattern] Composite pattern



    Mẫu pattern này hướng dẫn ta tổ chức các đối tượng theo cấu trúc cây.

    Ở hình vẽ trên chúng ta sẽ coi:
    - COMPONENT chính là đối tượng chung -> Object.
    - Còn LEAF và COMPOSITE lại là các nhánh và lá -> Là các đối tượng sử dụng như: Window, Button, Control,....

    Nếu ta xét tiếp COMPOSITE thì nó lại có 1 danh sách OBJECT nữa ( tức là bản thân COMPOSITE có thể có tiếp COMPOSITE và LEAF ) Ví dụ như với bản thân đối tượng Window nó có thể có các Control, Button hay Window trong đó nữa....

    - Và cứ tiếp tục như vậy nó sẽ tạo nên cấu trúc cây.

    Mình sẽ cài đặt 1 class uiObject sao cho nó có LinkList (sử dụng linklist của STL)

    C++ Code:
    1. #include <list>
    2.  
    3. using std::list;
    4.  
    5.  
    6. class uiObject
    7. {
    8. private:
    9.          // Danh sách các đối tượng con của nó
    10.          list<uiObject*>                  *zm_refOject;
    11.  
    12.          // Vị trí của chính nó trên đối tượng cha
    13.          list<uiObject*>::iterator         zm_thisObject;
    14.  
    15.          // Object này có phải được cha quản lý hay không -> new
    16.          bool                              zm_bNew;
    17. protected:
    18.          uiObject::uiObject();     
    19.  
    20.          // destructor
    21.          // Nhiệm vụ hủy các child đã new trên object này
    22.          virtual ~uiObject();
    23.        
    24.          // ref
    25.          // Phương thức này sẽ giữ địa chỉ của child
    26.          template<class T>
    27.          T* ref(uiObject* pObject)
    28.          {
    29.                   // Nếu danh sách liên kết chưa được tạo -> tạo nó ra
    30.                   if ( zm_refOject == NULL )
    31.                            zm_refOject = new list<uiObject*>();
    32.  
    33.                   // Thêm đối tượng ở cuối danh sách
    34.                   zm_refOject->push_back(pObject);
    35.  
    36.                   // Vị trí của object child này chính là ở cuối danh sách
    37.                   pObject->zm_thisObject    = --zm_refOject->end();
    38.  
    39.                   // Object này được new -> được cha quản lý
    40.                   pObject->zm_bNew = true;
    41.  
    42.                   // Tra về object
    43.                   return (T*) pObject;
    44.          }
    45. }

    C++ Code:
    1. uiObject::uiObject()
    2. {
    3.     // Không khởi tạo list -> khởi tạo khi gọi hàm ref
    4.     zm_refOject = NULL;
    5.  
    6.     // Đối tượng này chưa được quản lý bởi cha
    7.     zm_bNew = false;
    8. }
    9.  
    10. uiObject::~uiObject()
    11. {
    12.     // Nếu nó được cha quản lý
    13.     if ( zm_bNew )
    14.     {
    15.         // Xóa node trên danh sách cha
    16.         // zm_thisObject là iterator của linklist
    17.         *zm_thisObject = NULL;
    18.     }
    19.    
    20.     // Nếu nó có danh sách con
    21.     if ( zm_refOject )
    22.     {
    23.         // Xóa từ đầu tới cuối
    24.         list<uiObject*>::iterator i = zm_refOject->begin();
    25.         while ( i != zm_refOject->end() )
    26.         {
    27.             // Nếu đối tượng i chưa xóa (!= NULL)
    28.             if ( *i )
    29.                 // Xóa đối tượng
    30.                 delete (*i);
    31.            
    32.             // Chuyển tới node tiếp theo
    33.             i++;
    34.         }
    35.         // Xóa toàn bộ node trên list
    36.         zm_refOject->clear();
    37.  
    38.         // Giải phóng tài nguyên đã cấp phát cho link
    39.         delete zm_refOject;
    40.     }  
    41.    
    42. }

    Đơn giản phải không nào? Bây giờ là cách sử dụng.
    C++ Code:
    1. class t1Object:public uiObject
    2. {
    3. protected:
    4.     int a[100];
    5. public:
    6. };
    7.  
    8. class t2Object:public uiObject
    9. {
    10. protected:
    11.     double a[100];
    12.     t1Object *t;
    13. public:
    14.     t2Object()
    15.     {
    16.         // Tạo liên kết cha - con
    17.         t = ref<t1Object>(    new t1Object()    );
    18.     }
    19. };
    20.  
    21. class testObject:public uiObject
    22. {
    23. protected:
    24.     t1Object *a;
    25.     t2Object *b;
    26. public:
    27.     testObject()
    28.     {
    29.         // Tạo liên kết cha - con
    30.         a = ref<t1Object>(      new t1Object()      );
    31.  
    32.         // Tạo liên kết cha - con
    33.         b = ref<t2Object>(      new t2Object()      );
    34.     }
    35.  
    36.  
    37. };
    38.  
    39. void t()
    40. {  
    41.     testObject *p = new testObject();
    42.  
    43.     // Khi delete thì toàn bộ con của nó sẽ bị hủy theo.
    44.     delete p;
    45.  
    46. }

    Chỉ cần bọc thêm hàm ref<class T>() với toán tử new thì toàn bộ các class sẽ tự tạo mối quan hệ cha con với nhau.

    Và chỉ cần xóa thằng cha là đủ. Con của nó sẽ tự động hủy.

    Tuy nhiên ta chỉ có thể quản lý các Object mà thôi. Còn với các kiểu thường như int[] thì chỉ có giải pháp của uiMemoryManager.

    Bài viết sẽ tạm dừng ở đây. Xin cám ơn bạn đã cố gắng ngồi đọc
    Đã được chỉnh sửa lần cuối bởi ZCoder87 : 11-03-2009 lúc 09:37 PM.

  8. #8
    Ngày gia nhập
    06 2009
    Bài viết
    14

    Mặc định Phát hiện memory leak đối với COM object

    Cám ơn bài viết của bạn, nhân đây tôi cũng post tiếp phần kiểm tra memory leak khi dùng COM object dựa trên hiện thực interface IMallocSpy.
    Lớp CMallocSpy này tui lấy trên mạng, và sửa lại chút xíu dựa trên bài viết ở trên. Tui cũng chưa nghiên cứu sâu về chủ đề này, sau này có gì mới sẽ post tiếp:

    đây là code của lớp CMallocSpy:

    File H:

    C++ Code:
    1. #ifndef _CMallocSpy_h
    2. #define _CMallocSpy_h
    3.  
    4. class CMallocSpy : public IMallocSpy
    5. {
    6. public:
    7.     CMallocSpy(void);
    8.     ~CMallocSpy(void);
    9.  
    10.     //
    11.     // IUnknown methods
    12.     //
    13.     STDMETHOD(QueryInterface) (REFIID riid, LPVOID *ppUnk);
    14.     STDMETHOD_(ULONG, AddRef) (void);
    15.     STDMETHOD_(ULONG, Release) (void);
    16.  
    17.     //
    18.     // IMallocSpy methods
    19.     //
    20.     STDMETHOD_(ULONG, PreAlloc) (ULONG cbRequest);
    21.     STDMETHOD_(void*, PostAlloc) (void* pActual);
    22.  
    23.     STDMETHOD_(void*, PreFree) (void* pRequest, BOOL fSpyed);
    24.     STDMETHOD_(void, PostFree) (BOOL fSpyed);
    25.  
    26.     STDMETHOD_(ULONG, PreRealloc) (void* pRequest, ULONG cbRequest,
    27.                                    void** ppNewRequest, BOOL fSpyed);
    28.     STDMETHOD_(void*, PostRealloc) (void* pActual, BOOL fSpyed);
    29.  
    30.     STDMETHOD_(void*, PreGetSize) (void* pRequest, BOOL fSpyed);
    31.     STDMETHOD_(ULONG, PostGetSize) (ULONG cbActual, BOOL fSpyed);
    32.  
    33.     STDMETHOD_(void*, PreDidAlloc) (void* pRequest, BOOL fSpyed);
    34.     STDMETHOD_(BOOL, PostDidAlloc) (void* pRequest, BOOL fSpyed, BOOL fActual);
    35.  
    36.     STDMETHOD_(void, PreHeapMinimize) (void);
    37.     STDMETHOD_(void, PostHeapMinimize) (void);
    38.  
    39.     //
    40.     // Utilities ...
    41.     //
    42.     void Clear();
    43.     void Dump();
    44.     void SetBreakAlloc(int allocNum);
    45.  
    46. protected:
    47.     enum
    48.     {
    49.         MAX_ALLOCATIONS = 100000   // cannot handle more than max
    50.     };
    51.  
    52.     int     m_headerSize;
    53.     int     m_cbRequest;
    54.     ULONG   m_cRef;
    55.    
    56.     int     m_breakAlloc;
    57.  
    58.     int     m_counter;
    59.     int     m_currentDump;
    60.     int     m_totalLeak;
    61.    
    62.  
    63.     void**  m_map;
    64.     size_t  m_mapSize;
    65.  
    66. };
    67.  
    68.  
    69. #endif   // _CMallocSpy_h

    File CPP:

    C++ Code:
    1. #include <windows.h>
    2. #include "cmallspy.h"
    3. #include <stdio.h>
    4.  
    5.  
    6. struct MemoryHeader
    7. {
    8.     int     nOrder;
    9.     int     nSizeMemory;
    10. };
    11.  
    12. // Constructor/Destructor
    13. CMallocSpy::CMallocSpy(void)
    14. {
    15.     m_cRef = 0;
    16.    
    17.     m_mapSize = MAX_ALLOCATIONS;
    18.     m_map = (void**)malloc(m_mapSize * sizeof(void*));
    19.     m_headerSize = sizeof(MemoryHeader);
    20.  
    21.     m_counter = 0;
    22.     m_currentDump = 0;
    23.     m_totalLeak = 0;
    24. }
    25.  
    26.  
    27. CMallocSpy::~CMallocSpy(void)
    28. {
    29.     delete [] m_map;
    30. }
    31.  
    32. //IUnknown interface ...
    33. HRESULT CMallocSpy::QueryInterface(REFIID riid, LPVOID *ppUnk)
    34. {
    35.     HRESULT hr = S_OK;
    36.  
    37.     if (IsEqualIID(riid, IID_IUnknown))
    38.     {
    39.         *ppUnk = (IUnknown *) this;
    40.     }
    41.     else if (IsEqualIID(riid, IID_IMallocSpy))
    42.     {
    43.         *ppUnk =  (IMalloc *) this;
    44.     }
    45.     else
    46.     {
    47.         *ppUnk = NULL;
    48.         hr =  E_NOINTERFACE;
    49.     }
    50.     AddRef();
    51.     return hr;
    52. }
    53.  
    54.  
    55. ULONG CMallocSpy::AddRef(void)
    56. {
    57.     return ++m_cRef;
    58. }
    59.  
    60. ULONG CMallocSpy::Release(void)
    61. {
    62.     ULONG cRef;
    63.  
    64.     cRef = --m_cRef;
    65.     if (cRef == 0)
    66.     {
    67.         delete this;
    68.     }
    69.     return cRef;
    70. }
    71.  
    72. // IMallocSpy methods ...
    73. ULONG CMallocSpy::PreAlloc(ULONG cbRequest)
    74. {
    75.     m_cbRequest = cbRequest;
    76.     return cbRequest + m_headerSize;
    77. }
    78.  
    79. void *CMallocSpy::PostAlloc(void *pActual)
    80. {
    81.     if (m_breakAlloc == m_counter)
    82.         ::DebugBreak();
    83.  
    84.     // Store the allocation counter and note that this allocation
    85.     // is active in the map.
    86.     MemoryHeader* pMemHeader = (MemoryHeader*)pActual;
    87.     pMemHeader->nOrder = m_counter;
    88.     pMemHeader->nSizeMemory = m_cbRequest;
    89.  
    90.     m_map[m_counter] = (void*)pMemHeader;
    91.  
    92.     m_counter++;
    93.     return (void*)((BYTE*)pActual + m_headerSize);
    94. }
    95.  
    96. void *CMallocSpy::PreFree(void *pRequest, BOOL fSpyed)
    97. {
    98.     if (pRequest == NULL)
    99.     {
    100.         return NULL;
    101.     }
    102.  
    103.     if (fSpyed)
    104.     {
    105.         // Mark the allocation as inactive in the map.
    106.         pRequest = (void*)(((BYTE*)pRequest) - m_headerSize);
    107.  
    108.         MemoryHeader* pMemHeader = (MemoryHeader*)pRequest;
    109.         m_map[pMemHeader->nOrder] = NULL;
    110.  
    111.         return pRequest;
    112.     }
    113.     else
    114.     {
    115.         return pRequest;
    116.     }
    117. }
    118.  
    119.  
    120. void CMallocSpy::PostFree(BOOL fSpyed)
    121. {
    122.     return;
    123. }
    124.  
    125.  
    126. ULONG CMallocSpy::PreRealloc(void *pRequest,
    127.                              ULONG cbRequest,
    128.                              void **ppNewRequest,
    129.                              BOOL fSpyed)
    130. {
    131.     if (fSpyed  &&  pRequest != NULL)
    132.     {
    133.         // Mark the allocation as inactive in the map since IMalloc::Realloc()
    134.         // frees the originally allocated block.
    135.         m_cbRequest = cbRequest;
    136.  
    137.         BYTE* pActual = (BYTE*)pRequest - m_headerSize;
    138.         MemoryHeader* pMemHeader = (MemoryHeader*)pActual;
    139.         m_map[pMemHeader->nOrder] = NULL;
    140.  
    141.         *ppNewRequest = (void*)pActual;
    142.         return cbRequest + m_headerSize;
    143.     }
    144.     else
    145.     {
    146.         *ppNewRequest = pRequest;
    147.         return cbRequest;
    148.     }
    149. }
    150.  
    151.  
    152. void *CMallocSpy::PostRealloc(void *pActual, BOOL fSpyed)
    153. {
    154.     if (fSpyed)
    155.     {
    156.         if (m_breakAlloc == m_counter)
    157.             ::DebugBreak();
    158.  
    159.         // Store the allocation counter and note that this allocation
    160.         // is active in the map.
    161.         MemoryHeader* pMemHeader = (MemoryHeader*)pActual;
    162.         pMemHeader->nOrder = m_counter;
    163.         pMemHeader->nSizeMemory = m_cbRequest;
    164.  
    165.         m_map[m_counter] = (void*)pMemHeader;
    166.  
    167.         m_counter++;
    168.         return (void*)((BYTE*)pActual + m_headerSize);
    169.     }
    170.     else
    171.     {
    172.         return pActual;
    173.     }
    174. }
    175.  
    176.  
    177. void *CMallocSpy::PreGetSize(void *pRequest, BOOL fSpyed)
    178. {
    179.     if (fSpyed)
    180.         return (void *) (((BYTE *) pRequest) - m_headerSize);
    181.     else
    182.         return pRequest;
    183. }
    184.  
    185.  
    186. ULONG CMallocSpy::PostGetSize(ULONG cbActual, BOOL fSpyed)
    187. {
    188.     if (fSpyed)
    189.         return cbActual - m_headerSize;
    190.     else
    191.         return cbActual;
    192. }
    193.  
    194.  
    195. void *CMallocSpy::PreDidAlloc(void *pRequest, BOOL fSpyed)
    196. {
    197.     if (fSpyed)
    198.         return (void *) (((BYTE *) pRequest) - m_headerSize);
    199.     else
    200.         return pRequest;
    201. }
    202.  
    203.  
    204. BOOL CMallocSpy::PostDidAlloc(void *pRequest, BOOL fSpyed, BOOL fActual)
    205. {
    206.     return fActual;
    207. }
    208.  
    209.  
    210. void CMallocSpy::PreHeapMinimize(void)
    211. {
    212.     return;
    213. }
    214.  
    215.  
    216. void CMallocSpy::PostHeapMinimize(void)
    217. {
    218.     return;
    219. }
    220.  
    221. // Utilities ...
    222. void CMallocSpy::SetBreakAlloc(int allocNum)
    223. {
    224.     m_breakAlloc = allocNum;
    225. }
    226.  
    227.  
    228. void CMallocSpy::Clear()
    229. {
    230.     //memset(m_map, 0, m_mapSize);
    231.     m_counter = 0;
    232. }
    233.  
    234.  
    235. void CMallocSpy::Dump()
    236. {
    237.     WCHAR wbuff[256];
    238.  
    239.     ::OutputDebugString(L"CMallocSpy dump ->\n");
    240.  
    241.     int thisLeak = 0;
    242.     for (; m_currentDump < m_counter; m_currentDump++)
    243.     {
    244.         if (m_map[m_currentDump] != NULL)
    245.         {
    246.             MemoryHeader * pMemHeader = (MemoryHeader*)m_map[m_currentDump];
    247.             swprintf_s(wbuff, L" IMalloc memory leak %d bytes at [%d]\n",
    248.                 pMemHeader->nSizeMemory, pMemHeader->nOrder);
    249.             thisLeak += pMemHeader->nSizeMemory;
    250.             ::OutputDebugString(wbuff);
    251.         }
    252.     }
    253.     m_totalLeak += thisLeak;
    254.     swprintf_s(wbuff, L"More %d bytes leak.\n", thisLeak);
    255.     ::OutputDebugString(wbuff);
    256.     swprintf_s(wbuff, L"Totally %d bytes leak.\n", m_totalLeak);
    257.     ::OutputDebugString(wbuff);
    258.  
    259.     ::OutputDebugString(L"CMallocSpy dump complete.\n");
    260. }

    Cách dùng:
    Đặt đoạn code này chỗ bắt đầu dùng COM:

    C++ Code:
    1. pMallocSpy = new CMallocSpy();
    2. pMallocSpy->AddRef();
    3. ::CoRegisterMallocSpy(g_pMallocSpy);

    Đặt đoạn code này chỗ cần in ra tình trang memory leak:

    C++ Code:
    1. pMallocSpy->Dump();

    Đặt đoạn code này khi kết thúc kiểm tra memory leak:

    C++ Code:
    1. ::CoRevokeMallocSpy();
    2. pMallocSpy->Release();
    3. delete g_pMallocSpy;
    4. pMallocSpy = NULL;

    PS: cho hỏi làm sao cho code này thành code C++ như ở trên?
    Đã được chỉnh sửa lần cuối bởi wolverine : 25-06-2009 lúc 09:15 AM.

  9. #9
    Ngày gia nhập
    10 2011
    Bài viết
    552

    Ôi sao cái này ko ai cho đưa lên Stick nhỉ @@
    Um Mani Padme Hum...!!

  10. #10
    Ngày gia nhập
    01 2012
    Bài viết
    24

    @ZCoder87: Một trong những philosophy của C++ là "don't pay for what you don't use". Mỗi object của cậu bị đội lên 10bytes chưa kể overhead. Hơn nữa nó cũng chẳng có tác dụng gì lắm; cái uiMemoryManager::destroy() chỉ có thể gọi khi chương trình kết thúc vì nó chém tất cả các object còn sống bất kể có reference hay không => gc thì cũng chả phải. Mà khi chương trình kết thúc thì đằng nào OS nó chả free hộ. Cuối cùng kết quả đạt được chỉ là cho khỏi bị UIDEBUG_DUMPLEAK() nó chửi?

    Tổ chức theo dạng tree không thể thay thế smart pointer được vì đâu phải các object đều có quan hệ theo kiểu cha con? Một object có thể được share giữa nhiều object khác, tưởng tượng như cậu có một shorcut tới một thư mục trong một thư mục khác, nếu destroy cái thư mục cha của thư mục đó đi thì shorcut của cậu trở thành dangling shorcut trỏ tới một thằng đã chết nhăn răng. Reference counting là cần thiết trong trường hợp này.

    Mà quan hệ cha con thì thứ tự release cũng là một điều cần quan tâm vì nó còn liên quan tới destructor. Lúc đó logic trong destructor sẽ phải chuyển sang một method khác kiểu dispose() và cậu vẫn phải tự call trong những trường hợp thứ tự là quan trọng.

    Theo tớ thì tốt nhất là nên dừng ở mức detect + report thôi, đừng xa đà vào management làm gì. Debug mode thì thêm cái header chứa những thông tin như __FILE__, __LINE__, __FUNCTION__ và thay uiMemoryManager::destroy() bằng uiMemoryManager::reportLeaks() cho coder biết để fix thì hay hơn.
    Tuesday 03 January 2012
    Battery Level, 69%

Các đề tài tương tự

  1. Giải thuật Thảo luận giải Sudoku bằng phương pháp suy luận logic
    Gửi bởi BDK trong diễn đàn Thắc mắc CTDL & Giải thuật
    Trả lời: 1
    Bài viết cuối: 26-03-2014, 11:05 PM
  2. Game Đọc memory bằng API như thế nào?
    Gửi bởi tuandoi1 trong diễn đàn Thắc mắc lập trình C#
    Trả lời: 13
    Bài viết cuối: 27-05-2012, 11:08 PM
  3. Trả lời: 0
    Bài viết cuối: 01-03-2012, 10:41 PM
  4. Cấp phát memory ở 1 tiến trình khác như thế nào
    Gửi bởi langman trong diễn đàn Windows API, Hooking, xử lý Windows Message
    Trả lời: 2
    Bài viết cuối: 04-03-2011, 11:41 PM

Quyền hạn của bạn

  • Bạn không thể gửi đề tài mới
  • Bạn không thể gửi bài trả lời
  • Bạn không thể gửi các đính kèm
  • Bạn không thể chỉnh sửa bài viết của bạn