简体   繁体   中英

How to free Structure containing dynamically allocated double pointer to another structure type?

I have created a graph structure in C. The structural definition of the graph is given below.

typedef struct _GNODE_ {
    int nodeNumber;             // vertex/node number
    int adjNum;                 // number of adjacent nodes
    struct _GNODE_ **adjacent;  // array of pointers to adjacent nodes
} _GNODE_ ;

typedef struct _GRAPH_ {
    int vc;         // number of vertices
    char *name;     // graph name
    int **AM;       // adjacency matrix
    _GNODE_ **node; // array of pointer to each vertices
} _GRAPH_ ;

where _GNODE_ is the structure of a node in the graph.

The function readGraphFromTxt(char *fileName , _GRAPH_ *graph) reads a text file containing the adjacency matrix of the graph and call another function called createGraphFromAM(_GRAPH_ *graph) to create a graph structure.

int readGraphFromTxt(char *filename , _GRAPH_ *graph){
    FILE *fp;
    fp = fopen(filename, "r");
    int vc;
    int **AM;
    char *gname;
    if(fp != NULL){
        char graphName[100];
        fgets(graphName, 99, fp );
        gname = (char *)malloc(sizeof(char)*101);
        strncpy(gname , graphName, 99);
        fgets(graphName, 99, fp );
        fscanf(fp , "%d\n" , &vc);
        AM = (int **) malloc(sizeof(int*) * vc);
        for(int i=0; i<vc; i++){
            AM[i] = (int *)malloc(sizeof(int) * vc);
            if(fscanf(fp, "%s" , graphName) ==EOF)    break;
            for(int j=0; j<vc; j++){
                AM[i][j] = graphName[j] - '0';
            }
        }
    }
    if(AM != NULL) graph->AM = AM;
    if(gname != NULL )  graph->name = gname;
    graph->vc = vc;
    createGraphFromAM(graph);
    fclose(fp);
    return vc;
}

void createGraphFromAM(_GRAPH_ *graph) {
    graph->node = (_GNODE_ **)malloc(sizeof(_GNODE_) * graph->vc) ; // array of pointers to different nodes

    for (int i=0; i<graph->vc ; i++){
        graph->node[i] = (_GNODE_ *)malloc(sizeof(_GNODE_));
        graph->node[i]->adjNum=0;
        graph->node[i]->nodeNumber = i;     // node number
        graph->node[i]->adjacent = (_GNODE_ **)malloc(sizeof(_GNODE_)) ; // because allocating 0 byte is tricky
    }

    for (int i=0; i<graph->vc ; i++){
        for (int j=0; j<graph->vc ; j++){
            if(graph->AM[i][j]==1){     // check for adjacency between i and j
                graph->node[i]->adjNum++;   // if adjacent increment number of adjacent nodes number
                graph->node[i]->adjacent = (_GNODE_ **)realloc( graph->node[i]->adjacent , sizeof(_GNODE_) * (graph->node[i]->adjNum+1));   // reallocate the memory to hold new adjacent member
                graph->node[i]->adjacent[graph->node[i]->adjNum-1] = graph->node[j];    // points to another node
            }
        }
        graph->node[i]->adjacent[graph->node[i]->adjNum] = NULL; // set last adjacent node to NULL
    }
}

The function freeGraph(_GRAPH_ *graph) is supposed to de-allocate all the memory allocated to graph. But while calling this function the programs runs into a SEGMENTATION FAULT .

void freeGraph(_GRAPH_ *graph){
    // free graph
    for (int i=0; i<graph->vc; i++) {
        // free each node data
        printf("\nLoop: %d\n", i);
            // free each adjacent node
        for (int k=0; k<graph->node[i]->adjNum; k++){
            if(graph->node[i]->adjacent[k]!=NULL){
                free(graph->node[i]->adjacent[k]);
            }
        }
        if(graph->node[i]->adjacent!=NULL) {
            free(graph->node[i]->adjacent);
        }
        free(graph->node[i]);
    }
    free(graph->node);
    for(int i=0; i<graph->vc; i++)
        if(graph->AM[i]!=NULL) free(graph->AM[i]);
    free(graph->AM);
    free(graph->name);
    free(graph);
}

The function called printAllGraphNodeNumber(const _GRAPH_ *graph) which prints all the node number and their adjacent nodes number.

void printAllGraphNodeNumber(const _GRAPH_ *graph) {
    printf("NODES IN THE GRAPH: ");
    for (int i=0; i<graph->vc ; i++) {
        printf("\nNode Number : %d\n|----->  Adjacent Nodes: ",graph->node[i]->nodeNumber);
        for (int j=0; j<graph->node[i]->adjNum; j++){
            printf("%d , ", graph->node[i]->adjacent[j]->nodeNumber );
        }
    }
}

The content of the sample file "Graph01.txt" is: And the maximum number of vertices is : 20

one
AM
10
0100010011
1000110100
0000100001
0000000010
0110000100
1100000001
0000000001
0100100010
1001000101
1010011010

I solved this problem by removing the piece of code commented as free each adjacent node in the function freeGraph() . Resulting code looks like:

void freeGraph(_GRAPH_ *graph){
    // free graph
    for (int i=0; i<graph->vc; i++) {
        // free each node data
        printf("\nLoop: %d\n", i);
            // free each adjacent node
            /*
        for (int k=0; k<graph->node[i]->adjNum; k++){
            if(graph->node[i]->adjacent[k]!=NULL){
                free(graph->node[i]->adjacent[k]);
            }
        }*/
        if(graph->node[i]->adjacent!=NULL) {
            free(graph->node[i]->adjacent);
        }
        free(graph->node[i]);
    }
    free(graph->node);
    for(int i=0; i<graph->vc; i++)
        if(graph->AM[i]!=NULL) free(graph->AM[i]);
    free(graph->AM);
    free(graph->name);
    // commenting this out : because this is not dynamically allocated 
    //    free(graph);
}

The reason why I did this is because graph->node[i]->adjacent[k] are the pointers to different nodes of the graph which are going to be freed at last by free(graph->node[i]) .

I checked using Valgrind , its working perfectly. Stats showing a clean bill of health. All allocations are freed. I think I am good to go.

PS: I still don't think its an optimal solution. But this is how I solved my problem. Hope anybody else can provide a better answer.

Here's an adaptation of your code. I've cleaned up many points that I made in the comments to the question. In particular:

  • The structure names are removed from the namespace reserved for the implementation.
  • Operations are checked and errors are reported.
  • If the file open fails, uninitialized pointers are not freed.
  • If the file open fails, the code doesn't try to close the unopened file stream.
  • Because failure to allocate exits the program, there are fewer checks for nullness.
  • The code counts how many adjacent nodes there are in a row and allocates the correct space all at once, rather than allocating the array one element at a time.
  • I've preserved the null terminator for the adjacency list, though it isn't really necessary.
  • By default, the program reads from a file data . You can override that by specifying the file to read on the command line.

I also modified the code so that readGraphFromTxt() returns an allocated pointer, so that freeGraph() can correctly free it.

The main fix is to ensure that each malloc() is freed once. As you diagnosed, you were trying to free the nodes pointed at by the adjacency lists, which was incorrect behaviour. Those nodes should be freed en masse since they were allocated en masse.

I've tidied up the printing (at least according to my standards).

I used the files stderr.c and stderr.h available from GitHub to simplify the error reporting. When error reporting is simple, it is less likely to be omitted. I've cheated and used assert() in a couple of places instead od a formal error test and message.

Code

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "stderr.h"

typedef struct Node
{
    int nodeNumber;
    int adjNum;
    struct Node **adjacent;
} Node;

typedef struct Graph
{
    int vc;
    char *name;
    int **AM;
    Node **node;
} Graph;

static void createGraphFromAM(Graph *graph);

static Graph *readGraphFromTxt(const char *filename)
{
    FILE *fp = fopen(filename, "r");
    if (fp == 0)
        err_syserr("failed to open file %s for reading\n", filename);
    Graph *graph = malloc(sizeof(*graph));
    if (graph == 0)
        err_syserr("failed to allocate %zu bytes\n", sizeof(*graph));
    char line[100];
    if (fgets(line, sizeof(line), fp) == 0)
        err_error("premature EOF - no graph name\n");
    line[strcspn(line, "\n")] = '\0';
    char *gname = strdup(line);
    if (fgets(line, sizeof(line), fp) == 0)
        err_error("premature EOF - no auxilliary info\n");
    int vc = -1;
    if (fscanf(fp, "%d", &vc) != 1)
        err_error("format error: didn't get an integer\n");
    if (vc < 1 || vc > 20)
        err_error("size of graph out of control: %d\n", vc);

    int **AM = (int **)malloc(sizeof(int *) * vc);
    if (AM == 0)
        err_syserr("failed to allocate %zu bytes\n", sizeof(int *) * vc);

    for (int i = 0; i < vc; i++)
    {
        AM[i] = (int *)malloc(sizeof(int) * vc);
        if (AM[i] == 0)
            err_syserr("failed to allocate %zu bytes\n", sizeof(int) * vc);
        if (fscanf(fp, "%s", line) == EOF)
            err_error("premature EOF - not enough lines of data for the adjacency matrix\n");
        if (strlen(line) < (size_t)vc)
            err_error("Adjacency matrix line is too short (got [%s] (%zu); wanted %d)\n",
                      line, strlen(line), vc);
        for (int j = 0; j < vc; j++)
        {
            assert(line[j] == '0' || line[j] == '1');
            AM[i][j] = line[j] - '0';
        }
    }

    graph->AM = AM;
    graph->name = gname;
    graph->vc = vc;
    graph->node = 0;
    createGraphFromAM(graph);
    fclose(fp);
    return graph;
}

/* How many times does val appear in array arr of size num? */
static inline size_t val_count(size_t num, const int arr[num], int val)
{
    assert(arr != 0);
    size_t count = 0;
    for (const int *end = arr + num; arr < end; arr++)
    {
        if (*arr == val)
            count++;
    }
    return count;
}

static void createGraphFromAM(Graph *graph)
{
    graph->node = (Node **)malloc(sizeof(*graph->node) * graph->vc);
    if (graph->node == 0)
        err_syserr("failed to allocate %zu bytes\n", sizeof(*graph->node) * graph->vc);

    for (int i = 0; i < graph->vc; i++)
    {
        graph->node[i] = (Node *)malloc(sizeof(Node));
        if (graph->node[i] == 0)
            err_syserr("failed to allocate %zu bytes\n", sizeof(Node));
        graph->node[i]->adjNum = val_count(graph->vc, graph->AM[i], 1);
        graph->node[i]->nodeNumber = i;
        size_t adj_size = sizeof(Node *) * (graph->node[i]->adjNum + 1);
        graph->node[i]->adjacent = (Node **)malloc(adj_size);
        if (graph->node[i]->adjacent == 0)
            err_syserr("failed to allocate %zu bytes\n", adj_size);
    }

    for (int i = 0; i < graph->vc; i++)
    {
        Node *node = graph->node[i];
        int adj = 0;
        for (int j = 0; j < graph->vc; j++)
        {
            if (graph->AM[i][j] == 1)
                node->adjacent[adj++] = graph->node[j];
        }
        node->adjacent[node->adjNum] = NULL;
    }
}

static void freeGraph(Graph *graph)
{
    for (int i = 0; i < graph->vc; i++)
    {
        free(graph->node[i]->adjacent);
        free(graph->node[i]);
    }
    free(graph->node);

    for (int i = 0; i < graph->vc; i++)
        free(graph->AM[i]);
    free(graph->AM);

    free(graph->name);
    free(graph);
}

static void printAllGraphNodeNumber(const Graph *graph)
{
    assert(graph != 0);
    printf("Nodes in the graph %s: %d\n", graph->name, graph->vc);
    for (int i = 0; i < graph->vc; i++)
    {
        printf("Node: %d - Adjacent Nodes: ", graph->node[i]->nodeNumber);
        const char *pad = "";
        for (int j = 0; j < graph->node[i]->adjNum; j++)
        {
            printf("%s%d", pad, graph->node[i]->adjacent[j]->nodeNumber);
            pad = ", ";
        }
        putchar('\n');
    }
}

int main(int argc, char **argv)
{
    err_setarg0(argv[0]);
    const char *filename = "data";
    if (argc == 2)
        filename = argv[1];
    Graph *graph = readGraphFromTxt(filename);
    if (graph != 0)
    {
        printAllGraphNodeNumber(graph);
        freeGraph(graph);
    }
    return 0;
}

Data

A copy of what was supplied in the question:

one
AM
10
0100010011
1000110100
0000100001
0000000010
0110000100
1100000001
0000000001
0100100010
1001000101
1010011010

Output

Nodes in the graph one: 10
Node: 0 - Adjacent Nodes: 1, 5, 8, 9
Node: 1 - Adjacent Nodes: 0, 4, 5, 7
Node: 2 - Adjacent Nodes: 4, 9
Node: 3 - Adjacent Nodes: 8
Node: 4 - Adjacent Nodes: 1, 2, 7
Node: 5 - Adjacent Nodes: 0, 1, 9
Node: 6 - Adjacent Nodes: 9
Node: 7 - Adjacent Nodes: 1, 4, 8
Node: 8 - Adjacent Nodes: 0, 3, 7, 9
Node: 9 - Adjacent Nodes: 0, 2, 5, 6, 8

Having upgraded to macOS High Sierra 10.13, I am (once again) without Valgrind. However, several other tools reassure me that this is probably correct.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM