2010-07-27 16 views
16

Giả sử tôi viết mã của mình rất phòng thủ và luôn kiểm tra kiểu trả về từ tất cả các chức năng mà tôi gọi.Làm thế nào để tránh chuỗi dài miễn phí (hoặc xóa) sau mỗi lần kiểm tra lỗi trong C?

Vì vậy, tôi đi như:

char* function() { 
    char* mem = get_memory(100); // first allocation 
    if (!mem) return NULL; 

    struct binder* b = get_binder('regular binder'); // second allocation 
    if (!b) { 
     free(mem); 
     return NULL; 
    } 

    struct file* f = mk_file(); // third allocation 
    if (!f) { 
     free(mem); 
     free_binder(b); 
     return NULL; 
    } 

    // ... 
} 

Thông báo một cách nhanh chóng như thế nào free() mọi thứ trở nên ngoài tầm kiểm soát. Nếu một số chức năng không thành công, tôi phải giải phóng mọi phân bổ trước đây. Mã nhanh chóng trở nên xấu xí và tất cả những gì tôi làm là sao chép tất cả mọi thứ. Tôi trở thành một lập trình sao chép/dán, thậm chí tệ hơn, nếu ai đó thêm một tuyên bố ở đâu đó ở giữa, anh ta phải sửa đổi tất cả mã bên dưới để gọi số free() để bổ sung.

Trình lập trình C có kinh nghiệm về vấn đề này như thế nào? Tôi không thể tìm ra bất cứ thứ gì.

Cảm ơn, Boda Cydo.

Trả lời

34

Bạn có thể xác định nhãn mới để giải phóng tài nguyên và sau đó bạn có thể GOTO bất kỳ khi nào mã của bạn không thành công.

char* function() 
{ 
    char* retval = NULL; 
    char* mem = get_memory(100); // first allocation 
    if (!mem) 
     goto out_error; 

    struct binder* b = get_binder('regular binder'); // second allocation 
    if (!b) 
     goto out_error_mem; 

    struct file* f = mk_file(); // third allocation 
    if (!f) 
     goto out_error_b; 

    /* ... Normal code path ... */ 
    retval = good_value; 

    out_error_b: 
    free_binder(b); 
    out_error_mem: 
    free(mem); 
    out_error: 
    return retval; 
} 

Lỗi quản lý với GOTO đã được thảo luận ở đây: Valid use of goto for error management in C?

+6

+1 - trong khi không nổi tiếng ở trường, đây là thực tế nhất và được thực hiện tất cả các thời gian. đặt tất cả các câu lệnh miễn phí của bạn vào một nhãn ngay trước khi rời khỏi hàm, kiểm tra biến trạng thái trước tiên nếu cần và sử dụng GOTO. –

+0

Tính năng này hoạt động như thế nào? Tôi chưa bao giờ nghe về kỹ thuật này. Bạn có thể minh họa nó không? – bodacydo

+0

Có bạn đi, nó sẽ là một cái gì đó như thế. – karlphillip

5

Tôi biết tôi sẽ bị hành hình cho điều này, nhưng tôi có một người bạn cho biết họ đã sử dụng goto cho điều đó.

Sau đó, anh ấy nói với tôi rằng điều đó là không đủ trong hầu hết các trường hợp và giờ đây anh ấy đã sử dụng setjmp()/longjmp(). Về cơ bản, ông đã phát minh ra ngoại lệ của C++ nhưng với sự thanh lịch ít hơn nhiều.

Điều đó nói rằng, vì gotothể làm việc, bạn có thể cấu trúc lại nó thành một cái gì đó không sử dụng goto, nhưng thụt đầu dòng sẽ thoát khỏi tay nhanh:

char* function() { 
    char* result = NULL; 
    char* mem = get_memory(100); 
    if(mem) { 
     struct binder* b = get_binder('regular binder'); 
     if(b) { 
      struct file* f = mk_file(); 
      if (f) { 
       // ... 
      } 
      free(b); 
     } 
     free(mem); 
    } 
    return result; 
} 

BTW, tán biến địa phương các tuyên bố xung quanh khối như vậy không phải là tiêu chuẩn C.

Bây giờ, nếu bạn nhận ra rằng free(NULL); được xác định bằng tiêu chuẩn C là không làm gì, bạn có thể đơn giản hóa lồng làm tổ:

char* function() { 
    char* result = NULL; 

    char* mem = get_memory(100); 
    struct binder* b = get_binder('regular binder'); 
    struct file* f = mk_file(); 

    if (mem && b && f) { 
     // ... 
    } 

    free(f); 
    free(b); 
    free(mem); 

    return result; 
} 
+0

Làm rõ: phân tán các khai báo biến cục bộ xung quanh khối như trong mã của bạn không phải là tiêu chuẩn C, nhưng ví dụ đầu tiên của tôi đáp ứng tiêu chuẩn vì mỗi khối dấu ngoặc đơn có các khai báo đầu tiên. –

+0

Tôi nghĩ đến năm nay C99 đã trở thành tiêu chuẩn: http://en.wikipedia.org/wiki/C99#Design –

+0

C đã cho phép nó trong 10 năm nay. – dreamlax

5

Trong khi tôi ngưỡng mộ cách tiếp cận của bạn để mã hóa phòng thủ và đó là một điều tốt. Và mọi Lập trình viên C đều phải có tâm lý đó, nó cũng có thể áp dụng cho các ngôn ngữ khác ...

Tôi phải nói đây là một điều hữu ích về GOTO, mặc dù người thuần túy sẽ nói cách khác, đó sẽ là tương đương với khối finally nhưng có một Gotcha đặc biệt mà tôi có thể thấy có ...

karlphillip 's code đang gần hoàn thành nhưng .... giả sử dụng chức năng đã được thực hiện như thế này

char* function() { 
     struct file* f = mk_file(); // third allocation 
     if (!f) goto release_resources; 
     // DO WHATEVER YOU HAVE TO DO.... 
     return some_ptr; 
    release_resources: 
     free(mem); 
     free_binder(b); 
     return NULL; 
    } 

Hãy cẩn thận! !! Điều này sẽ phụ thuộc vào thiết kế và mục đích của chức năng mà bạn thấy phù hợp, đặt sang một bên .. nếu bạn đã trở về từ chức năng như thế, bạn có thể kết thúc rơi qua nhãn release_resources ....có thể gây ra lỗi nhỏ, tất cả các tham chiếu đến con trỏ trên heap đều biến mất và có thể kết thúc trả về rác ... vì vậy hãy đảm bảo nếu bạn đã cấp phát bộ nhớ và trả lại bộ nhớ, hãy sử dụng từ khóa return ngay trước nhãn bộ nhớ có thể biến mất ... hoặc tạo một rò rỉ bộ nhớ ....

+0

Tôi đồng ý, tôi có xu hướng muốn duy trì biến trạng thái và có tất cả các đường dẫn thông qua thoát hàm tại nhãn, kiểm tra biến trạng thái để giải phóng mọi phân bổ bộ nhớ hoặc xóa các tệp tạm thời, v.v. –

+0

@Brandon: Tuyệt đối - đó là một điều tinh tế mà có thể đi lên ... và bạn đi wtf đã xảy ra ... "oooh chết tiệt ... Tôi quên nó rơi qua nhãn ... sighup": D – t0mm13b

2

Nếu bạn muốn làm điều đó mà không goto, đây là một phương pháp cũng quy mô:

char *function(char *param) 
{ 
    int status = 0; // valid is 0, invalid is 1 
    char *result = NULL; 
    char *mem = NULL: 
    struct binder* b = NULL; 
    struct file* f = NULL: 

    // check function parameter(s) for validity 
    if (param == NULL) 
    { 
     status = 1; 
    } 

    if (status == 0) 
    { 
     mem = get_memory(100); // first allocation 

     if (!mem) 
     { 
      status = 1; 
     } 
    } 

    if (status == 0) 
    { 
     b = get_binder('regular binder'); // second allocation 

     if (!b) 
     { 
      status = 1; 
     } 
    } 

    if (status == 0) 
    { 
     f = mk_file(); // third allocation 

     if (!f) 
     { 
      status = 1; 
     } 
    } 

    if (status == 0) 
    { 
     // do some useful work 
     // assign value to result 
    } 

    // cleanup in reverse order 
    free(f); 
    free(b); 
    free(mem); 

    return result; 
} 
+6

Điều này là ghê gớm hơn bất kỳ goto bao giờ có thể . –

+0

@R: Mắt của khán giả. Một số người xem xét 'goto' một tiêu chí để không đánh giá mã. –

+6

Đó là bởi vì một số người không biết suy nghĩ nghiêm túc như thế nào. Không có công trình nào luôn nguy hiểm. Một số cấu trúc có nhiều khả năng gây ra vấn đề hơn các cấu trúc khác. Người ta phải xem xét các chi phí và lợi ích, chứ không phải là mù quáng nói "không bao giờ sử dụng X". – siride

2

Nếu cấu trúc dữ liệu của bạn rất phức tạp/lồng nhau, một goto duy nhất có thể không đủ, trong trường hợp này tôi đề nghị một cái gì đó như:

mystruct = malloc(sizeof *mystruct); 
if (!mystruct) goto fail1; 
mystruct->data = malloc(100); 
if (!mystruct->data) goto fail2; 
foo = malloc(sizeof *foo); 
if (!foo) goto fail2; 
... 
return mystruct; 
fail2: 
free(mystruct->data); 
fail1: 
free(mystruct); 

Ví dụ thế giới thực sẽ phức tạp hơn và có thể liên quan đến nhiều cấp độ cấu trúc lồng nhau, danh sách liên kết, v.v. Lưu ý ở đây không thể gọi free(mystruct->data); (vì dereferencing thành phần mystruct không hợp lệ) nếu malloc không thành công.

3

Bạn cũng có thể lấy cách tiếp cận ngược lại và kiểm tra thành công:

struct binder* b = get_binder('regular binder'); // second allocation 
if(b) { 
    struct ... *c = ... 
    if(c) { 
     ... 
    } 
    free(b); 
} 
+0

và có biến "kết quả" cho lợi tức cuối cùng. –

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