为什么在c中实现链表时使用动态内存分配(即malloc())?

时间:2016-08-06 11:46:32

标签: c memory-management data-structures struct

好的,对于业余程序员来说这个问题可能听起来很愚蠢。但严重的是,这让我感到困扰,对我的这一疑问的庄严回答受到了欢迎。我刚刚开始学习数据结构课程。而困扰我的是:

假设使用了C,

//Implementing a node

struct Node
{
     int data;
     struct *Node;
};

现在,在创建节点时,我们为什么要使用动态内存分配技术,我们使用malloc()。我们不能只创建一个'Struct Node'类型的变量。 例如:

struct Node N1;
 //First node - actually second where !st Node is assumed to be Head.

struct Node *Head = &N1;
struct Node N2;
N2.(*Node) = &N1;

我的代码的某些部分可能不正确,因为我只是一个初学者而且不熟悉C.但是知道你可能已经明白了我的意思。为什么我们不创建Node类型的Node类型的变量来分配内存t新节点为什么要进入动态内存分配的复杂性?

6 个答案:

答案 0 :(得分:3)

首先,您在声明结构时遇到错误。 struct *本身并不表示类型。您必须提供完整的类型名称:

struct Node
{
     int data;
     struct Node *Node;
};

您当然可以使用上面的局部变量来创建链接列表,但是这会将您限制为固定数量的列表元素,即您明确声明的列表元素。这也意味着您无法在函数中创建列表,因为这些变量将超出范围。

例如,如果您这样做:

struct Node *getList()
{
    struct Node head, node1, node2, node3;
    head.Node = &node1;
    node1.Node = &node2;
    node2.Node = &node3;
    node3.Node = NULL;
    return &head;
}

您的列表将限制为4个元素。你们需要成千上万的人吗?此外,通过返回局部变量的地址,当函数返回时它们超出范围,因此访问它们会导致undefined behavior

通过动态分配每个节点,您只受可用内存的限制。

以下是使用动态内存分配的示例:

struct Node *getList()
{
    struct Node *head, *current;
    head = NULL;
    current = NULL;

    // open file
    while (/* file has data */) {
        int data = /* read data from file */
        if (head == NULL) {      // list is empty, so create head node
            head = malloc(sizeof(struct Node *));
            current = head;
        } else {                 // create new element at end of list
            current->next = malloc(sizeof(struct Node *));
            current = current->next;
        }
        current->data = data;
        current->Node = NULL;
    }
    // close file
    return head;
}

这是psedo-code,不会详细介绍读取相关数据,但您可以看到如何创建程序生命周期中存在的任意大小的列表。

答案 1 :(得分:1)

如果这些变量是 local ,在函数范围内定义(即存储在堆栈中),则不应该这样做,因为在离开其范围后访问它们将导致未定义的行为(它们的当你调用其他函数时,内容可能会被覆盖)。实际上,每当你从函数返回一个指向本地,基于堆栈的变量的指针时,你就是做错了。鉴于C的性质,这是有问题的,因为没有任何事情会警告你你做错了什么,而且只有在你再次尝试访问这个区域时它才会失败。

另一方面,如果它们被声明为全局变量(在任何其他函数之外),那么你只是受到那种声明的变量数量的限制。

你可能会声明许多变量,但是跟踪哪一个是“免费”使用将是痛苦的。当然,您甚至可以更进一步说您将拥有一个全局预分配的节点阵列以防止使用malloc,但正如您所做的那样,您只能更接近编写自己的{{1}版本而不是坚持现有的,动态的。

此外,如果您不使用它,所有预先分配的空间都会被浪费,并且您无法在运行时动态增加列表(因此名称​​动态分配 )。

答案 2 :(得分:0)

Here是使用动态内存的一些很好的理由

  1. 声明节点struct Node N1;时,此节点将存储在堆栈内存中。在将获得销毁auto的节点的范围之后。但是在动态的情况下,你有完成后释放内存的句柄。

  2. 当你有一些内存限制。

  3. 当您不知道数组的大小时,动态内存分配将对您有所帮助。

答案 3 :(得分:0)

一个问题可能是您无法使用其他功能将新节点添加到列表中。

请记住,自动变量(如struct Node node100;创建的变量)仅在定义它们的函数内部具有范围。所以当你做这样的事情时:

int main()
{
    struct Node *head;
    /* Some code there you build list as:
       head ---> node1 ---> node2 --> .. ---> node99 
    */

    /* Add a new node using add_node function */
    add_node(head, 555);

    /* Access the last node*/
}

void add_node(struct Node *head, int val)
{
     /* Create new node WITHOUT using malloc */
     struct Node new_node;
     new_node.data = val;

     /* add this node to end of the list */
     /* code to add this node to the end of list */
     /* last_element_of_list.next = &new_node*/

     return;
}

现在您认为已在列表末尾添加了新节点。但是,不幸的是,一旦add_node函数返回,它的生命周期就会结束。当您尝试访问main函数中的最后一个节点时,程序崩溃。

因此,为了避免这种情况,您将把所有代码放在一个函数中 - 这样这些节点的生命周期就不会结束。

将所有代码放在一个函数中是不好的做法,会导致很多困难。

这是一种要求动态内存分配的情况,因为分配有malloc的节点将在范围内,直到使用free释放它,并且您可以放置​​执行不同内容的代码在不同的功能,这是一个很好的做法。

答案 4 :(得分:0)

您没有 使用动态内存来创建链接列表,尽管您肯定想要为每个节点创建单独的变量。如果你想存储多达N个项目,那么你需要声明N个不同的变量,当N变大时,这变得非常痛苦。使用链表的整个想法是它可以根据需要增长或缩小;它是一个动态的数据结构,所以即使你不使用mallocfree,你也会做一些非常相似的事情。

例如,您可以在文件范围内创建节点数组,如下所示:

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

/**
 *  use the static keyword to keep the names from being visible 
 *  to other translation units
 */
static struct node store[N];  /* our "heap" */
static struct node *avail;    /* will point to first available node in store */

您初始化数组,以便每个元素指向下一个,最后一个元素指向NULL

void initAvail( void )
{
  for ( size_t i = 0; i < N - 1; i++ )
    store[i].next = &store[i + 1];
  store[N - 1].next = NULL;
  avail = store;
}

要为列表分配节点,我们会抓取节点avail指向并更新avail以指向下一个可用节点(如果availNULL,那么没有更多的可用节点。)

struct node *getNewNode( void )
{
  struct node *newNode = NULL;

  if ( avail ) /* if the available list isn't empty */
  {
    newNode = avail;       /* grab first available node */
    avail = avail->next;   /* set avail to point to next available node */
    newNode->next = NULL;  /* sever newNode from available list, */
  }                        /* which we do *after* we update avail */
                           /* work it out on paper to understand why */
  return newNode;
}

完成节点后,将其添加回可用列表的开头:

void freeNode( struct node *n )
{
  n->next = avail;
  avail = n;
}

我们没有使用动态内存,因为我们没有调用mallicfree;但是,我们已经重新概括了动态内存功能,还有一个限制,即我们的“堆”具有固定的大小。

请注意,某些嵌入式系统没有这样的堆,因此您必须执行类似的操作才能在此类系统上实现列表。

答案 5 :(得分:0)

你可以写一个没有 malloc 的单链表,但要确保实现是在 main 中完成的。但是如何编写用于遍历、查找最小数等的程序呢?这些结构节点变量将超出范围。

struct node{
    int a;
    struct node* nextNode;
};

int main()
{
    struct node head,node1,node2;
    head.a=45;
    node1.a=98;
    node2.a=3;
    head.nextNode=&node1;
    node1.nextNode=&node2;
    node2.nextNode=NULL;

    if(head.nextNode== NULL)
    {
        printf("List is empty");
    }
    struct node* ptr=&head;
    while(ptr!=NULL)
    {
        printf("%d ",ptr->a);
        ptr=ptr->nextNode;
    }
}