简体   繁体   English

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

[英]Why use dynamic memory allocation(i.e. malloc()) when implementing linked list in c?

Okay this question may sound stupid to the amateur programmers .好吧,对于业余程序员来说,这个问题可能听起来很愚蠢。 But seriously this is bothering me and a solemn answer to this doubt of mine is welcomed.但严重的是,这让我感到困扰,欢迎对我的这个疑问做出严肃的回答。 I have just started to take my first ever course in data structures.我刚刚开始学习我的第一门数据结构课程。 And what is bothering me is this:而困扰我的是:

Assuming C is used,假设使用 C,

//Implementing a node

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

Now while creating a node why do we use the dynamic memory allocation technique where we use malloc().现在,在创建节点时,为什么我们在使用 malloc() 的地方使用动态内存分配技术。 Can't we just create a variable of type ' Struct Node '.我们不能只创建一个“结构节点”类型的变量。 ie something like:即类似:

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;

Well some parts of my code may be incorrect because I am only a beginner and not well versed with C. But by know you may have understood what I basically mean.好吧,我的代码的某些部分可能不正确,因为我只是一个初学者并且不精通 C。但是知道您可能已经理解我的基本意思。 Why don't we create variables of type Node of an Array of type Node to allocate memory t new nodes why get into the complexity of dynamic memory allocation?为什么我们不创建 Node 类型的 Node 类型的数组的变量来分配内存 t 新节点为什么要进入动态内存分配的复杂性?

First off, you have an error in how you declare your struct.首先,您在声明结构的方式上存在错误。 struct * by itself does not denote a type. struct *本身并不表示类型。 You have to give the full type name:您必须提供完整的类型名称:

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

You can certainly use local variables as above to make a linked list, however that limits you to a fixed number of list elements, ie the ones you explicitly declare.您当然可以使用上述局部变量来创建链表,但是这将您限制为固定数量的列表元素,即您明确声明的元素。 That would also mean you can't create a list in a function because those variables would go out of scope.这也意味着您不能在函数中创建列表,因为这些变量会超出范围。

For example, if you did this:例如,如果你这样做:

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

Your list would be restricted to 4 elements.您的列表将被限制为 4 个元素。 What of you needed thousands of them?你们中的什么人需要成千上万个? Also, by returning the address of local variables, they go out of scope when the function returns and thus accessing them results in undefined behavior .此外,通过返回局部变量的地址,它们会在函数返回时超出范围,因此访问它们会导致未定义行为

By dynamically allocating each node, you're only limited by your available memory.通过动态分配每个节点,您只会受到可用内存的限制。

Here's an example using dynamic memory allocation:这是使用动态内存分配的示例:

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

This is psedo-code that doesn't go into the details of reading the relevant data, but you can see how you can create a list of arbitrary size that exists for the lifetime of the program.这是不涉及读取相关数据的细节的伪代码,但您可以看到如何创建一个在程序的生命周期内存在的任意大小的列表。

If these variables are local , defined inside a function's scope (ie stored on the stack), you shouldn't do this, because accessing them after leaving their scope will result in undefined behavior (their contents will likely be overwritten as you call other functions).如果这些变量是local ,在函数的作用域内定义(即存储在堆栈中),则不应这样做,因为在离开作用域后访问它们将导致未定义的行为(它们的内容可能会在您调用其他函数时被覆盖) )。 In fact, any time you return a pointer to a local, stack based variable from your function, you are doing the wrong thing.事实上,任何时候当你从你的函数返回一个指向局部、基于堆栈的变量的指针时,你都在做错事。 Given the nature of C, this is problematic since nothing will warn you you are doing something wrong, and it will only fail later when you try to access this area again.鉴于 C 的性质,这是有问题的,因为没有任何东西会警告您做错了什么,并且只会在您稍后再次尝试访问该区域时失败。

On the other hand, if they are declared as global variables (outside any other function), then you are simply limited by the number of variables declared that way.另一方面,如果它们被声明为全局变量(在任何其他函数之外),那么您只会受到以这种方式声明的变量数量的限制。

You can potentially declare many variables, but keeping track of which one is "free" for use will be painful.您可以潜在地声明许多变量,但跟踪哪个是“免费”使用的将是痛苦的。 Sure, you can even go a step further and say you will have a global preallocated array of nodes to prevent using malloc , but as you are doing all this you are only getting closer to writing your own version of malloc , instead of sticking to the existing, dynamic one.当然,您甚至可以更进一步说您将拥有一个全局预分配的节点数组来防止使用malloc ,但是当您这样做时,您只会越来越接近编写自己的malloc版本,而不是坚持使用现有的,动态的。

Additionally, all preallocated space is wasted if you don't use it, and you have no way of dynamically growing your list in runtime (hence the name dynamic allocation ).此外,如果您不使用所有预先分配的空间,那么所有预先分配的空间都将被浪费,并且您无法在运行时动态增长您的列表(因此名称为dynamic allocation )。

Here is some good reasons to use dynamic memory 是使用动态内存的一些很好的理由

  1. When you declare node struct Node N1;当你声明 node struct Node N1; this node will store on stack memory.该节点将存储在堆栈内存中。 After scope of the node that will get destroy auto.But in case of dynamic you have handle to free the memory when you done.在将自动销毁的节点范围之后。但是在动态的情况下,您可以在完成后释放内存。

  2. When you have some memory limitation.当您有一些内存限制时。

  3. When you don't know the size of array then dynamic memory allocation will help you.当您不知道数组的大小时,动态内存分配将对您有所帮助。

One issue could be that you cannot use another function to add a new node to your list.一个问题可能是您无法使用其他函数将新节点添加到您的列表中。

Remember that automatic variables - like the ones created by struct Node node100;记住自动变量——比如由struct Node node100;创建的struct Node node100; - have scope only inside the function in which they are defined. - 仅在定义它们的函数内具有作用域。 So when you do something like this:所以当你做这样的事情时:

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

Now you think that you have added a new node to the end of the list.现在您认为您已经在列表的末尾添加了一个新节点。 But, unfortunately, its lifetime ends as soon as the add_node function returns.但是,不幸的是,一旦add_node函数返回,它的生命周期就会结束。 And when you try to access that last node in your main function your program crashes.当您尝试访问main函数中的最后一个节点时,您的程序就会崩溃。

So, to avoid this situation you will have put all your code in one single function - so that the lifetime of those nodes do not end.因此,为了避免这种情况,您会将所有代码放在一个函数中——这样这些节点的生命周期就不会结束。

Having all your code in ONE function is bad practice and will lead to many difficulties.将所有代码放在一个函数中是不好的做法,会导致许多困难。

This was one situation that asks for a dynamic memory allocation, because, a node allocated with malloc will be in scope untill it is freed using free , and you can put code that do different things in different functions, which is a good practice.这是一种要求动态内存分配的情况,因为使用malloc分配的节点将在作用域内,直到使用free释放它为止,并且您可以将执行不同操作的代码放在不同的函数中,这是一个很好的做法。

You don't have to use dynamic memory to create a linked list, although you definitely don't want to create separate variables for each node.没有使用动态内存中创建一个链表,但你肯定希望为每个节点创建单独的变量。 If you want to store up to N items, then you'd need to declare N distinct variables, which becomes a real pain as N gets large.如果您想存储最多 N 个项目,那么您需要声明 N 个不同的变量,当 N 变大时,这变得非常痛苦。 The whole idea behind using a linked list is that it can grow or shrink as necessary;使用链表背后的整个想法是它可以根据需要增长或缩小。 it's a dynamic data structure, so even if you don't use malloc and free , you're going to wind up doing something very similar.它是一个动态数据结构,因此即使您不使用mallocfree ,您最终也会做一些非常相似的事情。

For example, you can create an array of nodes at file scope like so:例如,您可以在文件范围内创建一个节点数组,如下所示:

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 */

You the initialize the array so each element points to the next, with the last element pointing to NULL :您初始化数组,以便每个元素指向下一个元素,最后一个元素指向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;
}

To allocate a node for your list, we grab the node avail points to and update avail to point to the next available node (if avail is NULL , then there are no more available nodes).要分配一个节点列表,我们抢节点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;
}

When you're done with a node, add it back to the head of the available list:完成节点后,将其添加回可用列表的头部:

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

We're not using dynamic memory in the sense that we aren't calling mallic or free ;我们没有使用动态内存,因为我们没有调用mallicfree however, we've pretty much recapitulated dynamic memory functionality, with the additional limitation that our "heap" has a fixed upper size.然而,我们几乎重述了动态内存功能,还有一个额外的限制,即我们的“堆”有一个固定的上限大小。

Note that some embedded systems don't have a heap as such, so you'd have to do something like this to implement a list on such systems.请注意,某些嵌入式系统本身没有堆,因此您必须执行类似操作才能在此类系统上实现列表。

You can write a singly linked list with out malloc , but make sure the implementation is done in main.你可以写一个没有 malloc 的单链表,但要确保实现是在 main 中完成的。 but what about writing program for traversing , finding least number ,etc .但是如何编写用于遍历、查找最小数等的程序呢? these struct node variables will go out of scope .这些结构节点变量将超出范围。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM