2009-04-28 61 views
34

Tôi, và tôi nghĩ rằng nhiều người khác, đã có thành công lớn bằng cách sử dụng con trỏ thông minh để bọc các hoạt động bộ nhớ không an toàn trong C++, bằng cách sử dụng những thứ như RAII, et cetera. Tuy nhiên, việc quản lý bộ nhớ gói dễ thực hiện hơn khi bạn có các destructors, classes, operator overloading, et cetera.Con trỏ thông minh/quản lý bộ nhớ an toàn cho C?

Đối với người nào đó viết bằng C99 thô, bạn có thể chỉ định (không có ý định chơi chữ) để giúp quản lý bộ nhớ an toàn?

Cảm ơn.

Trả lời

12

Rất khó để xử lý con trỏ thông minh trong nguyên C, vì bạn không có cú pháp ngôn ngữ để sao lưu mức sử dụng. Hầu hết các nỗ lực tôi đã nhìn thấy không thực sự làm việc, vì bạn không có những lợi thế của destructors chạy khi các đối tượng rời khỏi phạm vi, mà thực sự là những gì làm cho con trỏ thông minh hoạt động.

Nếu bạn thực sự lo lắng về điều này, bạn có thể muốn xem xét chỉ trực tiếp sử dụng garbage collector và bỏ qua yêu cầu con trỏ thông minh hoàn toàn.

+0

Làm thế nào nó hoạt động? Làm thế nào nó có thể theo dõi các phép gán con trỏ? – Calmarius

+0

@Calmarius Có nhiều cách khác nhau mà chúng hoạt động. Xem: http://en.wikipedia.org/wiki/Garbage_collection_(computer_science) –

+0

Tôi hiểu. Tôi hỏi về GC bạn đã liên kết. Nó tuyên bố nó hoạt động trên các chương trình C chưa sửa đổi, bằng cách thay thế chỉ malloc và realloc. Nhưng làm thế nào nó xác định vị trí các con trỏ trỏ đến khối được phân bổ? Chúng có thể được sao chép xung quanh trong chương trình. – Calmarius

3

Công cụ phân tích mã tĩnh như splint hoặc Gimpel PC-Lint có thể giúp bạn ở đây - bạn thậm chí có thể thực hiện "ngăn ngừa" vừa phải bằng cách nối chúng vào máy chủ tạo kiểu "tích hợp liên tục" tự động của bạn. (Bạn không có một trong những, phải không? Grin :)

Có khác (một số đắt hơn) biến thể về chủ đề này quá ...

+0

+1 Cuộc gọi tốt trên các công cụ kiểm tra mã tĩnh. –

2

Nếu bạn đang mã hóa trong Win32 bạn có thể sử dụng structured exception handling để thực hiện điều gì đó tương tự. Bạn có thể làm một cái gì đó như thế này:

foo() { 
    myType pFoo = 0; 
    __try 
    { 
     pFoo = malloc(sizeof myType); 
     // do some stuff 
    } 
    __finally 
    { 
     free pFoo; 
    } 
} 

Trong khi không khá dễ dàng như RAII, bạn có thể thu thập tất cả mã dọn dẹp ở một nơi và đảm bảo rằng mã được thực hiện.

8

Một cách tiếp cận khác mà bạn có thể muốn xem xét là phương pháp tiếp cận bộ nhớ được gộp chung là Apache uses. Điều này hoạt động đặc biệt tốt nếu bạn có sử dụng bộ nhớ động được liên kết với một yêu cầu hoặc đối tượng sống ngắn khác. Bạn có thể tạo một nhóm trong cấu trúc yêu cầu của bạn và đảm bảo rằng bạn luôn phân bổ bộ nhớ từ nhóm và sau đó giải phóng nhóm khi bạn xử lý xong yêu cầu. Nó không có âm thanh mạnh mẽ như nó là một khi bạn đã sử dụng nó một chút. Nó gần như là tốt đẹp như RAII.

4

Bạn không thể làm con trỏ thông minh trong C vì nó không cung cấp cú pháp cần thiết, nhưng bạn có thể tránh rò rỉ bằng thực hành. Viết mã phát hành tài nguyên ngay sau khi bạn phân bổ nó. Vì vậy, bất cứ khi nào bạn viết malloc, bạn nên viết free tương ứng ngay trong phần dọn dẹp.

Trong CI thấy 'GOTO dọn dẹp' mô hình rất nhiều:

int foo() 
{ 
    int *resource = malloc(1000); 
    int retVal = 0; 
    //... 
    if (time_to_exit()) 
    { 
     retVal = 123; 
     goto cleanup; 
    } 
cleanup: 
    free(resource); 
    return retVal; 
} 

Trong C chúng tôi cũng sử dụng rất nhiều bối cảnh đó phân bổ công cụ, quy tắc tương tự có thể được áp dụng cho điều đó quá:

int initializeStuff(Stuff *stuff) 
{ 
    stuff->resource = malloc(sizeof(Resource)); 
    if (!stuff->resource) 
    { 
     return -1; ///< Fail. 
    } 
    return 0; ///< Success. 
} 

void cleanupStuff(Stuff *stuff) 
{ 
    free(stuff->resource); 
} 

Điều này tương tự với các nhà xây dựng đối tượng và trình phá hủy. Miễn là bạn không cho đi các nguồn tài nguyên được phân bổ cho các đối tượng khác, nó sẽ không bị rò rỉ và con trỏ sẽ không lơ lửng.

Không khó để viết trình phân bổ tùy chỉnh theo dõi phân bổ và viết các khối bị rò rỉ atexit.

Nếu bạn cần đưa con trỏ đến tài nguyên được phân bổ, bạn có thể tạo ngữ cảnh bao bọc cho nó và mỗi đối tượng sở hữu ngữ cảnh trình bao thay vì tài nguyên. Những trình bao bọc này chia sẻ tài nguyên và một đối tượng truy cập, theo dõi việc sử dụng và giải phóng các đối tượng khi không ai sử dụng nó. Đây là cách hoạt động của các công cụ shared_ptrweak_ptr của C++ 11. Nó được viết chi tiết hơn ở đây: How does weak_ptr work?

2

Bạn có thể xác định macro, ví dụ BEGIN và END, được sử dụng thay cho niềng răng và kích hoạt tự động hủy tài nguyên thoát khỏi phạm vi của chúng. Điều này đòi hỏi rằng tất cả các tài nguyên như vậy được chỉ ra bởi các con trỏ thông minh cũng chứa con trỏ đến hàm hủy của đối tượng. Trong triển khai của tôi, tôi giữ một ngăn con trỏ thông minh trong bộ nhớ heap, ghi nhớ con trỏ ngăn xếp khi nhập vào một phạm vi và gọi trình phá hủy của tất cả các tài nguyên phía trên con trỏ ngăn xếp được ghi nhớ ở lối ra phạm vi (END hoặc thay thế macro để trả về). Điều này làm việc tốt ngay cả khi cơ chế ngoại lệ setjmp/longjmp được sử dụng, và dọn sạch tất cả các phạm vi trung gian giữa khối catch và phạm vi mà ngoại lệ cũng được ném ra. Xem https://github.com/psevon/exceptions-and-raii-in-c.git để thực hiện.

12

Câu hỏi này hơi cũ, nhưng tôi nghĩ tôi sẽ dành thời gian để liên kết tới smart pointer library cho trình biên dịch GNU (GCC, Clang, ICC, MinGW, ...).

Triển khai này dựa trên thuộc tính biến cleanup, phần mở rộng GNU, để tự động giải phóng bộ nhớ khi không nằm ngoài phạm vi và như vậy, là không ISO C99, nhưng C99 có phần mở rộng GNU.

Ví dụ:

simple1.c:

#include <stdio.h> 
#include <csptr/smart_ptr.h> 

int main(void) { 
    smart int *some_int = unique_ptr(int, 1); 

    printf("%p = %d\n", some_int, *some_int); 

    // some_int is destroyed here 
    return 0; 
} 

Compilation & Valgrind phiên:

$ gcc -std=gnu99 -o simple1 simple1.c -lcsptr 
$ valgrind ./simple1 
==3407== Memcheck, a memory error detector 
==3407== Copyright (C) 2002-2013, and GNU GPL\'d, by Julian Seward et al. 
==3407== Using Valgrind-3.10.0 and LibVEX; rerun with -h for copyright info 
==3407== Command: ./simple1 
==3407== 
0x53db068 = 1 
==3407== 
==3407== HEAP SUMMARY: 
==3407==  in use at exit: 0 bytes in 0 blocks 
==3407== total heap usage: 1 allocs, 1 frees, 48 bytes allocated 
==3407== 
==3407== All heap blocks were freed -- no leaks are possible 
==3407== 
==3407== For counts of detected and suppressed errors, rerun with: -v 
==3407== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) 
0
Sometimes i use this approach and it seems good :) 

Object *construct(type arg, ...){ 

    Object *__local = malloc(sizeof(Object)); 
    if(!__local) 
     return NULL; 
    __local->prop_a = arg; 
    /* blah blah */ 


} // constructor 

void destruct(Object *__this){ 

    if(__this->prop_a)free(this->prop_a); 
    if(__this->prop_b)free(this->prop_b); 

} // destructor 

Object *o = __construct(200); 
if(o != NULL) 
    ;; 

// use 

destruct(o); 

/* 
    done ! 
*/ 
Các vấn đề liên quan