【问题标题】:Linked List doesn't take in the next element (C language)链表不包含下一个元素(C 语言)
【发布时间】:2019-09-04 12:13:37
【问题描述】:

我是 C 语言的新手,我正在研究一个链接列表示例。

initialize() 函数似乎工作正常,但在第一次调用 insert() 后程序崩溃。

我认为问题出在将新元素添加到链表时,好像它溢出或者它不接受列表的新第一个元素。

我处理了一个类似的示例,其中包含一个只有 int 元素的链表,它运行良好。

代码如下:

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

typedef struct Element Element;

struct Element
{
    char f_name[10];
    char l_name[10];
    float score;
    Element* next;
};

typedef struct Liste Liste;

struct Liste
{
    Element* first;
};

Element* read()
{
    Element* element = (Element*) malloc(sizeof(element));
    if(element==NULL)
        exit(EXIT_FAILURE);
    printf("Please provide first name : \n");
    scanf(" %s", element->f_name);
    printf("Please provide last name : \n");
    scanf(" %s", element->l_name);
    printf("Please provide score : \n");
    scanf("%f", &(element->score));
    return element;
}

Liste* initialize()
{
    Liste* liste = (Liste*) malloc(sizeof(liste));
    Element* element = (Element*) malloc(sizeof(element));
    if(liste==NULL || element==NULL)
        exit(EXIT_FAILURE);
    element = read();
    element->next = NULL;
    liste->first = element;
    return liste;
}

void insert(Liste* liste)
{
    Element* nouveau = (Element*) malloc(sizeof(nouveau));
    if(liste==NULL || nouveau==NULL)
        exit(EXIT_FAILURE);
    nouveau = read();
    nouveau->next = liste->first;
    liste->first = nouveau;
}

int main()
{
    Liste* maListe = (Liste*) malloc(sizeof(maListe));
    maListe = initialize();
    insert(maListe);
    insert(maListe);
    insert(maListe);
    return 0;
}

我在这件事上做错了什么?我应该如何解决它?

谢谢。

【问题讨论】:

  • 对于初学者,您使用Element* nouveau = (Element*) malloc(sizeof(nouveau)); 初始化和分配的指针会立即丢失,并使用nouveau = read();,因为您正在为nouveau 分配一个新地址,从而造成内存泄漏。 malloc 的返回不需要强制转换,没有必要。见:Do I cast the result of malloc?
  • malloc(sizeof(element))错误element 是一个指针。对malloc 的其他调用也是如此。
  • Google 的malloc 用法示例,与您的代码进行比较。实际上有数以百万计的人。
  • 使用例如sizeof *element 代替。

标签: c linked-list


【解决方案1】:

我认为你的问题是你写了sizeof(element),你需要有sizeof(Element)。你有两个不同的地方。

请注意,“元素”是指针类型的变量,因此它具有指针的大小(可能是 8 个字节),而“元素”是具有更大大小的结构类型。因此,当您只分配太小的 sizeof(element) 字节时。

通过valgrind运行你的程序很容易发现这种错误。

【讨论】:

  • 或者:sizeof(*element)
【解决方案2】:

虽然您已经为 SegFault 找到了答案,但您还可以在其他领域清理和重构代码,以便更高效地协同工作。由于您使用列表结构liste 来保存指向first 中列表开头的指针,因此您还可以添加另一个指针last 以指向列表中的最后一个节点,从而不必迭代到每次插入的最后一个节点。使用last(或tail)指针,您的新节点始终插入last-&gt;next。例如,您的 Liste 结构可能是:

typedef struct Liste Liste;

struct Liste {
    Element *first, *last;
};

您的列表函数每个应该做一件事,这意味着initialize() 应该只分配和初始化Liste 节点及其指针。 read() 应该分配和读取并返回一个指向已填充节点的有效指针,或者在失败时返回 NULL。 insert() 应该这样做,将Liste 列表地址m 和来自read() 的节点插入到列表中。把这些功能放在一起你可以做:

Element *read()
{
    Element *element = malloc (sizeof(*element));   /* allocate */
    if (element == NULL)                            /* validate */
        return NULL;
    element->next = NULL;                           /* initialize */

    printf ("\nPlease provide first name : ");
    if (scanf ("%9s", element->f_name) != 1)   /* validate EVERY input */
        goto badread;

    printf ("Please provide last name  : ");
    if (scanf ("%9s", element->l_name) != 1)
        goto badread;

    printf ("Please provide score      : ");
    if (scanf ("%f", &element->score) != 1)
        goto badread;

    return element;     /* return allocated and initialized element */

badread:;     /* just a simple goto label for handling read error */

    free (element);     /* free memory of node if error */

    return NULL;
}

注意:使用goto 将您发送到超出正常返回的标签,您可以在其中为填充期间失败的节点释放内存。)

/* initialize the list, don't worry about the elements */
Liste *initialize (void)
{
    Liste *liste = malloc(sizeof *liste);
    if (liste == NULL) {
        perror ("malloc-liste");    /* give some meaningful error */
        exit (EXIT_FAILURE);
    }
    liste->first = liste->last = NULL;

    return liste;
}

void insert (Liste *liste, Element *nouveau)
{
    if (liste == NULL || nouveau == NULL)
        exit (EXIT_FAILURE);

    if (!liste->first)                          /* inserting 1st node */
        liste->first = liste->last = nouveau;
    else {                                      /* inserting all others */
        liste->last->next = nouveau;
        liste->last = nouveau;
    }
}

注意:初始化和插入是直截了当的,你处理的唯一两个类是插入第一个节点还是所有其他节点。让它非常简单)

总而言之,您可以编写完整的测试代码,如下所示添加一个函数来迭代列表打印值,然后使用一个类似的函数来迭代列表释放节点,最后是列表::

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

#define MAXN 10     /* if you need a constant, #define one (or more) */

typedef struct Element Element;

struct Element {
    char f_name[MAXN];
    char l_name[MAXN];
    float score;
    Element* next;
};

typedef struct Liste Liste;

struct Liste {
    Element *first, *last;
};

Element *read()
{
    Element *element = malloc (sizeof(*element));   /* allocate */
    if (element == NULL)                            /* validate */
        return NULL;
    element->next = NULL;                           /* initialize */

    printf ("\nPlease provide first name : ");
    if (scanf ("%9s", element->f_name) != 1)   /* validate EVERY input */
        goto badread;

    printf ("Please provide last name  : ");
    if (scanf ("%9s", element->l_name) != 1)
        goto badread;

    printf ("Please provide score      : ");
    if (scanf ("%f", &element->score) != 1)
        goto badread;

    return element;     /* return allocated and initialized element */

badread:;     /* just a simple goto label for handling read error */

    free (element);     /* free memory of node if error */

    return NULL;
}

/* initialize the list, don't worry about the elements */
Liste *initialize (void)
{
    Liste *liste = malloc(sizeof *liste);
    if (liste == NULL) {
        perror ("malloc-liste");    /* give some meaningful error */
        exit (EXIT_FAILURE);
    }
    liste->first = liste->last = NULL;

    return liste;
}

void insert (Liste *liste, Element *nouveau)
{
    if (liste == NULL || nouveau == NULL)
        exit (EXIT_FAILURE);

    if (!liste->first)                          /* inserting 1st node */
        liste->first = liste->last = nouveau;
    else {                                      /* inserting all others */
        liste->last->next = nouveau;
        liste->last = nouveau;
    }
}

void prnlist (Liste *liste)
{
    Element *iter = liste->first;

    while (iter) {  /* just iterate list outputting values */
        printf ("%-10s %-10s  ->  %.2f\n", 
                iter->f_name, iter->l_name, iter->score);
        iter = iter->next;
    }
}

void freelist (Liste *liste)
{
    Element *iter = liste->first;

    while (iter) {
        Element *victim = iter;
        iter = iter->next;          /* iterate to next node BEFORE */
        free (victim);              /* you free victim */
    }
    free (liste);
}

int main (void) {

    Liste *maListe = initialize();  /* create/initialize list */
    Element *node;

    while ((node = read()))         /* allocate/read */
        insert (maListe, node);     /* insert */

    puts ("\n\nElements in list:\n");   /* output list values */
    prnlist (maListe);

    freelist (maListe);     /* don't forget to free what you allocate */

    return 0;
}

使用/输出示例

$ ./bin/ll_liste

Please provide first name : Donald
Please provide last name  : Duck
Please provide score      : 99.2

Please provide first name : Minnie
Please provide last name  : Mouse
Please provide score      : 99.7

Please provide first name : Pluto
Please provide last name  : Dog
Please provide score      : 83.5

Please provide first name :

Elements in list:

Donald     Duck        ->  99.20
Minnie     Mouse       ->  99.70
Pluto      Dog         ->  83.50

内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1)始终保留指向起始地址的指针内存块,因此 (2) 当不再需要它时可以释放

您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后,以确认您释放了已分配的所有内存。

对于 Linux,valgrind 是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。

$ valgrind ./bin/ll_liste
==10838== Memcheck, a memory error detector
==10838== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==10838== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==10838== Command: ./bin/ll_liste
==10838==

Please provide first name : Donald
Please provide last name  : Duck
Please provide score      : 99.2

Please provide first name : Minnie
Please provide last name  : Mouse
Please provide score      : 99.6

Please provide first name : Pluto
Please provide last name  : Dog
Please provide score      : 87.2

Please provide first name :

Elements in list:

Donald     Duck        ->  99.20
Minnie     Mouse       ->  99.60
Pluto      Dog         ->  87.20
==10838==
==10838== HEAP SUMMARY:
==10838==     in use at exit: 0 bytes in 0 blocks
==10838==   total heap usage: 5 allocs, 5 frees, 144 bytes allocated
==10838==
==10838== All heap blocks were freed -- no leaks are possible
==10838==
==10838== For counts of detected and suppressed errors, rerun with: -v
==10838== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认您已释放已分配的所有内存并且没有内存错误。

检查一下,如果您有任何问题,请告诉我。

【讨论】:

  • 我打算先使用单链表来做这个例子,然后我会继续使用双链表来做这个例子。您的教程简单、清晰、准时!非常感谢!
  • 很高兴为您提供帮助。有很多方法可以将所有部分组合在一起。您可以更改create 的完成方式(实际上对于简单的结构,您可以在insert 函数中进行创建)。对于可能还必须为其他成员变量分配的更复杂的结构,create_nodefree_node 函数更有意义。事实上,由于所有列表操作都是相当通用的,因此您通常最终需要重写的唯一事情就是 create 和 free 函数。其余的只是通用列表操作。祝你编码顺利。
猜你喜欢
  • 1970-01-01
  • 2020-08-23
  • 2011-01-08
  • 1970-01-01
  • 1970-01-01
  • 2018-08-24
  • 2014-05-02
  • 1970-01-01
  • 2015-10-08
相关资源
最近更新 更多