C 为什么在使用函数时不能正确添加到链表中?

C 为什么在使用函数时不能正确添加到链表中?,c,linked-list,C,Linked List,我正在尝试创建一个链接列表。当我在创建对象的同一个函数中添加到列表中时,它会起作用 定义: typedef struct student { int num; char* name; } student; typedef struct container { student* data; struct container* next; } container ; 我使用的所有对象都是这样初始化的: student stu1; stu1.num = 6; stu

我正在尝试创建一个链接列表。当我在创建对象的同一个函数中添加到列表中时,它会起作用

定义:

typedef struct student {
    int num;
    char* name;
} student;

typedef struct container {
    student* data;
    struct container* next;
} container ;
我使用的所有对象都是这样初始化的:

student stu1;
stu1.num = 6;
stu1.name = "grefagf";

front = createContainer(&stu1);
back = front;

student stu2;
stu2.num = 3;
stu2.name = "dsghjyreawre";

student stu3;
stu3.num = 4;
stu3.name = "dsghhjrant";

student stu4;
stu4.num = 213;
stu4.name = "fdsafgrw";
container* tmp;

tmp = createContainer(&stu2);
back->next = tmp;
back = tmp;

tmp = createContainer(&stu3);
back->next = tmp;
back = tmp;

tmp = createContainer(&stu4);
back->next = tmp;
back = tmp;
当我将这些元素添加到main函数的列表中时,如下所示:

student stu1;
stu1.num = 6;
stu1.name = "grefagf";

front = createContainer(&stu1);
back = front;

student stu2;
stu2.num = 3;
stu2.name = "dsghjyreawre";

student stu3;
stu3.num = 4;
stu3.name = "dsghhjrant";

student stu4;
stu4.num = 213;
stu4.name = "fdsafgrw";
container* tmp;

tmp = createContainer(&stu2);
back->next = tmp;
back = tmp;

tmp = createContainer(&stu3);
back->next = tmp;
back = tmp;

tmp = createContainer(&stu4);
back->next = tmp;
back = tmp;
它工作正常,输出如下:

1:  6       grefagf
2:  3       dsghjyreawre
3:  4       dsghhjrant
4:  213     fdsafgrw
当我用我做的另一个函数打印它时

但如果我创建一个名为add and pass stu2,stu3…,如下所示的函数:

int add(student to_add) {

    container* tmp;
    tmp = createContainer(&to_add);
    printf("added: (%d, %s)\n", tmp->data->num, tmp->data->name);

    back->next = tmp;
    back = tmp;

    return 1;
}
然后在主功能中执行此操作:

add(stu2);
add(stu3);
add(stu4);
它的输出是:

1:  6       grefagf
2:  41096808        fdsafgrw
3:  41096808        fdsafgrw
4:  41096808        fdsafgrw
以下是您需要的来源:

非功能示例:

功能示例:


add函数还应该使用它将学生添加到的链接列表或容器

因此,您不应该在其中创建临时容器,而是使用传入容器并添加到传入容器的后面

编辑:刚刚意识到你的背部和前部是全球性的。这可能不是一个好主意,因为这样,整个程序只能有一个链接列表

现在重读

编辑2:啊,这是一个很好的例子。您正在传入student,它生成student的副本,然后在add函数中获取其地址,这是堆栈上变量的地址。堆栈被弹出,因此堆栈上变量的地址没有意义。相反,add应该包含一个指向学生的指针,您应该传入stu2、stu3等地址


编辑3:Ahh太晚了:

添加函数还应该使用它正在将学生添加到的链接列表或容器

因此,您不应该在其中创建临时容器,而是使用传入容器并添加到传入容器的后面

编辑:刚刚意识到你的背部和前部是全球性的。这可能不是一个好主意,因为这样,整个程序只能有一个链接列表

现在重读

编辑2:啊,这是一个很好的例子。您正在传入student,它生成student的副本,然后在add函数中获取其地址,这是堆栈上变量的地址。堆栈被弹出,因此堆栈上变量的地址没有意义。相反,add应该包含一个指向学生的指针,您应该传入stu2、stu3等地址


编辑3:啊,太晚了:

好的,我试过了,它正在工作。仅将“按地址获取”学生结构添加到add函数 如下

add(&stu2);
add(&stu3);
add(&stu4);
并将add的功能更改如下

add(&stu2);
add(&stu3);
add(&stu4);
int add(student *to_add) {

    container* tmp;
    tmp = createContainer(to_add);
    printf("added: (%d, %s)\n", tmp->data->num, tmp->data->name);

    back->next = tmp;
    back = tmp;

    return 1;
}

好的,我试过了,它正在工作。仅将“按地址获取”学生结构添加到add函数 如下

add(&stu2);
add(&stu3);
add(&stu4);
并将add的功能更改如下

add(&stu2);
add(&stu3);
add(&stu4);
int add(student *to_add) {

    container* tmp;
    tmp = createContainer(to_add);
    printf("added: (%d, %s)\n", tmp->data->num, tmp->data->name);

    back->next = tmp;
    back = tmp;

    return 1;
}

下面是一个带有add函数的代码的基本版本。它以60行的速度运行,而您的原始版本为83行

#include <stdio.h>
#include <stdlib.h>

typedef struct student
{
    int num;
    char *name;
} student;

typedef struct container
{
    student *data;
    struct container *next;
} container;

static container *back;
static container *front;

static container *createContainer(student *data)
{
    container *tmp = malloc(sizeof(container));
    tmp->data = data;
    tmp->next = NULL;
    return tmp;
}

static void add(student to_add)
{
    container *tmp = createContainer(&to_add);
    printf("added: (%d, %s)\n", tmp->data->num, tmp->data->name);
    back->next = tmp;
    back = tmp;
}

static void printList(container *item)
{
    for (int i = 0; item != NULL; item = item->next)
        printf("%d:\t%d\t\t%s\n", ++i, item->data->num, item->data->name);
}

int main(void)
{
    student stu1 = { 6, "grefagf" };
    student stu2 = { 3, "dsghjyreawre" };
    student stu3 = { 4, "dsghhjrant" };
    student stu4 = { 213, "fdsafgrw" };

    front = createContainer(&stu1);
    back = front;
    add(stu2);
    add(stu3);
    add(stu4);

    printf("front\n");
    printList(front);
    printf("\nback\n");
    printList(back);

    return EXIT_SUCCESS;
}
问题是,您正在将局部变量的地址传递给createContainer函数,但该变量超出范围,因此您的容器指向垃圾。由于这是在调用未定义的行为,因此在您的计算机上的结果可能不同,并且两者都是正确的。撞车也是可能的——这是UB的魅力之一

你需要用两种方法中的一种来修改它。createContainer复制所传递的内容,或者安排通过add to createContainer传递指向main中变量的指针。这段代码完成了第二步,但从长远来看,它可能不是更好的解决方案。然而,对于复制已传递内容的通用解决方案,要处理的内存管理要多得多

#include <stdio.h>
#include <stdlib.h>

typedef struct student
{
    int num;
    char *name;
} student;

typedef struct container
{
    student *data;
    struct container *next;
} container;

static container *back;
static container *front;

static container *createContainer(student *data)
{
    container *tmp = malloc(sizeof(container));
    tmp->data = data;
    tmp->next = NULL;
    return tmp;
}

static void add(student *to_add)
{
    container *tmp = createContainer(to_add);
    printf("added: (%d, %s)\n", tmp->data->num, tmp->data->name);
    back->next = tmp;
    back = tmp;
}

static void printList(container *item)
{
    for (int i = 0; item != NULL; item = item->next)
        printf("%d:\t%d\t\t%s\n", ++i, item->data->num, item->data->name);
}

int main(void)
{
    student stu1 = { 6, "grefagf" };
    student stu2 = { 3, "dsghjyreawre" };
    student stu3 = { 4, "dsghhjrant" };
    student stu4 = { 213, "fdsafgrw" };

    front = createContainer(&stu1);
    back = front;
    add(&stu2);
    add(&stu3);
    add(&stu4);

    printf("front\n");
    printList(front);
    printf("\nback\n");
    printList(back);

    return EXIT_SUCCESS;
}

此代码泄漏内存,因为它不尝试清理列表。目前还可以。请注意,您最终需要进行清理。

这是一个带有add函数的代码的最低版本。它以60行的速度运行,而您的原始版本为83行

#include <stdio.h>
#include <stdlib.h>

typedef struct student
{
    int num;
    char *name;
} student;

typedef struct container
{
    student *data;
    struct container *next;
} container;

static container *back;
static container *front;

static container *createContainer(student *data)
{
    container *tmp = malloc(sizeof(container));
    tmp->data = data;
    tmp->next = NULL;
    return tmp;
}

static void add(student to_add)
{
    container *tmp = createContainer(&to_add);
    printf("added: (%d, %s)\n", tmp->data->num, tmp->data->name);
    back->next = tmp;
    back = tmp;
}

static void printList(container *item)
{
    for (int i = 0; item != NULL; item = item->next)
        printf("%d:\t%d\t\t%s\n", ++i, item->data->num, item->data->name);
}

int main(void)
{
    student stu1 = { 6, "grefagf" };
    student stu2 = { 3, "dsghjyreawre" };
    student stu3 = { 4, "dsghhjrant" };
    student stu4 = { 213, "fdsafgrw" };

    front = createContainer(&stu1);
    back = front;
    add(stu2);
    add(stu3);
    add(stu4);

    printf("front\n");
    printList(front);
    printf("\nback\n");
    printList(back);

    return EXIT_SUCCESS;
}
问题是,您正在将局部变量的地址传递给createContainer函数,但该变量超出范围,因此您的容器指向垃圾。由于这是在调用未定义的行为,因此在您的计算机上的结果可能不同,并且两者都是正确的。撞车也是可能的——这是UB的魅力之一

你需要用两种方法中的一种来修改它。createContainer复制所传递的内容,或者安排通过add to createContainer传递指向main中变量的指针。这段代码完成了第二步,但从长远来看,它可能不是更好的解决方案。然而,对于复制已传递内容的通用解决方案,要处理的内存管理要多得多

#include <stdio.h>
#include <stdlib.h>

typedef struct student
{
    int num;
    char *name;
} student;

typedef struct container
{
    student *data;
    struct container *next;
} container;

static container *back;
static container *front;

static container *createContainer(student *data)
{
    container *tmp = malloc(sizeof(container));
    tmp->data = data;
    tmp->next = NULL;
    return tmp;
}

static void add(student *to_add)
{
    container *tmp = createContainer(to_add);
    printf("added: (%d, %s)\n", tmp->data->num, tmp->data->name);
    back->next = tmp;
    back = tmp;
}

static void printList(container *item)
{
    for (int i = 0; item != NULL; item = item->next)
        printf("%d:\t%d\t\t%s\n", ++i, item->data->num, item->data->name);
}

int main(void)
{
    student stu1 = { 6, "grefagf" };
    student stu2 = { 3, "dsghjyreawre" };
    student stu3 = { 4, "dsghhjrant" };
    student stu4 = { 213, "fdsafgrw" };

    front = createContainer(&stu1);
    back = front;
    add(&stu2);
    add(&stu3);
    add(&stu4);

    printf("front\n");
    printList(front);
    printf("\nback\n");
    printList(back);

    return EXIT_SUCCESS;
}
此代码泄漏内存,因为它不尝试清理列表。目前还可以。只需注意,你最终需要清理一下。

,但要在问题本身中做出正确的回答。看起来您正在按值传递一个要添加的结构,但您没有包含student的定义。@I

ljaEveriläI想包括一个带有函数和不带函数的示例,我想链接到pastebin会比在这里编写更容易复制/粘贴。我添加了函数定义。请重新思考;我们不需要离开现场去查看您询问的代码。阅读有关创建MCVE的内容。最小是重要的;这是完整的。注意代码stu1;stu1.num=6;stu1.name=grefagf;不是stu1的初始值设定项;它给它赋值。它将用student stu1={6,grefagf}初始化;或学生stu1={.num=6,.name=grefagf};-其中第二个使用指定的初始值设定项。IOW:initializer是C中的一个技术术语,适用于“定义时指定”。为什么不提供stu2的地址来添加函数,如add&stu2,并将函数修改为int addstudent*to_addit似乎是关于强制转换的,因为num变量似乎接受了地址值现在我将尝试您的代码并返回给您,但是在问题本身中产生一个适当的答案。看起来您正在按值传递一个要添加的结构,但您没有包含student的定义。@IljaEverilä我想同时包含一个带函数和不带函数的示例,我想链接到pastebin会比在这里编写它更容易复制/粘贴。我添加了函数定义。请重新思考;我们不需要离开现场去查看您询问的代码。阅读有关创建MCVE的内容。最小是重要的;这是完整的。注意代码stu1;stu1.num=6;stu1.name=grefagf;不是stu1的初始值设定项;它给它赋值。它将用student stu1={6,grefagf}初始化;或学生stu1={.num=6,.name=grefagf};-其中第二个使用指定的初始值设定项。IOW:initializer是C中的一个技术术语,适用于“定义时指定”。为什么不给出stu2的地址来添加函数,如add&stu2,并将函数修改为int addstudent*to_addit似乎是关于强制转换的,因为num变量似乎现在接受了地址值。我将尝试您的代码并返回给您,您的意思是什么“消费”?传入的变量是我要添加的对象,它创建了一个容器来存储它,然后将该容器添加到列表的后面。抱歉,已编辑。我不知道你的前后指针是全局变量。无关消耗是函数作为输入的另一种说法,我使用它来避免与i/O混淆,即来自标准输入的输入。啊,好的。我确实前后传递给函数,不是全局的,但它不起作用,所以我试着使它们全局化,因为我的教授总是这样做的。我只需要这个程序的一个链表,所以它不应该是一个问题。用reply编辑你说的“消费”是什么意思?传入的变量是我要添加的对象,它创建了一个容器来存储它,然后将该容器添加到列表的后面。抱歉,已编辑。我不知道你的前后指针是全局变量。无关消耗是函数作为输入的另一种说法,我使用它来避免与i/O混淆,即来自标准输入的输入。啊,好的。我确实前后传递给函数,不是全局的,但它不起作用,所以我试着使它们全局化,因为我的教授总是这样做的。无论如何,我只需要这个程序的一个链接列表,所以它不应该是一个问题。使用replyHow编辑我将如何“清理列表”?您将从前到后逐步执行,释放容器,然后再次将“前”和“后”设置为“空”。静态void freeListcontainer*item{while item!=NULL{container*next=item->next;freeitem;item=next;}front=back=NULL;}噢,就像释放内存一样?我是否只需要在使用完列表后再这样做?什么时候会发生内存泄漏?还有,什么是内存泄漏?抱歉问了这么多问题。是的,当你完成列表时,你可以释放它。如果代码继续运行而不释放现在未使用的列表,则会发生内存泄漏。在这个程序中,列表在程序退出之前一直在使用,释放它并不重要——这就是为什么它暂时还可以。但是,如果您使用的是从main调用的函数,当您无法再访问未发布的列表时,该列表将成为泄漏的内存。同样,对于由全局变量维护的单个列表,这不是一个紧迫的问题。如果您将代码泛化为一个子例程库,这将成为一个问题。当分配的内存无法再访问但尚未释放时,将发生内存泄漏。例如,如果您有一个函数,它分配一个字符串,将一些数据格式化为该字符串,将其打印到屏幕和日志文件,然后返回-嗯,几乎可以肯定的是,该内存不再可访问,并且会泄漏。它将不再使用,但仍分配给程序。对于长时间运行的程序,请参见备注
泄漏是个问题。对于短跑学生练习,它们不是主要问题。我如何“清理列表”?您可以从前面到后面依次进行,释放容器,然后再次将前面和后面的指向设置为NULL。静态void freeListcontainer*item{while item!=NULL{container*next=item->next;freeitem;item=next;}front=back=NULL;}噢,就像释放内存一样?我是否只需要在使用完列表后再这样做?什么时候会发生内存泄漏?还有,什么是内存泄漏?抱歉问了这么多问题。是的,当你完成列表时,你可以释放它。如果代码继续运行而不释放现在未使用的列表,则会发生内存泄漏。在这个程序中,列表在程序退出之前一直在使用,释放它并不重要——这就是为什么它暂时还可以。但是,如果您使用的是从main调用的函数,当您无法再访问未发布的列表时,该列表将成为泄漏的内存。同样,对于由全局变量维护的单个列表,这不是一个紧迫的问题。如果您将代码泛化为一个子例程库,这将成为一个问题。当分配的内存无法再访问但尚未释放时,将发生内存泄漏。例如,如果您有一个函数,它分配一个字符串,将一些数据格式化为该字符串,将其打印到屏幕和日志文件,然后返回-嗯,几乎可以肯定的是,该内存不再可访问,并且会泄漏。它将不再使用,但仍分配给程序。对于长时间运行的程序,内存泄漏是一个问题。对于短跑学生练习,它们不是一个大问题。