Sutilezas na alocação de espaço de memória em C

keep-calm-and-segmentation-faultNa linguagem C a declaração de uma estrutura de dados abstrata (struct) pode ser feita de várias maneiras bem distintas, o que na minha opinião é uma característica muito negativa porque pode gerar muitas confusões pela falta de um padrão bem estabelecido. Por conta disso, tive agora alguns problemas de alocação de memória ao trabalhar com uma forma que não estava habituado. Abaixo se encontra duas alternativas de alocar e desalocar espaço na memória declarando as estruturas como segue.

typedef struct vertice { 
    char *nome;
} *vertice;
typedef struct grafo {
    char *nome;
    int n_vertices;
    int n_arestas;
    vertice vertices;
    int **matriz;
} *grafo;

Observe que na definição do tipo já está incluso um indicador de que é ponteiro, ou seja, na declaração de um novo grafo ao invés de: grafo *g;  fazemos: grafo g;. Por causa disso podemos declarar um vetor de vértices da forma como está na estrutura grafo (linha 8). Para alocar espaço para o vetor de vértices, podemos proceder de duas direções diferentes, a primeira é:

...
//Salva os vertices em um vetor 
  g->vertices = malloc(g->n_vertices * sizeof(struct vertice));
  guarda_vertice(dot, g->vertices);
...
static void guarda_vertice(Agraph_t *dot, vertice vertices){
    int i = 0;
    for (Agnode_t *v = agfstnode(dot); v; v = agnxtnode(dot,v)) {        
        vertices[i].nome = (char*) malloc(1 + strlen(agnameof(v)));
        strcpy(vertices[i++].nome, agnameof(v));
    }
}

Esse tipo de alocação só é possível por causa da forma como foi declarado o vetor dentro do grafo g e não para ponteiros de g. Assim, cada célula deste contém espaço para a estrutura vertice propriamente dita, e por isso nas linhas 9 e 10 se utiliza vertices[i].nome e não vertices[i]->nome. Neste caso, a liberação do espaço alocado é feita assim:

int destroi_grafo(grafo g) {
    for (int i = 0; i < g->n_vertices; i++){
        free(g->vertices[i].nome);
        g->vertices[i].nome = NULL;
    }
    free(g->vertices);   
    free(g->nome);
    free(g);
    g = NULL;
    return g ? 0 : 1;
}

A segunda direção, usa uma ideia parecida mas parece mais dinâmica e precisa de mais alocações e consequentemente mais liberações. À começar pela diferença sutil na declaração do vetor em struct grafo (linha 6), nesta seria como:

typedef struct grafo {
    char *nome;
    int n_vertices;
    int n_arestas;
    int direcionado;
    vertice *vertices;
    int **matriz;
} *grafo;
...
g->vertices = malloc(g->n_vertices * sizeof(vertice));
    guarda_vertice(dot, g->vertices);
...
static void guarda_vertice(Agraph_t *dot, vertice *vertices){
    int i = 0;
    for (Agnode_t *v = agfstnode(dot); v; v = agnxtnode(dot,v)) {
        vertices[i] = malloc(sizeof(struct vertice));
        vertices[i]->nome = (char*) malloc(1 + strlen(agnameof(v)));
        strcpy(vertices[i++]->nome, agnameof(v));
    }
}

Nesta, é alocado espaço para um vetor de ponteiros para a estrutura vertice, a consequência é que precisa ser alocado espaço para uma estruct em cada célula da lista (linha  16). Isso implica em mudanças na desalocação também.

int destroi_grafo(grafo g) {
     for (int i = 0; i < g->n_vertices; i++){
        free(g->vertices[i]->nome);
        g->vertices[i]->nome = NULL;
        free(g->vertices[i]);
    }
    free(g->vertices);    
    free(g->nome);
    free(g);
    g = NULL;
    return g ? 0 : 1;
}

Nota: Este código não tem o objetivo de ser executável, por enquanto foi utilizado apenas para exemplificar as sutilezas na alocação de memória neste caso específico.

Comentar

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s