How do I free struct pointers with nested double pointers?

Viewed 147

I pasted code at the bottom that allocates lots of pointers but doesn't free any. I have a struct named Node that has fields of type struct Node**. In my main function I have the variable: Node** nodes = malloc(size * typeof(Node*));. I would like to know how to properly deallocate nodes.

typedef struct Node {
    size_t id;              // identifier of the node
    int data;               // actual data
    size_t num_parents;     // actual number of parent nodes
    size_t size_parents;    // current maximum capacity of array of parent nodes
    struct Node** parents;  // all nodes that connect from "upstream"
    size_t num_children;    // actual number of child nodes
    size_t size_children;   // current maximum capacity of array of children nodes
    struct Node** children; // all nodes that connect "downstream"
} Node;

I've pasted the whole code down at the bottom because it is already almost minimal (only things we don't need here are the printing function and find_smallest_value function). VS2019 also gives me two warnings for two lines within the main loop in the main function where I'm allocating each node:

Node** nodes = malloc((num_nodes + 1) * sizeof(Node*));
        for (size_t i = 1; i <= num_nodes; i++) {
            nodes[i] = malloc(sizeof(Node)); // WARNING Buffer overrun while writing to 'nodes':  the writable size is '((num_nodes+1))*sizeof(Node *)' bytes, but '16' bytes might be written.
            nodes[i]->id = i; // WARNING Reading invalid data from 'nodes':  the readable size is '((num_nodes+1))*sizeof(Node *)' bytes, but '16' bytes may be read.

I don't understand these warnings at all. Finally, you can obtain large input for this program from this website. Just save it to a text file and modify the hardcoded file name in the main function. The program runs fine if I comment out the last lines where I try to deallocate my nodes. My attempt at deallocating crashes the program. I'd greatly appreciate if anyone could explain the correct way to do it.

Explaining the purpose of the code:

The code at the bottom has the following goal. I'm trying to build a directed graph where every vertex has a label and a value. An example of such a graph. The graphs I'm interested in all represent hierarchies. I am to perform two operations on these graphs: I. given a vertex, find the one with smallest value that above it in the hierarchy and print its value; II. given a pair of vertices, swap their places. For example, given vertices 4 and 2 in that figure, the result of operation II would be the same graph but the vertices labelled 2 and 4 would have their labels and data swapped. Given vertex 6, the result of operation I would be "18". I implemented both operations successfully, I believe.

My main function reads from a txt file in order to build the data structure, which I chose to be a multiply linked list. Any input file should be of the following format (this file generates the graph shown in the figure and performs some operations on it):

7 8 9
21 33 33 18 42 22 26
1 2
1 3
2 5
3 5
3 6
4 6
4 7
6 7
P 7
T 4 2
P 7
P 5
T 1 4
P 7
T 4 7
P 2
P 6
  • First line has three numbers: number of vertices (nodes), number of edges (k, connections) and number of instructions (l, either operation I or II).
  • Second line is the data in each node. Labels correspond to the index of the node.
  • The next k lines consist of two node labels: left is a parent node, right is a child node.
  • The next l lines consist of instructions. P stands for operation I and it's followed by the label of the node. T stands for operation II and it's followed by the two labels of the nodes to be swapped.
  • The entire pattern can repeat.

The code:

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

typedef unsigned int uint;

typedef struct Node {
    size_t id;              // identifier of the node
    int data;               // actual data
    size_t num_parents;     // actual number of parent nodes
    size_t size_parents;    // current maximum capacity of array of parent nodes
    struct Node** parents;  // all nodes that connect from "upstream"
    size_t num_children;    // actual number of child nodes
    size_t size_children;   // current maximum capacity of array of children nodes
    struct Node** children; // all nodes that connect "downstream"
} Node;

Node** reallocate_node_array(Node** array, size_t* size) {
    Node** new_array = realloc(array, sizeof(Node*) * (*size) * 2);
    if (new_array == NULL) {
        perror("realloc");
        exit(1);
    }
    *size *= 2;
    return new_array;
}

// The intention is to pass `num_children` or `num_parents` as `size` in order to decrease them
void remove_node(Node** array, size_t* size, size_t index) {
    for (size_t i = index; i < *size - 1; i++) {
        array[i] = array[i + 1];
    }
    (*size)--; // the decrement to either `num_children` or `num_parents`
}

void remove_parent(Node* node, size_t id) {
    for (size_t i = 0; i < node->num_parents; i++) {
        if (node->parents[i]->id == id) {
            remove_node(node->parents, &node->num_parents, i);
        }
    }
}

void remove_child(Node* node, size_t id) {
    for (size_t i = 0; i < node->num_children; i++) {
        if (node->children[i]->id == id) {
            remove_node(node->children, &node->num_children, i);
        }
    }
}

void add_parent(Node* node, Node* parent) {
    if (node->num_parents >= node->size_parents) {
        node->parents = reallocate_node_array(node->parents, &node->size_parents);
    }
    node->parents[node->num_parents++] = parent;
}

void add_child(Node* node, Node* child) {
    if (node->num_children >= node->size_children) {
        node->children = reallocate_node_array(node->children, &node->size_children);
    }
    node->children[node->num_children++] = child;
}

uint number_of_digits(int n) {
    uint d = 0;
    do { d++; n /= 10; } while (n != 0);
    return d;
}
// return format: "{ parent1.id parent2.id ...} { id data } { child1.id child2.id ...}"
void print_node(Node node) {
    printf("{ ");
    for (size_t i = 0; i < node.num_parents; i++) {
        printf("%zu ", node.parents[i]->id);
    }
    printf("} [ %zu %d ] { ", node.id, node.data);
    for (size_t i = 0; i < node.num_children; i++) {
        printf("%zu ", node.children[i]->id);
    }
    printf("}\n");
}

void switch_nodes(Node* n1, Node* n2, Node** array) {
    uint temp_id = n1->id;
    uint temp_data = n1->data;
    n1->id = n2->id;
    n1->data = n2->data;
    n2->id = temp_id;
    n2->data = temp_data;
    Node* temp = array[n1->id];
    array[n1->id] = array[n2->id];
    array[n2->id] = temp;
}

int find_smallest_valued_parent(Node* node, uint depth) {
    // has no parents
    if (node->num_parents == 0 || node->parents == NULL) {
        if (depth == 0) return -1; // there was no parent on first call (nothing to report)
        else return node->data;
    }
    else {
        depth++;
        int minimum_value = node->parents[0]->data; // we're guaranteed 1 parent
        for (size_t i = 0; i < node->num_parents; i++) {
            int next_value = find_smallest_valued_parent(node->parents[i], depth);
            if (node->parents[i]->data < next_value) next_value = node->parents[i]->data;
            if (next_value < minimum_value) minimum_value = next_value;
        }
        return minimum_value;
    }
}

void free_node_array(Node** array, size_t start, size_t end) {
    for (size_t i = start; i < end; i++) {
        free(array[i]);
    }
    free(array);
}

int main() {
    char* file_name = "input_feodorv.txt";

    FILE* data_file = fopen(file_name, "r");
    if (data_file == NULL) {
        printf("Error: invalid file %s", file_name);
        return 1;
    }
    for (;;) {
        size_t num_nodes, num_relationships, num_instructions;
        if (fscanf(data_file, "%zu %zu %zu\n", &num_nodes, &num_relationships, &num_instructions) == EOF)
            break;

        Node** nodes = malloc((num_nodes + 1) * sizeof(Node*));
        for (size_t i = 1; i <= num_nodes; i++) {
            nodes[i] = malloc(sizeof(Node)); // WARNING Buffer overrun while writing to 'nodes':  the writable size is '((num_nodes+1))*sizeof(Node *)' bytes, but '16' bytes might be written.
            nodes[i]->id = i; // WARNING Reading invalid data from 'nodes':  the readable size is '((num_nodes+1))*sizeof(Node *)' bytes, but '16' bytes may be read.
            fscanf(data_file, "%u ", &nodes[i]->data);
            nodes[i]->num_children = 0;
            nodes[i]->size_children = 2;
            nodes[i]->children = (Node**)malloc(2 * sizeof(Node*));
            for (size_t j = 0; j < 2; j++) nodes[i]->children[j] = malloc(sizeof(Node));
            nodes[i]->num_parents = 0;
            nodes[i]->size_parents = 2;
            nodes[i]->parents = (Node**)malloc(2 * sizeof(Node*));
            for (size_t j = 0; j < 2; j++) nodes[i]->parents[j] = malloc(sizeof(Node));
        }

        for (size_t i = 0; i < num_relationships; i++) {
            size_t parent_id, child_id;
            fscanf(data_file, "%zu %zu\n", &parent_id, &child_id);

            add_child(nodes[parent_id], nodes[child_id]);
            add_parent(nodes[child_id], nodes[parent_id]);
        }

        for (size_t i = 0; i < num_instructions; i++) {
            char instruction;
            fscanf(data_file, "%c ", &instruction);
            if (instruction == 'P') {
                size_t id;
                fscanf(data_file, "%zu\n", &id);
                int minimum_value = find_smallest_valued_parent(nodes[id], 0);
                if (minimum_value == -1) printf("*\n");
                else printf("%u\n", minimum_value);
            }
            else {
                size_t n1_id, n2_id;
                fscanf(data_file, "%zu %zu\n", &n1_id, &n2_id);
                switch_nodes(nodes[n1_id], nodes[n2_id], nodes);
            }
        }
        /**/
        for (size_t i = 1; i <= num_nodes; i++) {
            free_node_array(nodes[i]->parents, 0, nodes[i]->size_parents);
            free_node_array(nodes[i]->children, 0, nodes[i]->size_children);
        }
        free_node_array(nodes, 0, num_nodes);
        /**/
    }
}
1 Answers

There is a memory leak in your code. In the main() function, you are doing:

    nodes[i]->children = (Node**)malloc(2 * sizeof(Node*));
    for (size_t j = 0; j < 2; j++) nodes[i]->children[j] = malloc(sizeof(Node));

and

    nodes[i]->parents = (Node**)malloc(2 * sizeof(Node*));
    for (size_t j = 0; j < 2; j++) nodes[i]->parents[j] = malloc(sizeof(Node));

that mean, allocating memory to nodes[i]->children[j] and nodes[i]->parents[j] pointers.

In add_child() and add_parent() function, you are making them point to some other node resulting in loosing there allocated memory reference:

void add_parent(Node* node, Node* parent) {
    .....
    node->parents[node->num_parents++] = parent;
}

void add_child(Node* node, Node* child) {
    .....
    node->children[node->num_children++] = child;
}

You actually don't need to allocate memory to nodes[i]->children[j] and nodes[i]->parents[j] pointers in main() because these pointer are suppose to point to the existing nodes of the graph and you are already allocating memory to those nodes here in main():

nodes[i] = malloc(sizeof(Node));

nodes[i] is an element of array of all the nodes of the given graph and childrens and parents pointer should point to these nodes only.

Now coming to freeing these pointers:

The way you are freeing the nodes of graph is not correct. Look at free_node_array() function:

void free_node_array(Node** array, size_t start, size_t end) {
    for (size_t i = start; i < end; i++) {
        free(array[i]);
    }
    free(array);
}

and you are calling it in this way:

        for (size_t i = 1; i <= num_nodes; i++) {
            free_node_array(nodes[i]->parents, 0, nodes[i]->size_parents);
            free_node_array(nodes[i]->children, 0, nodes[i]->size_children);
        }

That mean, you are freeing the pointers pointed by array of pointers nodes[i]->parents and nodes[i]->children. The members of nodes[i]->parents and nodes[i]->children are pointers which are pointing to elements of nodes array. It is perfectly possible that a node can be a child 1 or more parents and a parent node can have more than 1 child. Now assume case where a child node is pointed by 2 parent nodes, say n1 and n2. When you call free_node_array() function and pass the first parent (n1), it will end you freeing that child node and when free_node_array() function is called to free the second parent (n2), it will try to free the node which is already freed while freeing n1.

So, this way of freeing the memory is not correct. The correct way to free the memory is, simply, free the elements of nodes array because it's the array which will contain all the nodes of given graph and parents and children pointers are supposed to point to these nodes only. No need to traverse the hierarchy of parent and child nodes. To free the graph appropriately, you should do:

  • Traverse through the nodes array and for each element of array:

    • Free the array of parents pointer (free (nodes[i]->parents).
    • Free the array of children pointer (free (nodes[i]->children).
    • Free that element of nodes array (free (nodes[i]).

Once, this is done then free the nodes array - free (nodes).

Related