Trace de Alocação em C

por Fabio A. Mazzarino

Vazamento de memória é uma pequena praga que precisa ser combatido. Em alguns sistemas pode não fazer muita diferença, mas quando o processamento envolve um volume grande de dados, ou quando a memória é restrita, como em sistemas embarcados, por exemplo, é necessário tomar muito cuidado.

Pra facilitar a detecção de vazamento de memória podemos fazer uma pequena biblioteca para localizar cada malloc sem seu devido free.

Vamos começar com a estrutura de dados que vai nos permitir gerenciar cada alocação:

typedef struct __memnode {
    void *p;
    char *file;
    unsigned line;
    size_t size;
    struct __memnode *pnext;
} _memnode;
typedef _memnode* memnode;

Em seguida as funções de alocação e desalocação:

static memnode *lastnode = NULL;
void* _safemalloc(size_t size, char *file, unsigned line) {
    void *p = NULL;
    memnode *node;
    /* memory allocation */
    p = malloc(size);
    if (!p)
        goto err_p;
    node = (memnode*)malloc(sizeof(memnode));
    if (!node)
        goto err_node;
    /* fill node */
    node->p = p;
    node->file = file;
    node->line = line;
    node->size = size;
    /* insert node */
    if (lastnode)
        node->prev = lastnode;
    else
        node->prev = NULL;
    lastnode = node;
    return p;
err_node:
    free(p);
err_p:
    return NULL;
}

void safefree(void *p) {
    memnode *cursor = lastnode;
    memnode *prev = cursor;
    while (cursor) {
        if (cursor->p == p) {
            if (cursor == lastnode)
                lastnode = cursor->prev;
            else
                prev->prev = cursor->prev;
            free(p);
            free(cursor);
            break;
        }
        prev = cursor;
        cursor = cursor->prev;
    }
}

Primeiro código a ser notado é a variável global static declarada. Assim não é possível acessar a variável através de outros arquivos.

Note também que a função de alocação, _safemalloc, exige três argumentos: o tamanho da alocação, o arquivo e a linha do arquivo. Pra preencher esses dados apropriadamente vamos usar uma macro, assim basta executar safemalloc(size) que o nome do arquivo e o número da linha são preenchidos automaticamente.

#define safemalloc(size) _safemalloc(size, __FILE__, __LINE__)

Vamos fazer também um report de vazamento de memória pra ser executado antes do fim da execução do trecho em que será verificado por vazamento de memória:

void leakreport() {
    memnode *cursor = lastnode;
    int ct = 1;
    if (cursor == NULL) {
        printf("NO MEMORY LEAKS DETECTED\n");
        return;
    }
    printf("MEMORY LEAKS DETECTED\n");
    while (cursor) {
        printf("    LEAK #%d %d bytes: FILE %s:%ld\n", 
            ct, cursor->size, cursor->file, cursor->line);
        cursor = cursor->prev;
        ct++;
    }
}

Agora só falta um exemplo de uso:

void main() {
    char *filename = safemalloc(512);
    char *buffer = safemalloc(1024);
    safefree(buffer);
    leakreport();
}

A saída vai ser a seguinte:

MEMORY LEAKS DETECTED
    LEAK #1 512 bytes: FILE C:\Users\labcpp\code\memtrace.c:84

Indicando que houve um vazamento de 512 bytes na linha 84 do arquivo memtrace.c. Note que o caminho do arquivo e o número da linha pode variar.

Em alguns times a utilização de trace de memória é obrigatório, e vazamento de memória é tratado com a criticidade de um bug funcional. Mas cada time é um time, tudo depende do quão crítico é um vazamento de memória.