【问题标题】:Malloc shouldn't return NULL on this struct instantiationMalloc 不应在此结构实例化时返回 NULL
【发布时间】:2022-01-12 22:04:43
【问题描述】:

我正在研究一个以图为主题的挑战问题,因此我决定实现一个多重链表(这种数据结构可以表示有向图)。当我尝试为列表创建节点时遇到问题。该程序编译得很好,但是当它运行时,它只会到达某个点并且没有警告就退出。在 VS2019 中以调试模式运行它,IDE 显示我正在尝试取消引用空指针。事实上,在它编译之前,它就强调了可疑的行并警告这可能会发生。但我完全不明白为什么。这是链表的实现(用最小的工作示例,并且确实意味着 minimal,我已尽力...):

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

typedef unsigned int uint;

typedef struct Node {
    uint id;
    uint data;
    size_t num_parents;
    size_t size_parents;
    struct Node * parents;
    size_t num_children;
    size_t size_children;
    struct Node * children;
} Node;

/*/ ORIGINAL PROBLEMATIC REALLOCATING FUNCTION
Node * reallocate_node_array(Node * array, size_t* size) {
    Node * new_array = new_array(Node, *size * 2);  // this doesn't seem to be working as I expected
    for (size_t i = 0; i < *size; i++) {
        new_array[i] = array[i];                    // FAULTY LINE
    }
    *size *= 2;
    return new_array;
}
/**/
//NEW VERSION EDITED TO REFLECT CRAIG ESTEY'S COMMENTS AND ANSWER
Node * reallocate_node_array(Node * array, size_t* size) {
    array = realloc(array, (*size) * 2);
    if (array == NULL) {
        perror("realloc");
        exit(1);
    }
    *size *= 2;
    return array;
}

void remove_node(Node * array, size_t * size, size_t index) {
    for (int i = index; i < *size - 1; i++) {
        array[i] = array[i + 1];
    }
    (*size)--;
}

void remove_parent (Node * node, uint id) {
    for (int 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, uint id) {
    for (int i = 0; i < node->num_children; i++) {
        if (node->children[i].id == id) {
            remove_node(node->children, &node->num_children, i);
        }
    }
}

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

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

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

    FILE * data_file = fopen(file_name, "r");
    if (data_file == NULL) {
        printf("Error: invalid file %s", file_name);
        return 1;
    }

    uint num_nodes, num_relationships;

    fscanf(data_file, "%u %u\n", &num_nodes, &num_relationships);

    // I'm sorry that I'm not checking for the result of malloc in this block.
    // I promise I'll be more responsible in the future.
    Node * nodes = (Node*)malloc((num_nodes + 1) * sizeof(Node));
    for (size_t i = 1; i <= num_nodes; i++) {
        nodes[i].id = i;
        fscanf(data_file, "%u ", &nodes[i].data);
        nodes[i].num_children = 0;
        nodes[i].size_children = 10;
        nodes[i].children = (Node*)malloc(10 * sizeof(Node)); // FAULTY LINE #1
        nodes[i].num_parents = 0;
        nodes[i].size_parents = 10;
        nodes[i].parents = (Node*)malloc(10 * sizeof(Node));  // FAULTY LINE #2 
    }

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

        add_child(&employees[parent_id], &employees[child_id]);
        add_parent(&employees[child_id], &employees[parent_id]);
    }
    
    return 0;
}

当它显示“FAULTY LINE #1”和“#2”时,调试器告诉我程序已到达断点(引发异常)。

main函数的重点是构建如下结构(图): A directed graph with small number of nodes。最简洁的方法是从文件中读取指令。这里是input.txt的内容:

7 8
21 33 33 18 42 22 26
1 2
1 3
2 5
3 5
3 6
4 6
4 7
6 7

第一行:7是节点数; 8 是连接数(关系)。
所有其他行:左数为父节点;正确的数字是子节点。

所以,我的问题,我无法通过 reallocate_node_array 函数以及后来的“FAULTY LINE #1”和“#2”。

编辑


所以我在上面进行了很多编辑,以提供一个最低限度的工作示例并进一步阐明我的背景和困难。无论我做错了什么,如果你能告诉我,我将不胜感激。

然而,在我根据 Craig Estey 的批评编辑了我的 reallocate_node_array 函数之后,我能够在调试中更进一步,并在上述实现中发现了一些可怕的错误。最重要的是我的结构Node 的字段parentschildren 需要是Node** 类型而不是Node*,因为它们应该是数组以表示乘法-链表。考虑到这一点,我重写了如下实现,它按预期运行。但是,我遇到了使用此代码执行进一步任务的问题,这些问题不在此问题的范围内。如果我要提出一个新问题,我一定会牢记您的所有批评,下次尝试写一个好问题。

感谢大家的所有反馈。

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

typedef unsigned int uint;

typedef struct Node {
    uint 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;

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

// 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, uint 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, uint 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) {
        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) {
        reallocate_node_array(node->children, &node->size_children);
    }
    node->children[node->num_children++] = child;
}

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

    FILE* data_file = fopen(file_name, "r");
    if (data_file == NULL) {
        printf("Error: invalid file %s", file_name);
        return 1;
    }

    uint num_nodes, num_relationships;
    fscanf(data_file, "%u %u\n", &num_nodes, &num_relationships);

    Node* nodes = (Node*)malloc((num_nodes + 1) * sizeof(Node));
    for (size_t i = 1; i <= num_nodes; i++) {
        nodes[i].id = i;
        fscanf(data_file, "%u ", &nodes[i].data);
        nodes[i].num_children = 0;
        nodes[i].size_children = 10;
        nodes[i].children = (Node**)malloc(10 * sizeof(Node*));
        for (size_t j = 0; j < 10; j++) nodes[i].children[j] = (Node*)malloc(sizeof(Node));
        nodes[i].num_parents = 0;
        nodes[i].size_parents = 10;
        nodes[i].parents = (Node**)malloc(10 * sizeof(Node*));
        for (size_t j = 0; j < 10; j++) nodes[i].parents[j] = (Node*)malloc(sizeof(Node));
    }

    for (uint i = 0; i < num_relationships; i++) {
        uint parent_id, child_id;
        fscanf(data_file, "%u %u\n", &parent_id, &child_id);
        
        add_child(&nodes[parent_id], &nodes[child_id]);
        add_parent(&nodes[child_id], &nodes[parent_id]);
    }

    return 0;
}

【问题讨论】:

  • 我使用这个定义作为简写:#define new_array(type, size) type*)malloc(size*sizeof(type)) 摆脱它。然后弄清楚为什么事情会随着它到位......
  • 首先,检查malloc 是否返回NULL。那么*size在分配时的值是多少呢?
  • reallocate_node_array调用在哪里?请编辑您的问题并发布。如果是(例如):myarray = reallocate_node_array(myarray,&amp;myarray_size),那么 myarrayoriginal 值会泄露(因为函数不会 not free 旧/原始数组指针) .除非您尝试创建单独的 duplicate 副本,否则为什么不直接使用 realloc
  • 我按照@AndrewHenle 的建议摆脱了#define,并且遇到了一个可能与问题无关的不同错误。我正在调查。
  • @CraigEstey realloc 可能是最好的方法。我来自 C++ 世界,在 C 方面不是很有经验,所以我正在尝试练习,这就是我这样做的原因。我不知道realloc 有不同的效果。对reallocate_node_array 的调用是这样的:node-&gt;children = reallocate_node_array(node-&gt;children, &amp;node-&gt;size_children);

标签: c struct linked-list malloc doubly-linked-list


【解决方案1】:

来自我的顶级评论:

reallocate_node_array 的电话在哪里?请编辑您的问题并发布。如果是(例如):myarray = reallocate_node_array(myarray,&amp;myarray_size),则 myarray 的原始值被泄露(因为该函数不会释放旧的/原始数组指针)。除非您尝试创建单独的重复副本,否则为什么不使用 realloc? – 克雷格·埃斯蒂

您的回复表明这确实是问题所在。

所以,这里是简单的解决方法:

Node *
reallocate_node_array(Node *array, size_t *size)
{

    array = realloc(array,sizeof(*array) * *size * 2);

    if (array == NULL) {
        perror("realloc");
        exit(1);
    }

    *size *= 2;

    return array;
}

但是,当我看到作为 单独 参数传递的数组大小时,我想创建一个包含大小/长度的新“数组”结构。这类似于c++ 向量的作用:

typedef struct {
    Node *data;
    size_t size;
} NodeArray;

void
reallocate_node_array(NodeArray *array)
{

    array->data = realloc(array->data,sizeof(*array->data) * array->size * 2);

    if (array->data == NULL) {
        perror("realloc");
        exit(1);
    }

    array->size *= 2;
}

这有点过分了,因为调用者仍然需要跟踪事情。

这通常是初级 C 程序员的练习。

这是一个增强功能:

typedef struct {
    Node *data;                         // pointer to data
    size_t size;                        // number of elements currently in use
    size_t capacity;                    // number of elements available
} NodeArray;

void
reallocate_node_array(NodeArray *array,size_t need)
// array -- pointer to node array
// need -- number of elements to grow by
{
    size_t size = array->size + need;

    if (size >= array->capacity) {
        array->capacity = size + 100;
        array->data = realloc(array->data,
            sizeof(*array->data) * array->capacity);

        if (array->data == NULL) {
            perror("realloc");
            exit(1);
        }
    }
}

更新:

您应该始终将 realloc 的结果分配给临时变量 - 如果 realloc 无法扩展缓冲区,它会返回 NULL 但将原始缓冲区保留在原处。如果您将结果分配回数组-> 数据并且它为 NULL,那么您将有内存泄漏。 [编辑] - 约翰·博德

JohnBode 我知道这个技巧,但检查 NULL 并退出也很好。在大多数程序/系统上,内存不足是/应该是致命的。没有办法有意义地恢复。您可以处理错误,但程序进展如何? – 克雷格·埃斯蒂

我也是这么想的。我从来没有写过任何分配数组是可选的东西。 – 珊瑚白化

是的。正如我所说,对于大多数程序来说,这是致命的。 TL;DR 不用担心 - 很高兴,因为这是我的“肥皂盒”问题/nits 之一;-)

对于那些基于某些外部操作进行分配的程序(例如,许多客户端连接到服务器),程序应该/必须以另一种方式限制事物,而等到malloc/realloc 返回NULL,此时“为时已晚”。

例如,它应该限制可以同时处理的传入请求的数量。如果我们限制为 N 个请求,并且每个请求需要分配 M 个字节,我们必须事先知道 N * M 可以安全分配。

对于任务关键型实时应用程序,通常所有 [可能] 分配在程序初始化期间完成,并且具有预分配结构/缓冲区的各种子池。这就是我过去为商业、产品级应用程序/系统所做的事情。

对于实时应用,有“实时安全”的概念/规范。也就是说,程序将具有“确定性执行”。还有其他的,但其中一个原则要求在初始化期间完成所有分配,以使内存不足的情况不可能[按设计]。

此外,当分配失败时,程序现在处于不确定和不安全状态。 之前发生了什么其他让我们进入这种状态?

分配失败只是是因为它要求了太多的内存吗?也就是说,我们是否没有进行足够的资源限制检查。这将是一个设计缺陷。

或者,是因为[其他地方]存在错误并且堆已损坏?或者,程序数据/状态的其他部分现在已损坏?

我们无法确定。

为了安全起见,唯一要做的就是尽快中止。否则,当我们不再知道后果会是什么时,允许它继续下去的风险是什么?

(例如)它会[进一步]损坏数据库或删除错误文件等吗?分配失败可能表示 UB(未定义行为),其后果是继续操作向实时控制设备发送 错误 值,从而导致设备出现危险行为。思考:机器人控制、起搏器控制等。

简而言之,在现代系统上,通常有足够多的内存(例如千兆字节)来满足所有正常请求。因此,内存不足表示 错误(例如,执行分配的失控循环)。

[真正] 内存有限的实时嵌入式系统必须事先精心设计(遵循“实时安全”规则/指南),以防止/避免这种情况发生。

【讨论】:

  • 我忘记在分配中添加sizeof [现已修复]。所以,最后看看。如果您的 size 参数是 node 计数 [vs.总字节数],那么这是必需的
  • 从现在开始我会听从你的建议。这不是 问题,但它肯定是一个问题。虽然我很好奇:有没有用realloc 重新分配的替代方法?如果是的话,一个人会怎么做?我应该先释放我的指针,然后再调用malloc 吗?
  • 您应该始终将realloc 的结果分配给一个临时变量 - 如果realloc 无法扩展缓冲区,它会返回NULL 但保留原始缓冲区。如果您将结果分配回array-&gt;data 并且它是NULL,那么您将发生内存泄漏。将结果分配给一个临时指针变量并且不要更新array-&gt;capacity,直到之后你确保realloc成功 - Node *tmp = realloc( array-&gt;data, sizeof *array-&gt;data * (size + 100)); if ( tmp ) { array-&gt;data = tmp; array-&gt;capacity = size + 100; } else { /* handle realloc failure */ }
  • @JohnBode 我知道这个技巧,但检查 NULL 并退出也很好。在大多数程序/系统上,内存不足是/应该是致命的。没有办法有意义地恢复。您可以处理错误,但程序进展如何?
  • 我也是这么想的。我从来没有写过任何分配数组是可选的。
猜你喜欢
  • 2016-05-24
  • 1970-01-01
  • 1970-01-01
  • 2015-06-14
  • 1970-01-01
  • 2017-06-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多