动态列表中的Malloc函数

时间:2018-01-03 23:13:46

标签: c list dynamic

我开始使用动态列表并且我不明白为什么即使在main()程序中声明第一个节点时也需要使用malloc函数,下面的代码应该只是打印第一个节点中包含的数据,但如果我没有使用malloc函数初始化节点,它就不起作用了:

 struct node{
    int data;
    struct node* next;
};

void insert(int val, struct node*);

int main() {
    struct node* head ;
    head->data = 2;
    printf("%d \n", head->data);

}

4 个答案:

答案 0 :(得分:3)

技术上你没有,但维护具有相同内存模式的所有节点对你来说只是一个优势,没有真正的缺点。

假设所有节点都存储在动态内存中。

你的“插入”程序会更好地命名为“add”或(对于完整的功能上下文)“cons”,它应该返回新节点:

struct node* cons(int val, struct node* next)
{
  struct node* this = (struct node*)malloc( sizeof struct node );
  if (!this) return next;  // or some other error condition!
  this->data = val;
  this->next = next;
  return this;
}

构建列表现在非常简单:

int main()
{
  struct node* xs = cons( 2, cons( 3, cons( 5, cons( 7, NULL ) ) ) );
  // You now have a list of the first four prime numbers.

很容易处理它们。

  // Let’s print them!
  {
    struct node* p = xs;
    while (p)
    {
      printf( "%d ", p->data );
      p = p->next;
    }
    printf( "\n" );
  }

  // Let’s get the length!
  int length = 0;
  {
    struct node* p = xs;
    while (p)
    {
      length += 1;
      p = p->next;
    }
  }
  printf( "xs is %d elements long.\n", length );

顺便说一下,在命名时应尽量保持一致。您已将节点数据命名为“data”,但构造函数的参数将其称为“val”。你应该选择一个并坚持下去。

此外,通常是:

typedef struct node node;

现在除struct node定义之外的所有地方都可以使用node这个词。

哦,我差点忘了:不要忘记用适当的析构函数清理。

node* destroy( node* root )
{
  if (!root) return NULL;
  destroy( root->next );
  free( root );
  return NULL;
}

main()的附录:

int main()
{
  node* xs = ...

  ...

  xs = destroy( xs );
}

答案 1 :(得分:3)

当你声明一个变量时,你定义变量的类型,然后它就是 名称,也可以选择声明它的初始值。

每种类型都需要特定数量的内存。例如ul就是 32位操作系统上32位长,64位上8位长。

函数中声明的变量通常存储在相关的堆栈中 与功能。函数返回时,该函数的堆栈为 不再可用,变量不再存在。

当您需要变量的值/对象时,即使在函数之后也存在 返回,然后你需要在程序的不同部分分配内存, 通常是堆。这正是intmallocrealloc所做的。

否则

calloc

是错的。您已声明名为struct node* head ; head->data = 2; 的{​​{1}}类型的指针, 但你没有给它任何东西。所以它指向一个未指定的 记忆中的位置。 head尝试将值存储在未指定的位置 位置和程序很可能会因段错误而崩溃。

主要是你可以这样做:

struct node

head->data = 2将保存在堆栈中,只要int main(void) { struct node head; head.data = 2; printf("%d \n", head.data); return 0; } 没有,它就会一直存在 返回。但这只是一个很小的例子。在一个复杂的程序中你 有更多变量,对象等等,简单地声明所有变量是个坏主意 head中需要的变量。因此,最好在它们创建对象时创建它们 需要。

例如,你可以有一个创建对象的函数和另一个 调用main并使用该对象。

main

这里create_node使用malloc为一个struct node *create_node(int data) { struct node *head = malloc(sizeof *head); if(head == NULL) return NULL; // no more memory left head->data = data; head->next = NULL; return head; } struct node *foo(void) { struct node *head = create_node(112); // do somethig with head return head; } 分配内存 object,使用某些值初始化对象并返回指向该内存位置的指针。 create_node调用struct node并对其执行某些操作并返回 宾语。如果另一个函数调用foo,则此函数将获取该对象。

create_node还有其他原因。请考虑以下代码:

foo

在这种情况下,您知道您将需要4个整数。但有时你需要一个 例如,在运行时期间仅知道元素数量的数组 因为它取决于一些用户输入。为此,您还可以使用malloc

void foo(void)
{
    int numbers[4] = { 1, 3, 5, 7 };

    ...
}

当您使用mallocvoid foo(int size) { int *numbers = malloc(size * sizeof *numbers); // now you have "size" elements ... free(numbers); // freeing memory } malloc时,您需要释放内存。如果 你的程序不再需要内存了,你必须使用realloc(比如 最后一个例子。请注意,为简单起见,我省略了calloc的使用 free的示例。

答案 2 :(得分:2)

你所调用的是未定义的行为,因为你没有真正的节点,你有一个指针到一个实际上并不指向节点的节点。使用malloc和朋友创建一个可以驻留实际节点对象的内存区域,以及节点指针可以指向的位置。

在您的代码中,struct node* head是一个指向无处的指针,并且正如您所做的那样取消引用它是未定义的行为(通常会导致段错误)。您必须先将head指向有效的struct node,然后才能安全取消引用它。一种方式是这样的:

int main() {
    struct node* head;
    struct node myNode;
    head = &myNode;  // assigning the address of myNode to head, now head points somewhere
    head->data = 2;  // this is legal
    printf("%d \n", head->data);  // will print 2

}

但是在上面的例子中,myNode是一个局部变量,一旦函数存在就会超出范围(在本例中为main)。正如您在问题中所说,对于链接列表,您通常希望malloc数据,以便可以在当前范围之外使用。

int main() {
    struct node* head = malloc(sizeof struct node);
    if (head != NULL)
    {
        // we received a valid memory block, so we can safely dereference
        // you should ALWAYS initialize/assign memory when you allocate it.
        // malloc does not do this, but calloc does (initializes it to 0) if you want to use that
        // you can use malloc and memset together.. in this case there's just
        // two fields, so we can initialize via assignment.
        head->data = 2;
        head->next = NULL;
        printf("%d \n", head->data);

        // clean up memory when we're done using it
        free(head);
    }
    else
    {
        // we were unable to obtain memory
        fprintf(stderr, "Unable to allocate memory!\n");
    }
    return 0;
}

这是一个非常简单的例子。通常对于链接列表,您将具有插入函数(malloc通常发生并删除函数(free通常发生的位置。您将在至少有一个head指针总是指向列表中的第一个项目,对于双链表,你也需要一个tail指针。还可以有打印函数,{{但是,无论如何,你必须为实际对象分配空间。deleteEntireList是一种方法,所以内存的有效性在整个程序运行时间内都会持续存在。

编辑:

不正确的。这绝对适用于mallocint,它适用于任何对象和指针。如果你有以下内容:

int*

这是您在OP中所有的未定义行为。指针必须指向有效的内容才能取消引用它。在上面的代码中,int main() { int* head; *head = 2; // head uninitialized and unassigned, this is UB printf("%d\n", *head); // UB again return 0; } 未初始化,它没有确定性地指向任何内容,并且只要执行head(无论是读还是写),就会调用未定义的行为。与您的*head一样,您必须执行以下操作才能更正:

struct node

或者你可以做

int main() {
        int myInt;  // creates space for an actual int in automatic storage (most likely the stack)
        int* head = &myInt;  // now head points to a valid memory location, namely myInt
        *head = 2;  // now myInt == 2
        printf("%d\n", *head);  // prints 2

        return 0;
    }

这些规则适用于您正在处理的任何基本类型或数据结构。指针必须指向某些内容,否则取消引用它们是未定义的行为,并且您希望在发生这种情况时会出现段错误,因此您可以在TA评级之前或客户演示之前跟踪错误。墨菲定律规定UB在呈现时会一直崩溃。

答案 3 :(得分:1)

语句struct node* head;定义节点对象的指针,但不定义节点对象本身。由于您没有初始化指针(即通过让它指向由malloc - 语句创建的节点对象),因此与head->data一样取消引用此指针会产生未定义的行为。< / p>

克服这个问题的两种方法,(1)动态分配内存 - 产生具有动态存储持续时间的对象,或(2)将对象本身定义为具有自动存储持续时间的本地变量:

(1)动态存储时间

int main() {
    struct node* head = calloc(1, sizeof(struct node));
    if (head) {
      head->data = 2;
      printf("%d \n", head->data);
      free(head);
    }
}

(2)自动存储时间

int main() {
    struct node head;
    head.data = 2;
    printf("%d \n", head.data);
}