C编程malloc和NULL

时间:2018-09-29 16:40:17

标签: c malloc

我对C编程非常陌生,并且正在为一个涉及二进制搜索树的学校项目工作。在说明中,它指出我们必须为树中的每个节点调用malloc。

当我声明头节点时,以下语句之间有区别吗?如果是,那是正确的方法?我觉得第一种是正确的方法:

    struct Node* head = NULL; 
    head = (struct Node*) malloc(1 * sizeof(struct Node)); 

    struct Node* head = (struct Node*) malloc(1 * sizeof(struct Node));
    head = NULL; 

8 个答案:

答案 0 :(得分:2)

如果您删除第二行(使用= NULL),则版本B是版本A的简化版本,因为这会撤消您在第一行所做的所有工作。

让我们分解一下,从(struct Node*) malloc(1 * sizeof(struct Node))开始。 malloc调用分配一定数量的内存,这是struct Node大小的1倍。开头只是说“将其作为指向struct Node的指针”;没有这个,它就被当作“指向某物的指针”(又名void *)。

您实际上并不需要 开头的(struct Node *),因为您要为其分配的变量是struct Node *变量,但是有些人喜欢它。我个人不写它,因为它会使计算机停止运行,并警告我有关我经常犯的错字引起的某种错误。

现在由于版本之间的差异。版本A:

  1. 将指针设置为NULL
  2. 分配一些内存。
  3. 设置指针以指向该内存。

版本B:

  1. 分配一些内存。
  2. 设置指针以指向该内存。
  3. 将指针设置为NULL ...

您会明白为什么会有问题。

  • 您有一个指向NULL的指针,而不是可以存储数据的指针;和
  • 您已经为程序声明了一些内存,但是无法free对其进行存储;您的程序泄漏了内存。

这很糟糕。我建议使用版本B,但仅使用它的第一行。永远不要在没有malloc先将NULL指向free的情况下设置指向import { commitMutation, graphql } from "react-relay"; import { REQUEST_STATUS_NEW } from "../../../constants"; const mutation = graphql` mutation CreateRequestMutation($input: CreateRequestInput!) { createRequest(input: $input) { request { id tid title description price commission value expirationDate createdAt completionDate multipleResponders draft status requestProposals type { id name } industry { id name } applications { id } myApplication { id } } } } `; let tempId = 0; function sharedUpdater(store, request) { const root = store.getRoot(); const newRequests = root .getLinkedRecords("requests", { own: true }) .filter(r => r); if (!newRequests.find(m => m.getValue("id") === request.getValue("id"))) { newRequests.push(request); } root.setLinkedRecords(newRequests, "requests", { own: true }); } export const commit = (environment, input) => { tempId += 1; return commitMutation(environment, { mutation, variables: { input }, updater: store => { const payload = store.getRootField("createRequest"); console.log('payload: ', payload) const request = payload.getLinkedRecord("request"); sharedUpdater(store, request); } }); }; 的指针,否则您的程序会慢慢中断。

答案 1 :(得分:1)

第一个是正确的。第二个导致内存泄漏,并且(有可能;如果继续使用head)会导致段错误。

如果是第二个,则将指针分配给从malloc返回到head的一段代码。然后,立即用NULL覆盖该值,导致head变量基本上没有用。同时,您丢失了指向malloc d内存的指针。您永远不能使用或回收该内存,这被称为“内存泄漏”。

答案 2 :(得分:1)

第一个是“正确的”,但是对head = NULL进行了不必要的分配,因为紧随其后的是head = ... malloc(...)的第二个分配,覆盖了NULL分配。请注意,如果malloc()无法分配内存,它将返回NULL。

答案 3 :(得分:1)

在第二个版本中,您用malloc()覆盖了从NULL获得的指针值,因此无法再访问该内存,这种情况称为内存泄漏:绝对不会你想要什么。

第一个版本不是正确的,但是初始化指向NULL的指针是多余的,因为您将malloc()的返回值立即存储到其中。编译器可能会省略此冗余存储,但是出于可读性考虑,您可以将其简化为:

struct Node *head = (struct Node*)malloc(1 * sizeof(struct Node));

请注意,强制转换malloc的返回值在C语言中没有用,并且容易出错。建议只写:

struct Node *head = malloc(1 * sizeof(struct Node));

事实上,1 *通常被省略:

struct Node *head = malloc(sizeof(struct Node));

请注意,在malloc参数中使用显式类型也是容易出错的,因为C编译器将不会在分配的大小和指针类型之间执行任何类型的一致性检查。如果以后更改head的类型或错误地使用其他类型,则大小可能不正确。为了更安全地指定要分配的大小,请使用目标指针的目标类型:

struct Node *head = malloc(sizeof(*head));

为获得更安全的选择,请使用calloc()将内存初始化为所有位0,在大多数当前硬件上,整数和浮点成员的值为零,所有指针类型的值为空指针。另一个好处是,您可以按照自己的意愿指定要分配的项目数:

struct Node *head = calloc(1, sizeof(*head));  // all members initialized to zero

在所有情况下,强烈建议测试内存分配是否成功:

struct Node *head = calloc(1, sizeof(*head));  // all members initialized to zero
if (head == NULL) {
    fprintf(stderr, "memory allocation failed\n");
    exit(EXIT_FAILURE);
}

您可以使用sizeof *head代替sizeof(*head)进一步简化表达式。这些是等效的,括号是多余的。我个人忽略了它们,仅将其用作裸标识符,例如sizeof head,但将它们用于任何其他表达式。相反,对于类型,必须使用括号,如sizeof(struct Node)

答案 4 :(得分:1)

“如果是,那是正确的方法?”表示2个中至少有1个是不错的选择。

使用这些公理

  1. 首选dry vs. wet代码。放下演员。无需重新分配尚未使用的变量。

  2. Resource acquisition is initialization。尽可能初始化变量。

  3. 检查分配成功。 *alloc()呼叫成功了吗?

  4. 分配所引用对象的大小,而不是类型。易于编写正确的代码,进行审查和维护。


让我们考虑第3种选择,因为2个OP代码段不能很好地遵循所有这些公理。

struct Node* head = malloc(sizeof *head); 

// Check for allocation failure
if (head == NULL) {
  // Handle out of memory in some fashion
  fprintf(stderr, "Out of memory\n");
  exit(EXIT_FAILURE);
} 

答案 5 :(得分:0)

'c'中的所有语句均按顺序执行(跳转除外,例如forwhile循环)。

因此,在第一个片段中,您声明了变量“ head”并为其分配了NULL值,其次,您分配了内存并将其地址分配给了head,覆盖了先前的{{1} }。现在,您可以使用“ head”变量指向程序中分配的内存。

在第二个片段中,您做了相反的事情。您首先分配了内存,并将其地址分配给了NULL,然后用NULL覆盖了该值。现在,由于丢失了指针,您无法指向已分配的内存。您的程序将无法运行并且内存泄漏,因为以后无法释放它(因为它没有指向它的指针信息)。

答案 6 :(得分:0)

您甚至可以声明struct Node* head = (struct Node*) malloc(sizeof(struct Node))而无需初始化NULL,因为您先声明然后直接分配内存。如果您确实想将先前分配的内容填为零,请使用bzero(head, sizeof(struct Node));(包括strings.h)。

第二种方法是错误的,因为现在头指向NULL,并且不再引用以前分配的内存,从而导致内存泄漏。

答案 7 :(得分:-1)

struct Node* head = NULL; 
head = (struct Node*) malloc(1 * sizeof(struct Node)); 

此格式不必要地将head初始化为NULL(空指针),然后立即将head重新分配给调用malloc()的结果。这并不是直接有害的-大多数编译器都可以避免进行初始初始化。但是,这不是一个好习惯。

在C中不需要显式类型转换(aka强制转换)。如果没有前面的#include <stdlib.h>,也会引起未定义的行为,因为-如果没有<stdlib.h>-编译器将假定{{1} }返回malloc()

如果没有前面的int,则没有类型转换,该赋值将触发编译错误,因为#include <stdlib.h>无法隐式转换为指针。类型转换允许通过强制从int到指针的转换来编译代码。最终结果是int返回一个malloc()指针,该值被转换为void,该int被转换回一个指针。问题是指针不能保证在往返的转换中生存,因此int可能没有指向任何有效的指针。因此,该指针的任何后续用法(例如访问该节点的成员)都将产生不确定的行为。

所以有两件事可以使这个示例更好;

  • 直接初始化head,而不是将其初始化为head,然后立即重新分配。
  • 放弃类型转换

最终结果将是

NULL

或者,以避免与两次键入struct Node* head = malloc(1 * sizeof(struct Node)); 相关的问题(例如,以后再更改一次而不更改另一个)

struct Node

以上内容在您的程序中是否更正确,取决于 other 代码对struct Node* head = malloc(1 * sizeof(*head)); 的处理方式。

第二种情况

head

第一行与第一个示例具有相同的净效果,因此我建议对该示例进行调整。

第二条语句(struct Node* head = (struct Node*) malloc(1 * sizeof(struct Node)); head = NULL; )立即丢弃head = NULL返回的值。您的程序现在无法使用该内存,也无法释放它(因为malloc()存储的值未存储在任何变量中)。但是,即使您的程序无法再访问malloc()分配的内存,也将保持分配状态。标准C中没有任何方法可以使它在程序运行时被神奇地释放。

如果您多次执行该代码(例如,在重复调用的函数中),则每次都会重复泄漏,并且在程序运行时不会释放任何分配。如果做得足够频繁,可能会导致程序耗尽可用内存,从而阻止正确执行。