Làm cách nào để sử dụng valgrind để tìm rò rỉ bộ nhớ?


181

Làm cách nào để sử dụng valgrind để tìm rò rỉ bộ nhớ trong chương trình?

Xin ai đó giúp tôi và mô tả các bước để thực hiện thủ tục?

Tôi đang sử dụng Ubuntu 10.04 và tôi có một chương trình a.c, vui lòng giúp tôi.


16
Bạn sử dụng valgrind để kiểm tra chương trình đã biên dịch của bạn , không phải mã nguồn.
Tony

6
Câu trả lời dưới đây của @RageD là chính xác, tại sao bạn không chấp nhận nó?
Pratik Singhal

1
Một rò rỉ được gây ra bởi một cái gì đó bạn không làm - tức là. bộ nhớ được phân bổ miễn phí. Do đó Valgrind không thể hiển thị cho bạn "nơi" rò rỉ - chỉ bạn biết nơi bộ nhớ được phân bổ không còn cần thiết nữa. Tuy nhiên, bằng cách cho bạn biết phân bổ nào không miễn phí () d, bằng cách truy tìm việc sử dụng bộ nhớ đó thông qua chương trình của bạn, bạn sẽ có thể xác định nơi nào sẽ nhận được free () d. Một lỗi phổ biến là lỗi thoát khỏi chức năng mà không giải phóng bộ nhớ được phân bổ.
MikeW

1
Liên quan: với bất kỳ công cụ nào: stackoverflow.com/questions/6261201/ từ
Ciro Santilli 冠状 病 六四 事件

Câu trả lời:


296

Cách chạy Valgrind

Không phải xúc phạm OP, nhưng đối với những người đến với câu hỏi này và vẫn còn mới đối với Linux, bạn có thể phải cài đặt Valgrind trên hệ thống của mình.

sudo apt install valgrind  # Ubuntu, Debian, etc.
sudo yum install valgrind  # RHEL, CentOS, Fedora, etc.

Valgrind có thể dễ dàng sử dụng cho mã C / C ++, nhưng thậm chí có thể được sử dụng cho các ngôn ngữ khác khi được định cấu hình đúng (xem phần này cho Python).

Để chạy Valgrind , hãy truyền tệp thực thi dưới dạng đối số (cùng với bất kỳ tham số nào cho chương trình).

valgrind --leak-check=full \
         --show-leak-kinds=all \
         --track-origins=yes \
         --verbose \
         --log-file=valgrind-out.txt \
         ./executable exampleParam1

Tóm lại, các cờ là:

  • --leak-check=full: "mỗi rò rỉ riêng lẻ sẽ được hiển thị chi tiết"
  • --show-leak-kinds=all: Hiển thị tất cả các loại rò rỉ "xác định, gián tiếp, có thể, có thể tiếp cận" trong báo cáo "đầy đủ".
  • --track-origins=yes: Ưu tiên đầu ra hữu ích hơn tốc độ. Điều này theo dõi nguồn gốc của các giá trị chưa được khởi tạo, có thể rất hữu ích cho các lỗi bộ nhớ. Cân nhắc tắt nếu Valgrind chậm không chấp nhận được.
  • --verbose: Có thể cho bạn biết về hành vi bất thường của chương trình của bạn. Lặp lại cho dài hơn.
  • --log-file: Viết vào một tập tin. Hữu ích khi đầu ra vượt quá không gian thiết bị đầu cuối.

Cuối cùng, bạn muốn xem báo cáo Valgrind giống như thế này:

HEAP SUMMARY:
    in use at exit: 0 bytes in 0 blocks
  total heap usage: 636 allocs, 636 frees, 25,393 bytes allocated

All heap blocks were freed -- no leaks are possible

ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Tôi có một rò rỉ, nhưng ở đâu ?

Vì vậy, bạn bị rò rỉ bộ nhớ và Valgrind không nói gì có ý nghĩa. Có lẽ, một cái gì đó như thế này:

5 bytes in 1 blocks are definitely lost in loss record 1 of 1
   at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
   by 0x40053E: main (in /home/Peri461/Documents/executable)

Chúng ta hãy xem mã C tôi đã viết quá:

#include <stdlib.h>

int main() {
    char* string = malloc(5 * sizeof(char)); //LEAK: not freed!
    return 0;
}

Vâng, đã mất 5 byte. Chuyện đã xảy ra như thế nào? Báo cáo lỗi chỉ nói mainmalloc. Trong một chương trình lớn hơn, điều đó sẽ gây rắc rối nghiêm trọng khi săn lùng. Điều này là do cách thức thực thi được biên dịch . Chúng tôi thực sự có thể nhận được thông tin chi tiết từng dòng về những gì đã sai. Biên dịch lại chương trình của bạn bằng cờ gỡ lỗi (Tôi đang sử dụng gccở đây):

gcc -o executable -std=c11 -Wall main.c         # suppose it was this at first
gcc -o executable -std=c11 -Wall -ggdb3 main.c  # add -ggdb3 to it

Bây giờ với bản dựng gỡ lỗi này, Valgrind trỏ đến dòng mã chính xác phân bổ bộ nhớ đã bị rò rỉ! (Từ ngữ rất quan trọng: nó có thể không chính xác nơi rò rỉ của bạn, nhưng những gì đã bị rò rỉ. Dấu vết giúp bạn tìm thấy ở đâu .)

5 bytes in 1 blocks are definitely lost in loss record 1 of 1
   at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
   by 0x40053E: main (main.c:4)

Kỹ thuật sửa lỗi rò rỉ bộ nhớ

  • Hãy sử dụng www.cplusplus.com ! Nó có tài liệu tuyệt vời về các chức năng C / C ++.
  • Lời khuyên chung cho rò rỉ bộ nhớ:
    • Hãy chắc chắn rằng bộ nhớ được phân bổ động của bạn trong thực tế sẽ được giải phóng.
    • Đừng phân bổ bộ nhớ và quên gán con trỏ.
    • Không ghi đè con trỏ bằng một con trỏ mới trừ khi bộ nhớ cũ được giải phóng.
  • Lời khuyên chung cho các lỗi bộ nhớ:
    • Truy cập và ghi vào địa chỉ và chỉ số mà bạn chắc chắn thuộc về bạn. Lỗi bộ nhớ khác với rò rỉ; Họ thường chỉ IndexOutOfBoundsException gõ các vấn đề.
    • Không truy cập hoặc ghi vào bộ nhớ sau khi giải phóng nó.
  • Đôi khi các rò rỉ / lỗi của bạn có thể được liên kết với nhau, giống như một IDE phát hiện ra rằng bạn chưa gõ khung đóng. Giải quyết một vấn đề có thể giải quyết những vấn đề khác, vì vậy hãy tìm một vấn đề có vẻ là thủ phạm tốt và áp dụng một số ý tưởng sau:

    • Liệt kê các hàm trong mã của bạn phụ thuộc vào / phụ thuộc vào mã "vi phạm" có lỗi bộ nhớ. Thực hiện theo thực thi của chương trình (có thể ngay cả trong gdbcó lẽ) và tìm kiếm các lỗi điều kiện tiên quyết / hậu điều kiện. Ý tưởng là theo dõi quá trình thực thi chương trình của bạn trong khi tập trung vào thời gian tồn tại của bộ nhớ được phân bổ.
    • Hãy thử nhận xét khối mã "vi phạm" (theo lý do, để mã của bạn vẫn biên dịch). Nếu lỗi Valgrind biến mất, bạn đã tìm thấy nó ở đâu.
  • Nếu vẫn thất bại, hãy thử tìm kiếm nó. Valgrind cũng có tài liệu !

Nhìn vào rò rỉ và lỗi phổ biến

Xem con trỏ của bạn

60 bytes in 1 blocks are definitely lost in loss record 1 of 1
   at 0x4C2BB78: realloc (vg_replace_malloc.c:785)
   by 0x4005E4: resizeArray (main.c:12)
   by 0x40062E: main (main.c:19)

Và mã:

#include <stdlib.h>
#include <stdint.h>

struct _List {
    int32_t* data;
    int32_t length;
};
typedef struct _List List;

List* resizeArray(List* array) {
    int32_t* dPtr = array->data;
    dPtr = realloc(dPtr, 15 * sizeof(int32_t)); //doesn't update array->data
    return array;
}

int main() {
    List* array = calloc(1, sizeof(List));
    array->data = calloc(10, sizeof(int32_t));
    array = resizeArray(array);

    free(array->data);
    free(array);
    return 0;
}

Là một trợ lý giảng dạy, tôi đã thấy lỗi này thường xuyên. Học sinh sử dụng một biến cục bộ và quên cập nhật con trỏ ban đầu. Lỗi ở đây là nhận thấy rằng reallocthực sự có thể di chuyển bộ nhớ được phân bổ ở nơi khác và thay đổi vị trí của con trỏ. Sau đó chúng tôi rời đi resizeArraymà không cho array->databiết mảng được chuyển đến đâu.

Viết không hợp lệ

1 errors in context 1 of 1:
Invalid write of size 1
   at 0x4005CA: main (main.c:10)
 Address 0x51f905a is 0 bytes after a block of size 26 alloc'd
   at 0x4C2B975: calloc (vg_replace_malloc.c:711)
   by 0x400593: main (main.c:5)

Và mã:

#include <stdlib.h>
#include <stdint.h>

int main() {
    char* alphabet = calloc(26, sizeof(char));

    for(uint8_t i = 0; i < 26; i++) {
        *(alphabet + i) = 'A' + i;
    }
    *(alphabet + 26) = '\0'; //null-terminate the string?

    free(alphabet);
    return 0;
}

Lưu ý rằng Valgrind chỉ cho chúng ta dòng mã nhận xét ở trên. Mảng kích thước 26 được lập chỉ mục [0,25], đó là lý do tại sao *(alphabet + 26)một bản ghi không hợp lệ của nó nằm ngoài giới hạn. Một ghi không hợp lệ là kết quả phổ biến của lỗi off-by-one. Nhìn vào phía bên trái của hoạt động chuyển nhượng của bạn.

Đọc không hợp lệ

1 errors in context 1 of 1:
Invalid read of size 1
   at 0x400602: main (main.c:9)
 Address 0x51f90ba is 0 bytes after a block of size 26 alloc'd
   at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
   by 0x4005E1: main (main.c:6)

Và mã:

#include <stdlib.h>
#include <stdint.h>

int main() {
    char* destination = calloc(27, sizeof(char));
    char* source = malloc(26 * sizeof(char));

    for(uint8_t i = 0; i < 27; i++) {
        *(destination + i) = *(source + i); //Look at the last iteration.
    }

    free(destination);
    free(source);
    return 0;
}

Valgrind chỉ chúng tôi vào dòng nhận xét ở trên. Nhìn vào lần lặp cuối cùng ở đây, đó là
*(destination + 26) = *(source + 26);. Tuy nhiên, *(source + 26)là ngoài giới hạn một lần nữa, tương tự như viết không hợp lệ. Việc đọc không hợp lệ cũng là kết quả phổ biến của các lỗi do lỗi một. Nhìn vào phía bên phải của hoạt động chuyển nhượng của bạn.


Topia mã nguồn mở (U / Dys)

Làm thế nào để tôi biết khi rò rỉ là của tôi? Làm cách nào để tìm rò rỉ của tôi khi tôi đang sử dụng mã của người khác? Tôi tìm thấy một rò rỉ không phải của tôi; tôi nên làm gì Tất cả đều là những câu hỏi chính đáng. Đầu tiên, 2 ví dụ trong thế giới thực cho thấy 2 lớp các cuộc gặp gỡ phổ biến.

Jansson : một thư viện JSON

#include <jansson.h>
#include <stdio.h>

int main() {
    char* string = "{ \"key\": \"value\" }";

    json_error_t error;
    json_t* root = json_loads(string, 0, &error); //obtaining a pointer
    json_t* value = json_object_get(root, "key"); //obtaining a pointer
    printf("\"%s\" is the value field.\n", json_string_value(value)); //use value

    json_decref(value); //Do I free this pointer?
    json_decref(root);  //What about this one? Does the order matter?
    return 0;
}

Đây là một chương trình đơn giản: nó đọc một chuỗi JSON và phân tích cú pháp. Trong quá trình thực hiện, chúng tôi sử dụng các cuộc gọi thư viện để thực hiện phân tích cú pháp cho chúng tôi. Jansson thực hiện các phân bổ cần thiết một cách linh hoạt vì JSON có thể chứa các cấu trúc lồng nhau của chính nó. Tuy nhiên, điều này không có nghĩa là chúng tôi decrefhoặc "giải phóng" bộ nhớ được cung cấp cho chúng tôi khỏi mọi chức năng. Trong thực tế, mã này tôi đã viết ở trên ném cả "Đọc không hợp lệ" và "Viết không hợp lệ". Những lỗi đó biến mất khi bạn đưa ra decrefdòng cho value.

Tại sao? Biến valueđược coi là "tham chiếu mượn" trong API Jansson. Jansson theo dõi bộ nhớ của nó cho bạn và bạn chỉ cần các decref cấu trúc JSON độc lập với nhau. Bài học ở đây: đọc tài liệu . Có thật không. Đôi khi thật khó hiểu, nhưng họ đang nói với bạn tại sao những điều này xảy ra. Thay vào đó, chúng tôi có câu hỏi hiện tại về lỗi bộ nhớ này.

SDL : thư viện đồ họa và chơi game

#include "SDL2/SDL.h"

int main(int argc, char* argv[]) {
    if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) != 0) {
        SDL_Log("Unable to initialize SDL: %s", SDL_GetError());
        return 1;
    }

    SDL_Quit();
    return 0;
}

Có gì sai với mã này ? Nó liên tục rò rỉ ~ 212 KiB bộ nhớ cho tôi. Hãy dành một chút thời gian để suy nghĩ về nó. Chúng tôi bật SDL và sau đó tắt. Câu trả lời? Không có gì sai.

Điều đó thoạt nghe có vẻ kỳ quái . Sự thật mà nói, đồ họa rất lộn xộn và đôi khi bạn phải chấp nhận một số rò rỉ như là một phần của thư viện tiêu chuẩn. Bài học ở đây: bạn không cần phải dập tắt mọi rò rỉ bộ nhớ . Đôi khi bạn chỉ cần ngăn chặn rò rỉ vì chúng là những vấn đề đã biết mà bạn không thể làm gì được . (Đây không phải là sự cho phép của tôi để bỏ qua rò rỉ của riêng bạn!)

Trả lời cho khoảng trống

Làm thế nào để tôi biết khi rò rỉ là của tôi?
Nó là. (Chắc chắn 99%, dù sao)

Làm cách nào để tìm rò rỉ của tôi khi tôi đang sử dụng mã của người khác?
Có thể người khác đã tìm thấy nó. Hãy dùng thử Google! Nếu thất bại, hãy sử dụng các kỹ năng tôi đã cho bạn ở trên. Nếu điều đó không thành công và bạn chủ yếu thấy các lệnh gọi API và một chút dấu vết ngăn xếp của riêng bạn, hãy xem câu hỏi tiếp theo.

Tôi tìm thấy một rò rỉ không phải của tôi; tôi nên làm gì
Đúng! Hầu hết các API có cách để báo cáo lỗi và sự cố. Sử dụng chúng! Giúp trả lại cho các công cụ bạn đang sử dụng trong dự án của bạn!


Đọc thêm

Cảm ơn vì đã ở lại với tôi lâu như vậy. Tôi hy vọng bạn đã học được điều gì đó, vì tôi đã cố gắng hướng đến phổ rộng của những người đi đến câu trả lời này. Một số điều tôi hy vọng bạn đã hỏi dọc đường: Công cụ cấp phát bộ nhớ của C hoạt động như thế nào? Điều gì thực sự là rò rỉ bộ nhớ và lỗi bộ nhớ? Chúng khác với segfaults như thế nào? Valgrind hoạt động như thế nào? Nếu bạn có bất kỳ thứ gì trong số này, xin vui lòng khơi dậy sự tò mò của bạn:


4
Câu trả lời tốt hơn nhiều, một sự xấu hổ đây không phải là câu trả lời được chấp nhận.
A. Smoliak

Tôi tin rằng đó là một thực hành tốt để làm một việc như vậy, tôi đã tự mình làm một vài điều
A. Smoliak

1
Tôi có thể đánh dấu sao câu trả lời này và sử dụng nó làm tài liệu tham khảo trong tương lai cho bản thân mình không? Làm tốt lắm!
Zap

không memcheckcông cụ được kích hoạt theo mặc định?
abhiarora

@abhiarora Vâng. Trang người đàn ông cho chúng tôi biết đó memchecklà công cụ mặc định:--tool=<toolname> [default: memcheck]
Joshua Detwiler

146

Thử cái này:

valgrind --leak-check=full -v ./your_program

Miễn là valgrind được cài đặt, nó sẽ đi qua chương trình của bạn và cho bạn biết điều gì sai. Nó có thể cung cấp cho bạn con trỏ và những nơi gần đúng nơi có thể tìm thấy rò rỉ của bạn. Nếu bạn đang phân tách, hãy thử chạy qua gdb.


"Your_program" nghĩa là gì? Đây có phải là vị trí mã nguồn hoặc tên ứng dụng như tệp apk?
HoàngVu

7
your_program== tên thực thi hoặc bất kỳ lệnh nào bạn sử dụng để chạy ứng dụng của mình.
RageD

27

Bạn có thể chạy:

valgrind --leak-check=full --log-file="logfile.out" -v [your_program(and its arguments)]

1

Bạn có thể tạo bí danh trong tệp .bashrc như sau

alias vg='valgrind --leak-check=full -v --track-origins=yes --log-file=vg_logfile.out'

Vì vậy, bất cứ khi nào bạn muốn kiểm tra rò rỉ bộ nhớ, chỉ cần làm đơn giản

vg ./<name of your executable> <command line parameters to your executable>

Điều này sẽ tạo ra một tệp nhật ký Valgrind trong thư mục hiện tại.

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.