我可以对某些节点使用分配,而其他节点不在 C 的链表中使用分配吗?

Can I use allocation for some nodes and others don't in a linked list in C

提问人:R.Duarte 提问时间:10/15/2023 最后编辑:R.Duarte 更新时间:10/16/2023 访问量:80

问:

我不明白为什么有必要使用指针然后使用动态内存来分配结构。有什么能阻止我这样做吗:

typedef struct node {
    char name[100];
    int age;
    struct node *next;
  
} node_t ;

int main(){
    
    node_t *head = NULL;
    node_t person2 = {"Alex", 15, NULL};
    head = person2.name;
    return 0;
}

取而代之的是:

typedef struct node {
    char name[100];
    int age;
    struct node *next;
  
} node_t ;

int main(){

    node_t *head = NULL;

    node_t *person1;
    person1 = (node_t*)malloc(sizeof(node_t));
    person1->next = NULL;
    head = person1;
    return 0;
}

我知道我们分配内存的地方是不同的,但实际上,这两种方式的工作方式不同吗?我提出问题的原因是因为我只在网上的每个示例中看到指向 malloc 方式的指针。

我可以同时使用这两种方式吗?

我要么将整个节点指向下一个节点:

head = person1;

下一个节点可能指向下一个节点的名称,例如:

person1->next = person2.name;

如果我没有弄错,当我们指向动态内存地址时,我们指向第一个数据类型的字节,在这种情况下将是 name[0] 字节。

c 链接列表

评论

0赞 Craig Estey 10/15/2023
将成员添加到您的(例如)并适当设置。删除节点时,仅当设置了此项时才执行structint is_allocated;free
2赞 Oka 10/15/2023
它们在寿命方面有很大差异。
1赞 Chris Dodd 10/15/2023
您的第一个示例给出了有关不兼容指针的警告 -- 您应该改用head = &person2;
0赞 Craig Estey 10/15/2023
但是,如果是我,我会一直这样做,因为总的来说,这更简单malloc
0赞 dbush 10/15/2023
尝试根据任意大小的文件的内容创建一个链表,然后看看哪种方式更有意义。

答:

2赞 Chris Dodd 10/15/2023 #1

主要问题是知道如何以及何时释放链表使用的内存 - 分配的内存必须释放,而在堆栈上分配的内存不能释放 amd 将在包含帧退出时自动释放。更糟糕的是,在帧退出后传递或尝试使用它会导致未定义的行为,并且这两种情况一开始似乎都有效,后来会导致问题。mallocfreefreefree

一种可能性是用一个位标志来标记每个节点,该标志是为使用 malloc 分配的节点设置的,并为其他节点清除。这样,您至少知道是否应该将给定节点传递给 。这仍然无助于释放来自堆栈上分配的节点的悬空指针的可能性,而这些指针仍在列表中。onHeapfree

另一种可能性是“正常”地在堆上分配节点,但在极少数情况下,您希望分配和释放同一范围内的节点,您可以在堆栈上执行此操作。因此,您可能有如下所示的代码:

void func(struct node *list) {  // list (probably) on the heap
    struct node local;  // a new node we'll prepend
    // init fields of local with some data
    local.next = list;
    func2(&local);   // some other function
}

在这里,调用它的代码“拥有”正在传入的列表,并且可以以任何方式分配它(在堆上使用 malloc 或其他方式)并以相同的方式清理它。 然后只拥有它预先传递给 func2 的单个节点(它在列表中没有任何内容,因此不会释放任何内容),并在返回时自动释放。在这里使用堆栈可以看作是分配的局部优化。funcfunclocal

评论

0赞 R.Duarte 10/15/2023
因此,如果我可以互换使用它,释放 malloc 的唯一方法是在程序结束时?
1赞 Chris Dodd 10/15/2023
您的呼叫需要与您的呼叫匹配。这就是混合内存分配方法(堆栈和堆)如此困难的原因。freemalloc
0赞 Vlad from Moscow 10/15/2023 #2

在第一个代码片段中,我认为您的意思是

head = &person2;

而不是

head = person2.name;

当然,您可以使用局部变量作为列表的节点。但是在这种情况下,如果列表需要存储大量节点,那么您将需要声明不同的大量局部变量,这些变量将表示效率低下的节点,并使您的程序非常庞大,无法读取。

1赞 Chris 10/15/2023 #3

寿命是问题所在。如果你的节点是自动声明的,你可以回避这个问题,但是一旦节点开始源自其他函数,就会出现这个问题。main

假设我们创建了一个函数,该函数接受一个 int 值和一个指向下一个节点的指针,并返回一个新节点。create_node

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

struct node *create_node(int value, struct node *next) {
    struct node new_node = {.value=value, .next=next};
    return &new_node;
}

这将返回指向局部变量的指针。随之而来的是未定义的行为。

如果我们动态分配内存,则节点的寿命将超过函数的范围。

struct node *create_node(int value, struct node *next) {
    struct node *new_node = malloc(sizeof(struct node));
    if (!new_node) return NULL;
    
    new_node->value = value;
    new_node->next = next;

    return new_node;
}

我们现在可以安全地写出这样的东西:

struct node *prepend_value(struct node *list, int value) {
    struct node *new_node = create_node(value, list);

    return new_node;
}