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.
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.
Câu trả lời:
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)
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
main
và malloc
. 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)
IndexOutOfBoundsException
gõ các vấ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:
gdb
có 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ổ.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 realloc
thự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 resizeArray
mà không cho
array->data
biết mảng được chuyển đến đâu.
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.
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.
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.
#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 decref
hoặ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 decref
dò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.
#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!)
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ả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:
memcheck
công cụ được kích hoạt theo mặc định?
memcheck
là công cụ mặc định:--tool=<toolname> [default: memcheck]
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
== 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.
Bạn có thể chạy:
valgrind --leak-check=full --log-file="logfile.out" -v [your_program(and its arguments)]
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.