Rò rỉ có thể tiếp cận được phát hiện bởi Valgrind


154

Tất cả các chức năng được đề cập trong khối này là các chức năng thư viện. Làm thế nào tôi có thể khắc phục rò rỉ bộ nhớ này?

Nó được liệt kê dưới danh mục " Vẫn có thể truy cập ". (Có thêm 4 cái nữa, rất giống nhau, nhưng có kích cỡ khác nhau)

 630 bytes in 1 blocks are still reachable in loss record 5 of 5
    at 0x4004F1B: calloc (vg_replace_malloc.c:418)
    by 0x931CD2: _dl_new_object (dl-object.c:52)
    by 0x92DD36: _dl_map_object_from_fd (dl-load.c:972)
    by 0x92EFB6: _dl_map_object (dl-load.c:2251)
    by 0x939F1B: dl_open_worker (dl-open.c:255)
    by 0x935965: _dl_catch_error (dl-error.c:178)
    by 0x9399C5: _dl_open (dl-open.c:584)
    by 0xA64E31: do_dlopen (dl-libc.c:86)
    by 0x935965: _dl_catch_error (dl-error.c:178)
    by 0xA64FF4: __libc_dlopen_mode (dl-libc.c:47)
    by 0xAE6086: pthread_cancel_init (unwind-forcedunwind.c:53)
    by 0xAE61FC: _Unwind_ForcedUnwind (unwind-forcedunwind.c:126)

Catch: Một khi tôi chạy chương trình của mình, nó không bị rò rỉ bộ nhớ, nhưng nó có thêm một dòng trong đầu ra Valgrind, vốn không có mặt trước đây:

Loại bỏ các sym ở 0x5296fa0-0x52af438 trong /lib/libgcc_s-4.4.4-20100630.so.1 do munmap ()

Nếu rò rỉ không thể được khắc phục, ai đó ít nhất có thể giải thích lý do tại sao dòng munmap () khiến Valgrind báo cáo rò rỉ 0 "vẫn có thể truy cập" không?

Biên tập:

Đây là một mẫu thử nghiệm tối thiểu:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void *runner(void *param) {
    /* some operations ... */
    pthread_exit(NULL);
}

int n;

int main(void) {

    int i;
    pthread_t *threadIdArray;

    n=10; /* for example */

    threadIdArray = malloc((n+n-1)*sizeof(pthread_t));  

    for(i=0;i<(n+n-1);i++) {
        if( pthread_create(&threadIdArray[i],NULL,runner,NULL) != 0 ) {
            printf("Couldn't create thread %d\n",i);
            exit(1);
        }
    }


    for(i=0;i<(n+n-1);i++) {
        pthread_join(threadIdArray[i],NULL);
    }

    free(threadIdArray);

    return(0);
}

Chạy với:

valgrind -v --leak-check=full --show-reachable=yes ./a.out

Câu trả lời:


378

Có nhiều hơn một cách để định nghĩa "rò rỉ bộ nhớ". Cụ thể, có hai định nghĩa chính về "rò rỉ bộ nhớ" được sử dụng phổ biến giữa các lập trình viên.

Định nghĩa đầu tiên thường được sử dụng của "rò rỉ bộ nhớ" là "Bộ nhớ được phân bổ và sau đó không được giải phóng trước khi chương trình kết thúc." Tuy nhiên, nhiều lập trình viên (đúng) cho rằng một số loại rò rỉ bộ nhớ phù hợp với định nghĩa này thực sự không gây ra bất kỳ vấn đề nào, và do đó không nên coi "rò rỉ bộ nhớ" thực sự .

Một định nghĩa chặt chẽ hơn (và hữu ích hơn) về "rò rỉ bộ nhớ" là, "Bộ nhớ đã được phân bổ và sau đó không thể được giải phóng vì chương trình không còn bất kỳ con trỏ nào đến khối bộ nhớ được phân bổ." Nói cách khác, bạn không thể giải phóng bộ nhớ mà bạn không còn bất kỳ con trỏ nào nữa. Do đó bộ nhớ như vậy là một "rò rỉ bộ nhớ". Valgrind sử dụng định nghĩa chặt chẽ hơn của thuật ngữ "rò rỉ bộ nhớ". Đây là loại rò rỉ có khả năng gây ra sự suy giảm heap đáng kể, đặc biệt là đối với các quy trình tồn tại lâu dài.

Danh mục "vẫn có thể truy cập" trong báo cáo rò rỉ của Valgrind đề cập đến các phân bổ chỉ phù hợp với định nghĩa đầu tiên về "rò rỉ bộ nhớ". Các khối này không được giải phóng, nhưng chúng có thể đã được giải phóng (nếu lập trình viên muốn) vì chương trình vẫn đang theo dõi các con trỏ tới các khối bộ nhớ đó.

Nói chung, không cần phải lo lắng về các khối "vẫn có thể truy cập". Họ không đặt ra loại vấn đề mà rò rỉ bộ nhớ thực sự có thể gây ra. Ví dụ, thông thường không có khả năng cạn kiệt đống từ các khối "vẫn có thể truy cập". Điều này là do các khối này thường là phân bổ một lần, các tham chiếu được lưu giữ trong suốt thời gian của quá trình. Mặc dù bạn có thể thực hiện và đảm bảo rằng chương trình của bạn giải phóng tất cả bộ nhớ được phân bổ, nhưng thường thì không có lợi ích thiết thực nào khi làm như vậy vì hệ điều hành sẽ lấy lại tất cả bộ nhớ của quá trình sau khi quá trình kết thúc. Tương phản điều này với sự thật rò rỉ bộ nhớ, nếu không được trộn, có thể khiến quá trình hết bộ nhớ nếu để đủ lâu hoặc đơn giản sẽ khiến quá trình tiêu thụ nhiều bộ nhớ hơn mức cần thiết.

Có lẽ là lần duy nhất hữu ích để đảm bảo rằng tất cả các phân bổ đều khớp với "giải phóng" là nếu các công cụ phát hiện rò rỉ của bạn không thể biết được khối nào "vẫn có thể truy cập được" (nhưng Valgrind có thể làm điều này) hoặc nếu hệ điều hành của bạn không lấy lại được tất cả bộ nhớ của quá trình kết thúc (tất cả các nền tảng mà Valgrind đã được chuyển để thực hiện việc này).


bạn có thể phỏng đoán những gì munmap () đang làm khiến các khối "vẫn có thể tiếp cận" biến mất không?

3
@crypto: Nó có thể được munmapgọi là kết quả của việc dỡ tải một đối tượng chia sẻ. Và tất cả các tài nguyên được sử dụng bởi đối tượng chia sẻ có thể được giải phóng trước khi nó được tải. Điều này có thể giải thích tại sao "vẫn có thể tiếp cận" được giải phóng trong munmapvụ án. Tôi chỉ đang suy đoán ở đây, mặc dù. Không có đủ thông tin ở đây để nói chắc chắn.
Dan Mould

3
Một trường hợp trong đó bộ nhớ "vẫn có thể truy cập" có thể được coi là rò rỉ bộ nhớ: giả sử bạn có bảng băm trong đó bạn thêm con trỏ vào đống bộ nhớ được phân bổ làm giá trị. Nếu bạn tiếp tục chèn các mục mới trên bàn, nhưng sẽ không xóa và giải phóng những mục bạn không cần nữa, nó có thể phát triển vô hạn, làm rò rỉ sự kiện bộ nhớ heap nếu bộ nhớ đó "vẫn có thể truy cập được". Đây là trường hợp rò rỉ bộ nhớ mà bạn có thể có trong Java hoặc các ngôn ngữ được thu gom rác khác.
lvella

Xem thêm câu trả lời này trong Câu hỏi thường gặp về valgrind về các khối "vẫn có thể truy cập" được tạo bởi STL. valgrind.org/docs/manual/faq.html#faq.reports
John Perry

5
"nhiều lập trình viên (đúng) lập luận rằng [bộ nhớ bị rò rỉ] không thực sự gây ra [một] vấn đề, và do đó không nên được coi là rò rỉ bộ nhớ thực" - Lol ... Xây dựng một DLL gốc với loại rò rỉ bộ nhớ đó, và sau đó có Java hoặc .Net tiêu thụ nó. Java và .Net tải và giải nén DLL hàng nghìn lần trong suốt vòng đời của một chương trình. Mỗi khi DLL được tải lại, nó sẽ rò rỉ thêm một chút bộ nhớ. Các chương trình chạy dài cuối cùng sẽ hết bộ nhớ. Nó khiến cho người bảo trì OpenJDK của Debian phát điên. Ông cũng nói như vậy trong danh sách gửi thư của OpenSSL trong khi chúng tôi đang thảo luận về rò rỉ bộ nhớ "lành tính" của OpenSSL.
jww

10

Vì có một số thói quen từ gia đình pthread ở phía dưới (nhưng tôi không biết cụ thể đó), tôi đoán rằng bạn đã đưa ra một số chủ đề như có thể tham gia đã chấm dứt thực thi.

Thông tin trạng thái thoát của luồng đó được giữ cho đến khi bạn gọi pthread_join. Do đó, bộ nhớ được giữ trong một bản ghi mất khi kết thúc chương trình, nhưng nó vẫn có thể truy cập được vì bạn có thể sử dụng pthread_joinđể truy cập nó.

Nếu phân tích này là chính xác, hãy khởi chạy các luồng này tách ra hoặc tham gia chúng trước khi kết thúc chương trình của bạn.

Chỉnh sửa : Tôi đã chạy chương trình mẫu của bạn (sau một số sửa chữa rõ ràng) và tôi không có lỗi nhưng sau đây

==18933== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 4 from 4)
--18933-- 
--18933-- used_suppression:      2 dl-hack3-cond-1
--18933-- used_suppression:      2 glibc-2.5.x-on-SUSE-10.2-(PPC)-2a

dl-điều này giống với hầu hết những gì bạn thấy, tôi đoán rằng bạn thấy một vấn đề đã biết có giải pháp về tập tin triệt tiêu valgrind. Có lẽ hệ thống của bạn không cập nhật hoặc bản phân phối của bạn không duy trì những thứ này. (Của tôi là Ubuntu 10,4, 64 bit)


Tôi nhận được 0 lỗi giống như bạn. Vui lòng kiểm tra tóm tắt rò rỉ để biết thông tin về "rò rỉ".

@crypto: Tôi không hiểu. Bạn có nghĩa là bạn có những áp lực giống như tôi có?
Jens Gustyt

used_suppression: 14 dl-hack3-cond-1 <- đó là những gì tôi nhận được

6

Bạn không xuất hiện để hiểu những gì still reachablecó nghĩa là.

Bất cứ điều gì still reachablekhông bị rò rỉ. Bạn không cần phải làm bất cứ điều gì về nó.


24
Điều này mâu thuẫn với các verbage khác được cung cấp bởi Valgrind cũng như về mặt kỹ thuật không chính xác. Bộ nhớ "vẫn có thể truy cập" khi thoát khỏi chương trình và do đó có khả năng bị rò rỉ. Điều gì xảy ra nếu bạn đang gỡ lỗi mã để chạy trên RTOS mà không dọn sạch bộ nhớ sau khi thoát khỏi chương trình?
Toymakerii

4
Thật không may, điều đó không phải lúc nào cũng đúng. Ví dụ, bộ mô tả tệp bị mất có thể được tính là rò rỉ bộ nhớ, nhưng valgrind phân loại chúng là "vẫn có thể truy cập", có lẽ vì các con trỏ dẫn đến chúng vẫn có thể truy cập được trong bảng hệ thống. Nhưng với mục đích gỡ lỗi, chẩn đoán thực sự là "rò rỉ bộ nhớ".
Cyan

Định nghĩa tập tin bị mất không phải là rò rỉ bộ nhớ theo định nghĩa. Có lẽ bạn đang nói về FILEcon trỏ bị mất ?
Sử dụng tiếng Nga

6

Dưới đây là một lời giải thích phù hợp về "vẫn có thể truy cập":

"Vẫn có thể truy cập" là các rò rỉ được gán cho các biến toàn cục và tĩnh cục bộ. Vì valgrind theo dõi các biến toàn cục và tĩnh, nó có thể loại trừ phân bổ bộ nhớ được gán "một lần và quên". Một biến toàn cục đã gán một phân bổ một lần và không bao giờ gán lại rằng phân bổ đó thường không phải là "rò rỉ" theo nghĩa là nó không tăng trưởng vô thời hạn. Nó vẫn là một rò rỉ theo nghĩa nghiêm ngặt, nhưng thường có thể bị bỏ qua trừ khi bạn là người phạm tội.

Các biến cục bộ được chỉ định phân bổ và không miễn phí hầu như luôn bị rò rỉ.

Đây là một ví dụ

int foo(void)
{
    static char *working_buf = NULL;
    char *temp_buf;
    if (!working_buf) {
         working_buf = (char *) malloc(16 * 1024);
    }
    temp_buf = (char *) malloc(5 * 1024);

    ....
    ....
    ....

}

Valgrind sẽ báo cáo work_buf là "vẫn có thể truy cập - 16k" và temp_buf là "chắc chắn bị mất - 5k".


-1

Đối với những độc giả tương lai, "Still Reachable" có thể có nghĩa là bạn đã quên đóng một cái gì đó như một tập tin. Mặc dù có vẻ như không có trong câu hỏi ban đầu, bạn nên luôn đảm bảo rằng bạn đã làm điều đó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.