C 프로그램에서 메모리 누수를 감지하는 간단한 프로그램

C/C++와 같은 언어로 프로그래밍할 때마다 메모리를 직접 관리해야 합니다. Python, Java 등과 같은 다른 언어에서는 사용하지 않는 메모리를 자동으로 해제할 방법이 없습니다.

때때로 우리는 메모리 누수를 남기는 프로그램을 작성하는데, 그 결과 일정 시간이 지나면 프로그램이 충돌하고 메모리 누수가 어디에 남아 있는지 찾기가 매우 어렵습니다.

따라서 이러한 문제를 디버깅하기 위해 프로그램에서 메모리 누수를 감지하는 간단한 프로그램을 작성할 수 있습니다.

여기서 아이디어는 모든 할당을 추적하고 프로그램 실행 중에 해제되었는지 여부를 확인하는 것입니다.

프로그램 실행 중 메모리 할당 정보를 저장하기 위한 기본 데이터 구조부터 시작하겠습니다.

데이터 구조 코드

// We assume max 1000 allocations will take place 
#define MAX_ALLOCATIONS 1000

/* 
Data Structure to keep track of memory allocations

address -> Memory address allocated
size -> Size allocated
line -> Line number where allocation is done
*/
typedef struct {
    size_t address;
    size_t size;
    uint32_t line;
} Mem;

/*
mem -> store all the allocation in array
total_allocated_size -> Keep track total memory allocated
total_free_size -> Keep track total memory freed
*/
struct {
    Mem mem[MAX_ALLOCATIONS];
    size_t total_allocated_size;
    size_t total_free_size;
} data;


이제 데이터 구조에서 메모리 할당을 찾고, 삽입하고, 지우는 몇 가지 도우미 함수를 만들 것입니다.

도우미 함수 코드

/**
 * Find a memory by its address
 * 
 * @return: Pointer to memory
*/
Mem *find_by_address(size_t address) {
    for (uint32_t i=0; i<MAX_ALLOCATIONS; i++) {
        if (data.mem[i].address == address)
            return &data.mem[i]; // as soon as find return
    }

    // otherwise return null
    return NULL;
}


/**
 * insert memory allocated with size
*/
void insert(size_t address, size_t size, uint32_t line) {
    // check for null
    if (address == 0) {
        WARN("Memory allocation failed", line);
        return;
    }

    Mem *mem = find_by_address(0);
    // if the return value is null we need to increase the MAX_ALLOCATIONS value
    if (mem == NULL) {
        WARN("Max allocations reached", line);
        return;
    }

    // save all the allocation info
    mem->address = address;
    mem->size = size;
    mem->line = line;
    data.total_allocated_size += size;
}

/**
 * Remove the memory allocation
 * 
 * @return: -1 on failure else 0
*/
int erase(size_t address, uint32_t line) {
    if (address == 0) {
        WARN("Tried to free a null ptr", line);
        return -1;
    }

    Mem *mem = find_by_address(address);
    // if the address is not found we assume it is already deleted
    if (mem == NULL) {
        WARN("Double free detected", line);
        return -1;
    }

    // set address to null and update info
    mem->address = 0;
    data.total_free_size += mem->size;
    return 0;
}


프로그램이 끝나면 메모리 누수에 대한 자세한 보고서를 인쇄합니다.

암호

void print_report() {
    printf("\nLeak Summary\n");
    printf("Total Memory allocated %lu bytes\n", data.total_allocated_size);
    printf("Total Memory freed     %lu bytes\n", data.total_free_size);
    printf("Memory Leaked          %lu bytes\n\n", 
        data.total_allocated_size - data.total_free_size);

    if (data.total_free_size != data.total_allocated_size) {
        printf("Detailed Report\n");
        for (int i=0; i<MAX_ALLOCATIONS; i++) {
            if (data.mem[i].address != 0) {
                printf("Memory leak at line %d: (%lu bytes)\n", 
                    data.mem[i].line,
                    data.mem[i].size);
            }
        }
    }
}


이제 기본 할당자가 가로채는 함수를 만들어 메모리 할당 동안 삽입하고 사용 가능한 동안 지우십시오.

사용자 지정 할당자 함수

// Create allocator functions
void *_malloc(size_t size, uint32_t line) {
    void *ptr = malloc(size);

    // insert to memory data
    insert((size_t)ptr, size, line);

    return ptr;
}

void _free(void *ptr, uint32_t line) {
    // erase memory data
    if (erase((size_t)ptr, line) == 0)
        free(ptr);
}


여기서 우리는 가로챈 함수로 mallocfree와 같은 기본 할당자 함수를 재정의합니다.

기본 할당자 함수 재정의

// redefine allocator functions
#define malloc(size) _malloc(size, __LINE__)
#define free(ptr) _free(ptr, __LINE__)


그게 다야!

완전한 코드

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

#define MAX_ALLOCATIONS 100
#define WARN(msg, line) (printf("Warning %d: %s\n", line, msg))

/* 
Data Structure to keep track of memory allocations
*/
typedef struct {
    size_t address;
    size_t size;
    uint32_t line;
} Mem;

struct {
    Mem mem[MAX_ALLOCATIONS];
    size_t total_allocated_size;
    size_t total_free_size;
} data;

/**
 * Find a memory by its address
 * 
 * @return: Pointer to memory
*/
Mem *find_by_address(size_t address) {
    for (uint32_t i=0; i<MAX_ALLOCATIONS; i++) {
        if (data.mem[i].address == address)
            return &data.mem[i]; // as soon as find return
    }

    // otherwise return null
    return NULL;
}


/**
 * insert memory allocated with size
*/
void insert(size_t address, size_t size, uint32_t line) {
    // check for null
    if (address == 0) {
        WARN("Memory allocation failed", line);
        return;
    }

    Mem *mem = find_by_address(0);
    // if the return value is null we need to increase the MAX_ALLOCATIONS value
    if (mem == NULL) {
        WARN("Max allocations reached", line);
        return;
    }

    // save all the allocation info
    mem->address = address;
    mem->size = size;
    mem->line = line;
    data.total_allocated_size += size;
}

/**
 * Remove the memory allocation
 * 
 * @return: -1 on failure else 0
*/
int erase(size_t address, uint32_t line) {
    if (address == 0) {
        WARN("Tried to free a null ptr", line);
        return -1;
    }

    Mem *mem = find_by_address(address);
    // if the address is not found we assume it is already deleted
    if (mem == NULL) {
        WARN("Double free detected", line);
        return -1;
    }

    // set address to null and update info
    mem->address = 0;
    data.total_free_size += mem->size;
    return 0;
}

void print_report() {
    printf("\nLeak Summary\n");
    printf("Total Memory allocated %lu bytes\n", data.total_allocated_size);
    printf("Total Memory freed     %lu bytes\n", data.total_free_size);
    printf("Memory Leaked          %lu bytes\n\n", 
        data.total_allocated_size - data.total_free_size);

    if (data.total_free_size != data.total_allocated_size) {
        printf("Detailed Report\n");
        for (int i=0; i<MAX_ALLOCATIONS; i++) {
            if (data.mem[i].address != 0) {
                printf("Memory leak at line %d: (%lu bytes)\n", 
                    data.mem[i].line,
                    data.mem[i].size);
            }
        }
    }
}

// Override allocation functions
void *_malloc(size_t size, uint32_t line) {
    void *ptr = malloc(size);

    // insert to memory data
    insert((size_t)ptr, size, line);

    return ptr;
}

void _free(void *ptr, uint32_t line) {
    // erase memory data
    if (erase((size_t)ptr, line) == 0)
        free(ptr);
}

// redefine allocator functions
#define malloc(size) _malloc(size, __LINE__)
#define free(ptr) _free(ptr, __LINE__)

int main() {
    int *n1 = malloc(sizeof(int));
    free(n1);

    int *n2 = NULL;
    free(n2);

    int *n3 = malloc(sizeof(int));
    free(n3);
    free(n3);

    int *n4 = malloc(sizeof(int));

    print_report();
    return 0;
}


산출

naman@namantam1:~/programs$ gcc main.c && ./a.out
Warning 143: Tried to free a null ptr
Warning 147: Double free detected

Leak Summary
Total Memory allocated 12 bytes
Total Memory freed     8 bytes
Memory Leaked          4 bytes

Detailed Report
Memory leak at line 149: (4 bytes)


위의 출력에서 ​​모든 메모리 누수를 double-free로 감지한 것이 분명합니다.

Note: This program will not be able to keep track of memory allocation done by inbuild or any third-party library. So if we free memory allocated in library call, It will show double free, We can simply ignore them.




❤️이 글을 읽어주셔서 정말 감사합니다. 저는 새로운 것을 배우는 열정적인 공대생이므로 실수를 발견하거나 제안할 사항이 있으면 댓글로 알려주세요.

또한 이 게시물이 어떤 식으로든 도움이 된다면 공유하고 엄지손가락을 치켜세우는 것을 고려하십시오.

좋은 웹페이지 즐겨찾기