valgrind를 사용하여 메모리 누수를 찾으려면 어떻게합니까?


183

valgrind를 사용하여 프로그램에서 메모리 누수를 찾으려면 어떻게합니까?

누군가 나를 도와주고 절차를 수행하는 단계를 설명해 주시겠습니까?

우분투 10.04를 사용하고 있으며 프로그램이 a.c있습니다. 도와주세요.


16
valgrind를 사용 하여 소스 코드가 아닌 컴파일 된 프로그램 을 테스트하십시오 .
Tony

6
@RageD에 의해 아래에 주어진 대답은 정확합니다. 왜 받아들이지 않습니까?
Pratik Singhal

1
누수는 당신이 하지 못하는 것에 의해 발생합니다 . 할당 된 메모리를 비 웁니다. 따라서 Valgrind는 누출이 "어디에"있는지 표시 할 수 없으며 할당 된 메모리가 더 이상 필요하지 않은 위치 만 알 수 있습니다. 그러나 어떤 할당이 사용 가능하지 않은지 알려 주면 프로그램을 통해 해당 메모리 사용을 추적하여 사용 가능한 위치를 결정할 수 있어야합니다. 일반적인 실수는 할당 된 메모리를 해제하지 않고 함수를 종료하는 것입니다.
MikeW

답변:


297

Valgrind를 실행하는 방법

OP를 모욕하지 말고이 질문을 받고 Linux 처음 접하는 사람들을 위해 Valgrind를 설치 해야합니다.

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

Valgrind의는 C / C ++ 코드를 쉽게 사용할 수 있지만 (참조 제대로 구성된 경우에도 다른 언어에 사용할 수있는 파이썬을).

Valgrind를 실행하려면 실행 파일을 매개 변수와 함께 인수로 프로그램에 전달하십시오.

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

간단히 말해서 플래그는 다음과 같습니다.

  • --leak-check=full: "각 개별 누출이 자세히 표시됩니다"
  • --show-leak-kinds=all: "전체"보고서에 "확정, 간접, 가능, 도달 가능한"누출 유형을 모두 표시하십시오.
  • --track-origins=yes: 속도보다 유용한 출력을 선호합니다. 초기화되지 않은 값의 출처를 추적하여 메모리 오류에 매우 유용 할 수 있습니다. Valgrind가 너무 느리면 끄는 것을 고려하십시오.
  • --verbose: 프로그램의 비정상적인 동작에 대해 알려줄 수 있습니다. 더 자세한 정보를 얻으려면 반복하십시오.
  • --log-file: 파일에 씁니다. 출력이 터미널 공간을 초과 할 때 유용합니다.

마지막으로 다음과 같은 Valgrind 보고서를 보려고합니다.

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)

누수가 있지만 어디에 있습니까?

따라서 메모리 누수가 있고 Valgrind는 의미있는 말을하지 않습니다. 아마도 다음과 같은 것이 있습니다.

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)

내가 쓴 C 코드도 살펴 보자.

#include <stdlib.h>

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

글쎄, 5 바이트가 손실되었습니다. 어떻게이 일이 일어 났어요? 오류 보고서는 말한다 mainmalloc. 더 큰 프로그램에서는 사냥하기가 매우 까다로울 수 있습니다. 실행 파일이 컴파일 된 방식 때문입니다 . 실제로 무엇이 잘못되었는지에 대한 세부적인 정보를 얻을 수 있습니다. 디버그 플래그로 프로그램을 다시 컴파일하십시오 ( gcc여기에서 사용하고 있습니다).

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

이제이 디버그 빌드를 통해 Valgrind 는 누출 된 메모리를 할당하는 정확한 코드 줄을 가리 킵니다 . (문구는 중요하다 : 당신의 누출이 어디 있는지 정확히하지 않을 수도 있습니다,하지만 무슨 일이 유출있어 추적 당신이 찾는 데 도움이됩니다. 여기서 .)

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)

메모리 누수 및 오류 디버깅 기술

  • www.cplusplus.com활용하십시오 ! C / C ++ 함수에 대한 훌륭한 문서가 있습니다.
  • 메모리 누수에 대한 일반적인 조언 :
    • 동적으로 할당 된 메모리가 실제로 해제되는지 확인하십시오.
    • 메모리를 할당하지 말고 포인터를 할당하는 것을 잊지 마십시오.
    • 이전 메모리가 해제되지 않는 한 새 포인터로 포인터를 덮어 쓰지 마십시오.
  • 메모리 오류에 대한 일반적인 조언 :
    • 자신이 속한 주소 및 색인에 액세스하고 기록하십시오. 메모리 오류는 누출과 다릅니다. 그들은 종종 IndexOutOfBoundsException 유형 문제입니다.
    • 메모리를 비운 후 메모리에 액세스하거나 쓰지 마십시오.
  • 때로는 닫는 괄호를 입력하지 않았 음을 발견 한 IDE와 마찬가지로 누출 / 오류가 서로 연결될 수 있습니다. 한 가지 문제를 해결하면 다른 문제를 해결할 수 있으므로 좋은 범인이되는 문제를 찾아 다음 아이디어 중 일부를 적용하십시오.

    • 메모리 오류가있는 "불쾌한"코드에 의존하거나 의존하는 코드의 함수를 나열하십시오. 프로그램 실행 ( gdb아마도)을 따르고 전제 조건 / 후 조건 오류를 찾으십시오. 할당 된 메모리 수명에 중점을 두면서 프로그램 실행을 추적하는 것이 좋습니다.
    • "불쾌한"코드 블록을 주석 처리해보십시오 (이유로 코드가 여전히 컴파일 됨). Valgrind 오류가 사라지면 오류가 발생한 위치를 찾은 것입니다.
  • 다른 모든 방법이 실패하면 찾아보십시오. Valgrind도 문서를 가지고 있습니다!

일반적인 누수 및 오류 살펴보기

당신의 포인터를보십시오

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)

그리고 코드 :

#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;
}

교수 조교로서 나는이 실수를 자주 보았다. 학생은 지역 변수를 사용하고 원래 포인터를 업데이트하는 것을 잊습니다. 여기서 오류 realloc는 실제로 할당 된 메모리를 다른 곳으로 이동하고 포인터의 위치를 ​​변경할 수 있다는 것을 알 수 있습니다. 그런 다음 어레이가 어디로 옮겼는지 알려주지 resizeArray않습니다 array->data.

잘못된 쓰기

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)

그리고 코드 :

#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;
}

Valgrind는 위의 주석 처리 된 코드 라인을 나타냅니다. 크기 26의 배열은 색인화되어 [0,25] *(alphabet + 26)유효하지 않은 쓰기입니다. 범위를 벗어났습니다. 유효하지 않은 쓰기는 일대일 오류의 일반적인 결과입니다. 할당 작업의 왼쪽을보십시오.

잘못된 읽기

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)

그리고 코드 :

#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는 위의 주석 처리 된 줄을 가리 킵니다. 마지막 반복을 살펴보십시오
*(destination + 26) = *(source + 26);. 그러나 *(source + 26)유효하지 않은 쓰기와 유사하게 다시 범위를 벗어났습니다. 유효하지 않은 읽기는 일대일 오류의 일반적인 결과이기도합니다. 할당 작업의 오른쪽을보십시오.


오픈 소스 (U / Dys) 토피아

누출이 언제 발생하는지 어떻게 알 수 있습니까? 다른 사람의 코드를 사용할 때 누수를 어떻게 찾습니까? 나는 내 것이 아닌 누출을 발견했다. 내가 뭔가를해야합니까? 모두 합법적 인 질문입니다. 첫째, 2 가지 클래스의 일반적인 만남을 보여주는 실제 사례 2 개.

얀손 : 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;
}

이것은 간단한 프로그램입니다. JSON 문자열을 읽고 구문 분석합니다. 만드는 과정에서 라이브러리 호출을 사용하여 구문 분석을 수행합니다. JSON은 자체적으로 중첩 된 구조를 포함 할 수 있으므로 Jansson은 필요한 할당을 동적으로 수행합니다. 그러나 이것이 decref우리가 모든 기능에서 우리에게 주어진 메모리를 "사용"하거나 의미하지는 않습니다 . 사실, 위에서 작성한이 코드는 "잘못된 읽기"와 "잘못된 쓰기"를 모두 발생시킵니다. 에 대한 decref라인을 꺼내면 이러한 오류가 사라집니다 value.

왜? 이 변수 value는 Jansson API에서 "빌려온 참조"로 간주됩니다. Jansson은 메모리를 추적하므로 decref JSON 구조를 서로 독립적으로 유지 해야합니다 . 레슨 : 문서를 읽으십시오 . 정말. 때로는 이해하기 어렵지만 왜 이런 일이 발생하는지 알려줍니다. 대신 이 메모리 오류에 대한 기존 질문 이 있습니다.

SDL : 그래픽 및 게임 라이브러리

#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;
}

이 코드에 어떤 문제가 있습니까? 그것은 ~ 212 KiB의 메모리를 지속적으로 누출시킵니다. 그것에 대해 잠시 생각하십시오. 우리는 SDL을 켜고 끈다. 대답? 아무 문제가 없다.

처음에는 기괴한 소리가납니다 . 진실은 그래픽이 지저분하고 때로는 표준 라이브러리의 일부로 누출을 받아 들여야한다는 것입니다. 여기서 교훈 : 모든 메모리 누수를 멈출 필요는 없습니다 . 누수 에 대해서는 아무것도 할 수없는 알려진 문제이기 때문에 누수억제 해야하는 경우가 있습니다 . (이것은 당신의 누출을 무시하는 나의 허락이 아닙니다!)

공허에 대한 답변

누출이 언제 발생하는지 어떻게 알 수 있습니까?
그것은. (어쨌든 99 % 확실)

다른 사람의 코드를 사용할 때 누수를 어떻게 찾습니까?
다른 사람이 이미 찾았을 가능성이 있습니다. 구글을보십시오! 그래도 실패하면 위에서 설명한 기술을 사용하십시오. 이것이 실패하고 대부분 API 호출과 자신의 스택 추적이 거의 없다면 다음 질문을 참조하십시오.

나는 내 것이 아닌 누출을 발견했다. 내가 뭔가를해야합니까?
예! 대부분의 API에는 버그와 문제를보고하는 방법이 있습니다. 그것을 써! 프로젝트에서 사용중인 도구를 돌려주십시오!


추가 자료

이렇게 오래있어 주셔서 감사합니다. 이 답변에 도달하는 광범위한 사람들을 돌 보려고 노력하면서 무언가를 배웠기를 바랍니다. C의 메모리 할당자가 어떻게 작동합니까? 실제로 메모리 누수와 메모리 오류는 무엇입니까? 그것들은 segfault와 어떻게 다릅니 까? Valgrind는 어떻게 작동합니까? 당신이 이것들 중 하나를 가지고 있다면, 당신의 호기심을 먹이십시오 :


4
훨씬 더 나은 대답, 부끄러운 일이지만 부끄러운 일입니다.
A. 스몰리 아크

나는 그런 일을하는 것이 좋은 습관이라고 믿습니다. 저는 몇 가지
A. Smoliak

1
이 답변에 별표를 표시하여 나중에 참조 할 수 있습니까? 잘 했어!
Zap

않는 memcheck도구는 기본적으로 활성화되어 있습니다?
abhiarora

@abhiarora 예. 매뉴얼 페이지는 memcheck기본 도구 임을 알려줍니다 .--tool=<toolname> [default: memcheck]
Joshua Detwiler

146

이 시도:

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

valgrind가 설치되어 있으면 프로그램을 통해 무엇이 잘못되었는지 알려줍니다. 누출이 발견 될 수있는 대략적인 장소와 포인터를 제공 할 수 있습니다. segfault'ing하는 경우을 통해 실행하십시오 gdb.


"your_program"은 무슨 뜻입니까? 이 소스 코드 위치 또는 응용 프로그램 이름 (예 : apk 파일)입니까?
HoangVu

7
your_program== 실행 파일 이름 또는 응용 프로그램을 실행하는 데 사용하는 명령
RageD

27

당신은 실행할 수 있습니다 :

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

1

다음과 같이 .bashrc 파일에서 별칭을 만들 수 있습니다

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

따라서 메모리 누수를 확인할 때마다 간단히

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

현재 디렉토리에 Valgrind 로그 파일이 생성됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.