我想在c中实现一个简单的链表,但是我只能在本地访问list元素,在list_append函数中,而不是在main函数中。
#include <stdio.h>
#include <stdlib.h>
typedef struct list_element {
struct list_element *next;
int val;
} list_element_t;
typedef struct list {
struct list_element *first;
} list_t;
int list_append(list_t *list, int value) {
if (list->first == NULL) { // if case 1
// This scope is accessed after each list_append-call. I'm expecting it to be accessed only once.
list = (list_t *) malloc(sizeof(list_t));
list_element_t *new_ele = (list_element_t *) malloc(sizeof(list_element_t));
new_ele->next = NULL;
new_ele->val = value;
list->first = new_ele;
printf("list firstn");
printf("%dn", list->first->val);
return value;
} else {
printf("Do nothing.n");
}
return -1;
}
int main (int argc, char* argv[]) {
list_t list;
list.first = NULL;
printf("insert 47: %dn", list_append(&list, 47));
printf("insert 11: %dn", list_append(&list, 11));
printf("%dn", list.first->val); // This will cause a seg fault.
}
我期望if case 1只在第一次list_append调用中被访问,但它也在第二次调用(以及任何进一步的调用)中被访问。
我将从稍微不同的角度来解决这个问题。让我们逐一查看这些行:
int list_append(list_t *list, int value) {
到目前为止,我们知道list
是一个指针。它可能指向某个东西,也可能是NULL
。很有可能,它至少有时指向某些东西,否则将它传递给函数就没有多大意义了!对象是一个列表。
if (list->first == NULL)
现在可以说list
肯定指向某物,否则list->first
就没有多大意义了。只能对指向对象的指针使用->
。
list = malloc(sizeof(list_t));
这一行使list
忘记了它指向的是什么,而使它指向一个全新的东西(这里是malloc
刚刚返回的list_t
对象)。
当一个指针可以指向一个新的对象时,这是很棒的。但是旧的物体呢?它去了哪里?还有别的指针指向它吗?如果是这样,我们将如何处理这两个列表呢?如果没有,则存在内存泄漏。也许不再需要它了?如果是,应该在指向它的最后一个指针停止之前释放。在对象内部的指针所指向的对象也应该以同样的方式考虑。那些对象还需要吗?还有其他指针指向它们吗?等等。
这些问题真的很难回答。你不应该编写会引发难题的代码。
与正确的代码比较:
int list_append(list_t *list, int value) {
if (list->first == NULL) {
list->first = malloc(sizeof(list_element_t));
应该担心list->first
指向的对象吗?不,没有这样的对象。这个指针是NULL
!因此,如果您已经检查了NULL
指针,则可以自由地对该指针进行赋值。
另一种不需要担心旧对象的情况是在局部声明指针时。
list_element_t* element = other_element;
这里也没有element
在初始化之前指向的旧对象。
每次给指针赋值时,你都应该停下来问自己这些问题。在赋值之前,这个指针是否指向一个对象?如果是这样,这个物体去了哪里?其他指针会照顾它吗,还是它永远丢失了?
在某些情况下,您确实需要回答这些问题,例如,在遍历链表时:
while (current != NULL) {
// do something
current = current->next;
这里current
明确指向一个对象。这个对象会丢失吗?有没有别的东西指向那个物体?是的!它是列表中前一个节点的指针(或者列表本身,如果current
是一个头)。这个包含所有节点的列表不会移动到任何地方。所以这个对象不会丢失,所以赋值current
是安全的。
当然,如果你指定一个函数参数,你应该知道新价值函数里面只看到。但这是一个完全不同的故事。
你的list_append
函数并不像你想象的那样。
int list_append(list_t *list, int value) { if (list->first == NULL) { // if case 1 // This scope is accessed after each list_append-call. I'm expecting it to be accessed only once. list = (list_t *) malloc(sizeof(list_t)); list_element_t *new_ele = (list_element_t *) malloc(sizeof(list_element_t)); new_ele->next = NULL; new_ele->val = value; list->first = new_ele; printf("list firstn"); printf("%dn", list->first->val); return value; }
list
是函数的本地参数。您已经修改了它,但是您没有修改它指向的列表。当函数退出时,该列表保持不变。
你可能想这样做:
int list_append(list_t *list, int value) {
if (list->first == NULL) {
list->first = malloc(sizeof(list_element_t));
...
list->first
相当于(*list).first
,list
指针的解引用更明显。