链表在第二次迭代期间打印符号而不是单词

Linked List prints symbols instead of words during second iteration

提问人:dirthogger 提问时间:9/28/2023 最后编辑:chqrliedirthogger 更新时间:9/28/2023 访问量:78

问:

我是 C 语言的初学者,所以我知道我的代码可能看起来像废话。我想做的就是用一个包含字符数组和频率的结构创建一个链表。它从测试文件中读取行,并简单地打印出文件。它正确读取文件,第一次遍历链表并正确打印出来。但是,在链表的第二次迭代中,它会打印出正确的行数和正确的整数,但单词被符号替换。我到处寻找,我只是需要一些帮助。这是我的代码。int

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

#define Word_MAX_LENGTH 255

struct WordFreq
{
    char word[Word_MAX_LENGTH];
    int frequency;
    struct WordFreq *next;
} WordFreq;

struct WordFreq *head = NULL;

void insert(struct WordFreq *newNode)
{
    if (head == NULL)
    {
        head = newNode;
        newNode->next = NULL;
        return;
    }

    struct WordFreq *current = head;

    while (current->next != NULL)
    {
        current = current->next;
    }

    current->next = newNode;
    newNode->next = NULL;
}

void main(int argc, char *argv[])
{
    if (argc != 2)
    {
        printf("Please run as %s [filename]\n", argv[0]);
        return;
    }

    FILE *f;
    f = fopen(argv[1], "r");

    if (f == NULL)
    {
        printf("File (%s) does not exist\n", argv[1]);
        return;
    }

    struct WordFreq *line = malloc(sizeof(struct WordFreq));

    if (line == NULL)
    {
        printf("Cannot do dynamic memory managment\n");
        return;
    }

    printf("File content in %s:\n", argv[1]);

    while (fscanf(f, "%s %d", line->word, &(line->frequency)) != EOF)
    {
        printf("%s %d\n", line->word, line->frequency);

        insert(line);

        line = malloc(sizeof(struct WordFreq));

        if (line == NULL)
        {
            printf("Cannot do dynamic memory management");
            return;
        }
    }
    fclose(f);

    // To keep head intact for sorting portion
    struct WordFreq *current = head;
    
    printf("\nContent of linked list:\n");

    while (current != NULL)
    {
        printf("%s %d\n", current->word, current->frequency);
        struct WordFreq *temp = current;
        current = current->next;

        free(temp);
    }
    
    printf("\n\n\n");
    current = head; 
    while (current != NULL)
    {
        printf("%s %d\n", current->word, current->frequency);
        current = current->next;
    }
    free(current);
    free(line);
}

非常感谢任何帮助。很遗憾我已经编程了一段时间,但我想学习 C,因为我想了解为什么程序会做他们所做的事情来帮助未来的努力。

c 数据结构 链接列表 malloc 动态内存分配

评论

3赞 Some programmer dude 9/28/2023
你有一个循环,其中所有节点都在你的列表中。然后,使用现在不存在的节点再次迭代列表。这会导致未定义的行为。在最后一个循环之后,您还将调用一个 null 指针,但这没关系,因为它被指定为不执行任何操作。freefree
2赞 Fe2O3 9/28/2023
OT: “// 保持头部完好无损以进行排序部分” 通过立即填充 BST(二叉搜索树)而不是简单的链表,您将避免痛苦的世界。当您填充树的节点时,您已经在对它们进行排序...
1赞 stark 9/28/2023
避免使用全局变量。将指针传递到 head 或从 insert 返回其值。

答:

1赞 chqrlie 9/28/2023 #1

在第一次迭代期间释放了链表,因此第二次迭代具有未定义的行为,因为该指针已成为无效指针,并且列表节点的内存内容已更改。从它们读取具有未定义的行为,任何事情都可能发生,包括程序因无效的内存访问而停止或您观察到的随机输出。head

您应该编写函数:一个用于打印列表内容,另一个用于释放列表。将这些操作组合在一起会造成混淆,并导致诸如此类的逻辑错误。

另请注意以下备注:

  • 的返回类型为 。修改语句,在错误和成功时返回,即:正常操作。mainintreturn10

  • printf("File (%s) does not exist\n", argv[1])可能不是正确的诊断:文件可能存在,但如果进程没有适当的访问权限,则文件将失败。您可以使用并输出相应的错误消息:fopenerrno

     #include <errno.h>
     #include <string.h>
    
     [...]
    
         if (f == NULL) {
             fprintf(stderr, "cannot open %s: %s\n", argv[1], strerror(errno));
             return 1;
         }
    
  • 而不是您应该测试执行两个转换是否成功:它应该返回 .更好的是:一次读取一行输入,并用于转换行内容,并使用包含违规输入行的显式消息报告转换错误。fscanf(f, "%s %d", line->word, &(line->frequency)) != EOFfscanf()2fgetssscanf()

  • head被释放两次:最后必须删除。free(current)

这是修改后的版本:

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

#define WORD_MAX_LENGTH 255

struct WordFreq {
    char word[WORD_MAX_LENGTH];
    int frequency;
    struct WordFreq *next;
} WordFreq;

struct WordFreq *head = NULL;

void insert(struct WordFreq *newNode) {
    if (head == NULL) {
        head = newNode;
    } else {
        struct WordFreq *current = head;
        while (current->next != NULL) {
            current = current->next;
        }
        current->next = newNode;
    }
    newNode->next = NULL;
}

void print_list(const struct WordFreq *node) {
    while (node != NULL) {
        printf("%s %d\n", node->word, node->frequency);
        node = node->next;
    }
}

void free_list(struct WordFreq *node) {
    while (node != NULL) {
        struct WordFreq *temp = node;
        node = node->next;
        free(temp);
    }
}

int main(int argc, char *argv[]) {
    char line[WORD_MAX_LENGTH + 20];

    if (argc != 2) {
        fprintf(stderr, "Please run as %s [filename]\n", argv[0]);
        return 1;
    }

    FILE *f = fopen(argv[1], "r");
    if (f == NULL) {
        fprintf(stderr, "Cannot open %s: %s\n", argv[1], strerror(errno));
        return 1;
    }

    printf("File content in %s:\n", argv[1]);

    while (fgets(line, sizeof line, f)) {
        struct WordFreq *node = malloc(sizeof(*node));
        if (node == NULL) {
            fprintf(stderr, "Cannot allocate node for %s\n", line);
            fclose(f);
            return 1;
        }
        if (sscanf(line, "%254s %d", node->word, &node->frequency) == 2) {
            printf("%s %d\n", node->word, node->frequency);
            insert(node);
        } else {
            fprintf(stderr, "invalid line: %s\n", line);
            free(node);
        }
    }
    fclose(f);

    printf("\nContent of linked list:\n");

    print_list(head);
    // ...
    free_list(head);
    return 0;
}

评论

0赞 dirthogger 9/28/2023
哦,好吧,我删除了 free(temp),现在它可以工作了。我以为通过创建一个指向链接列表的新指针,我保留了它,但实际上,当我释放 temp 时,我释放了实际节点,因为 temp 只是一个指向数据的指针。因此,无论我是否发出 100 个指针,如果它指向数据,则释放一个指针将覆盖数据。谢谢大家的帮助,一定会做出这些改变。
0赞 chqrlie 9/28/2023
@dirthogger:没错。掌握指针与数组和结构的概念是一项基本的 C 技能。欢迎使用 C 编程。
1赞 dirthogger 9/28/2023
谢谢你,在我为 Linux 内核开发或恶意软件分析做出贡献之前,肯定还有很长的路要走。
0赞 dirthogger 9/29/2023
我不明白的一件事是,如果我释放了内存空间,为什么最后一个 while 循环会打印出正确的行数和正确的数字?
0赞 chqrlie 9/29/2023
@dirthogger:不能保证任何事情,但一个可能的解释是指向的内存和链接没有改变,所以循环工作,但char数组已经作为竞技场内部簿记的一部分被改变,所以它在输出中显示为随机字符。行为是未定义的,任何事情都可能发生,您可能会在不同的机器上、使用不同的编译器,甚至在另一个时间观察到不同的行为。headnextwordfree