如何使用嵌套双指针释放结构指针?

How do I free struct pointers with nested double pointers?

我在底部粘贴了分配大量指针但不释放任何指针的代码。我有一个名为 Node 的结构,它具有 struct Node** 类型的字段。在我的主要功能中,我有变量:Node** nodes = malloc(size * typeof(Node*));。我想知道如何正确解除分配 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;

我将整个代码粘贴在底部,因为它已经几乎是最小的了(这里我们不需要的只是打印功能和 find_smallest_value 功能)。 VS2019 还针对我分配每个节点的主函数主循环中的两行给出了两个警告:

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.

我完全不理解这些警告。最后,您可以从 this website 获得此程序的大量输入。只需将其保存到文本文件并在主函数中修改硬编码文件名即可。如果我注释掉我尝试解除分配节点的最后几行,程序运行良好。我试图解除分配使程序崩溃。如果有人能解释正确的方法,我将不胜感激。

解释代码的用途:

底部的代码具有以下目标。我正在尝试构建一个有向图,其中每个顶点都有一个标签和一个值。 An example of such a graph. 我感兴趣的图表都表示层次结构。我要对这些图执行两个操作: I. 给定一个顶点,在层次结构中找到它上面具有最小值的那个并打印它的值;二。给定一对顶点,交换它们的位置。例如,给定该图中的顶点 4 和 2,操作 II 的结果将是相同的图,但标记为 2 和 4 的顶点将交换其标签和数据。给定顶点 6,操作 I 的结果将是“18”。我相信我成功地实施了这两个操作。

我的主要功能是从 txt 文件中读取以构建数据结构,我选择将其作为多重链表。任何输入文件应为以下格式(此文件生成如图所示的图形并对其执行一些操作):

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

代码:

#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);
        /**/
    }
}

您的代码中存在内存泄漏。在 main() 函数中,您正在做:

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

也就是说,分配内存给nodes[i]->children[j]nodes[i]->parents[j]指针。

add_child()add_parent() 函数中,您使它们指向某个其他节点,导致丢失分配的内存引用:

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;
}

您实际上不需要为 main() 中的 nodes[i]->children[j]nodes[i]->parents[j] 指针分配内存,因为这些指针假定指向图形的现有节点,而您是已经在 main():

中为这些节点分配了内存
nodes[i] = malloc(sizeof(Node));

nodes[i] 是给定图的所有节点的数组元素,childrensparents 指针应仅指向这些节点。

现在开始释放这些指针:

您释放图形节点的方式不正确。看free_node_array()函数:

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);
}

你是这样称呼它的:

        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);
        }

这意味着,您正在释放指针数组 nodes[i]->parentsnodes[i]->children 指向的指针。 nodes[i]->parentsnodes[i]->children 的成员是指向 nodes 数组元素的指针。一个节点完全有可能是 1 个或多个父节点,并且父节点可以有多个 1 个子节点。现在假设子节点由 2 个父节点指向的情况,比如 n1n2。当您调用 free_node_array() 函数并传递第一个父节点 (n1) 时,它将结束您释放该子节点,而当调用 free_node_array() 函数以释放第二个父节点 (n2), 它会在释放 n1.

时尝试释放已经被释放的节点

所以,这种释放内存的方式是不正确的。释放内存的正确方法是简单地释放 nodes 数组的元素,因为该数组将包含给定图形的所有节点,并且 parentschildren 指针应该仅指向这些节点。无需遍历父子节点的层次结构。要适当地释放图表,您应该这样做:

  • 遍历nodes数组,对于数组的每个元素:

    • 释放 parents 指针数组 (free (nodes[i]->parents)。
    • 释放 children 指针数组 (free (nodes[i]->children)。
    • 释放 nodes 数组 (free (nodes[i]) 的那个元素。

完成后释放 nodes 数组 - free (nodes).