【问题标题】:Linked List Infinite Loop链表无限循环
【发布时间】:2015-10-21 04:40:30
【问题描述】:

尝试创建一个程序来计算与strcmp 进行的比较次数,同时将当前节点移动到头部。虽然算法有一些问题..它有时会“工作”,有时会给我一个无限循环。现在尝试了 lldb 几个小时,并且每 2 行代码就放置 printf 消息,但我不知道如何查明问题。我认为它在算法中的某个地方,但我看不出它有什么问题。

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

typedef struct Node2
{
    char* word;
    struct Node2 *next;
} Node2;

Node2* head = NULL;
Node2* now = NULL;
int uniqueWords = 0;
int totalMTFRComparisons = 0;


Node2* iNode(char* word)
{   
    Node2* ptr = malloc(sizeof(Node2));
    Node2* tmp = head;
    Node2* prev = head;

    while (tmp)
    {
        totalMTFRComparisons++;
        printf("Current word: %s", tmp->word);
        if (strcmp(tmp->word, word) == 0)
        {
            prev->next = tmp->next;
            tmp->next = head;
            return tmp;
        }
        prev = tmp;
        tmp = tmp->next;
        printf("tmp incremented.\n");
    }

    ptr->word = malloc(strlen(word) + 1);
    strcpy(ptr->word, word);
    ptr->next = NULL;
    if (head == NULL)
    {
        head = now = ptr;
        return ptr;
    }
    else
    {
        ptr->next = head;
        head = ptr;
    }
    return ptr;
}

char* getString()
{
    static char buffer[128];
    while (fgets(buffer, 128, stdin) != NULL)
    {
        iNode(buffer);
    }
    return buffer;
}

int main()
{
    getString();
    printf("Total words: %d, total MTFR comparisons: %d\n", uniqueWords, totalMTFRComparisons);

    Node2* ptr2 = head;
    Node2* tmp2 = head;
    while (ptr2 != NULL)
    {
        tmp2 = ptr2->next;
        free(ptr2->word);
        free(ptr2);
        ptr2 = tmp2;
    }
}

我的控制台刚刚收到垃圾邮件: tmp incremented. 但这并不总是发生 - 它只是有时会发生,所以我不知道是什么原因造成的。

输入和输出示例:http://pastebin.com/QfuCm7gt

【问题讨论】:

  • 如果您在循环中使用return tmp; 路径,则会泄漏内存。在你知道你需要它之​​前不要分配内存。但是,这不会给您带来无限循环。
  • 基本诊断:您应该在输入函数时打印出要搜索的单词。在循环中,您应该打印要比较的单词。如果找到匹配项,则应在返回之前打印该事实。如果你不这样做,你应该尽可能多地报告,分配内存(参见我之前的评论),并报告其他发生的事情。
  • 您的代码有两个 malloc 调用,使用后没有释放。会有巨大的内存泄漏。
  • @Krypton 担心不是朋友,内存在别处被释放!不过还是谢谢。
  • @Sleepless:其他地方?到目前为止,分配的内存地址似乎具有 local 范围,因为 ptr 是本地的..

标签: c algorithm linked-list


【解决方案1】:

您为字符串分配的内存太少:

ptr->word = malloc(strlen(word));
strcpy(ptr->word, word);

您需要分配strlen(word) + 1 字节以允许空终止符。

当你写超出分配的空间时,你会调用未定义的行为。对您来说不幸的是,这可能意味着它有时看起来工作正常 - 这是对未定义行为的有效响应,似乎按预期工作,除非它适合系统改变主意并表现异常。

考虑是否可以使用strdup() 函数。如果没有,请考虑是否应该编写和使用自己的版本。不要忘记检查内存分配是否成功。

(在您修复未定义的行为之前,如果还有其他问题,那么想知道还有什么问题真的没有意义。)


我写了这段代码来看看我是否可以模拟你的问题。我不能:

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

typedef struct Node
{
    char *word;
    struct Node *next;
} Node;

int totalMTFRComparisons = 0;

Node* head = NULL;
static Node* iNode(char* word)
{   
    Node* ptr = malloc(sizeof(Node));
    Node* tmp = head;
    Node* prev = head;

    while (tmp)
    {
        totalMTFRComparisons++;
        if (strcmp(tmp->word, word) == 0)
        {
            prev->next = tmp->next;
            tmp->next = head;
            //tmp->next = prev;

/* JL: Still a memory leak here. */
/* Either free(ptr) or don't allocate until after the loop */

            return tmp;
        }
        prev = tmp;
        tmp = tmp->next;
        printf("tmp incremented.\n");
    }
    ptr->word = malloc(strlen(word) + 1);
    strcpy(ptr->word, word);
    ptr->next = NULL;
    if (head == NULL)
    {
        head = ptr;
        return ptr;
    }
    else
    {
        ptr->next = head;
        head = ptr;
    }
    return ptr;
}

static void dump_list(Node *node)
{
    while (node != 0)
    {
        printf("Node word: [[%s]]\n", node->word);
        node = node->next;
    }
}

static void free_list(Node *node)
{
    printf("Freeing list\n");
    while (node != 0)
    {
        Node *next = node->next;
        printf("Freeing: [[%s]]\n", node->word);
        free(node->word);
        free(node);
        node = next;
    }
}

int main(void)
{
    char line[4096];

    while (fgets(line, sizeof(line), stdin) != 0)
    {
        printf("Line: [[%s]]\n", line);
        char *ptr = line;
        char *tok;
        while ((tok = strtok(ptr, "\n\t ")) != 0)
        {
            printf("Word: [[%s]]\n", tok);
            iNode(tok);
            ptr = NULL;
        }
        dump_list(head);
    }
    free_list(head);
    return 0;
}

这或多或少是一个 MCVE。转储和释放功能只是让我确保我可以看到列表中的内容并释放所有内存。

除了修复内存覆盖问题外,我只提供了Node 类型的定义,将static 放在函数前面(以避免我会收到的编译警告之一),并添加了两个支持函数和main();否则我没有更改您的代码。 (顺便说一句,第一次编译时唯一的抱怨是关于cur2,我注意到了但忘了删除——这非常好;很少有程序,即使是我编写的程序,也能如此干净地完成。)

运行时,我输入:

abc def ghi
mno pqr stuvwxyz humongously-and-tempestuously-but-neither-abstemiously-nor-facetiously-long word!

我得到了输出:

abc def ghi
Line: [[abc def ghi
]]
Word: [[abc]]
Word: [[def]]
tmp incremented.
Word: [[ghi]]
tmp incremented.
tmp incremented.
Node word: [[ghi]]
Node word: [[def]]
Node word: [[abc]]
mno pqr stuvwxyz humongously-and-tempestuously-but-neither-abstemiously-nor-facetiously-long word!
Line: [[mno pqr stuvwxyz humongously-and-tempestuously-but-neither-abstemiously-nor-facetiously-long word!
]]
Word: [[mno]]
tmp incremented.
tmp incremented.
tmp incremented.
Word: [[pqr]]
tmp incremented.
tmp incremented.
tmp incremented.
tmp incremented.
Word: [[stuvwxyz]]
tmp incremented.
tmp incremented.
tmp incremented.
tmp incremented.
tmp incremented.
Word: [[humongously-and-tempestuously-but-neither-abstemiously-nor-facetiously-long]]
tmp incremented.
tmp incremented.
tmp incremented.
tmp incremented.
tmp incremented.
tmp incremented.
Word: [[word!]]
tmp incremented.
tmp incremented.
tmp incremented.
tmp incremented.
tmp incremented.
tmp incremented.
tmp incremented.
Node word: [[word!]]
Node word: [[humongously-and-tempestuously-but-neither-abstemiously-nor-facetiously-long]]
Node word: [[stuvwxyz]]
Node word: [[pqr]]
Node word: [[mno]]
Node word: [[ghi]]
Node word: [[def]]
Node word: [[abc]]
Freeing list
Freeing: [[word!]]
Freeing: [[humongously-and-tempestuously-but-neither-abstemiously-nor-facetiously-long]]
Freeing: [[stuvwxyz]]
Freeing: [[pqr]]
Freeing: [[mno]]
Freeing: [[ghi]]
Freeing: [[def]]
Freeing: [[abc]]

在 Valgrind 下运行时,我得到了一些奇怪的输出,但那是因为我在没有更新抑制的情况下升级了 o/s(Mac OS X Yosemite 到 El Capitan)。泄漏都在系统代码中,而不是在此代码中,AFAICT。


聊天后

代码的一个特点是如果单词输入了两次,该单词应该移到列表的前面。我的测试是在独特的词组上进行的。问题在于处理第一个单词的重复。这段代码中似乎有一个强大的修复:

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


typedef struct Node2
{
    char* word;
    struct Node2 *next;
} Node2;

Node2* head = NULL;
Node2* now = NULL;
int uniqueWords = 0;
int totalMTFRComparisons = 0;

bool DEBUG = true;

static
Node2* iNode(char* word)
{   
    if (DEBUG)
        printf("addNode2 called [[%s]].\n", word);
    Node2* tmp = head;
    Node2* prev = 0;

    while (tmp)
    {
        totalMTFRComparisons++;
        printf("Current word: %s\n", tmp->word);
        if (strcmp(tmp->word, word) == 0)
        {
            printf("Already entered: [[%s]]\n", tmp->word);
            if (prev != 0)
            {
                prev->next = tmp->next;
                tmp->next = head;
                head = tmp;
            }
            //tmp->next = prev;
            return tmp;
        }
        prev = tmp;
        tmp = tmp->next;
        printf("tmp incremented.\n");
    }

    Node2* ptr = malloc(sizeof(Node2));
    printf("New word: [[%s]]\n", word);
    uniqueWords++;
    ptr->word = malloc(strlen(word) + 1);
    strcpy(ptr->word, word);
    ptr->next = NULL;
    if (head == NULL)
    {
        head = now = ptr;
        return ptr;
    }
    else
    {
        ptr->next = head;
        head = ptr;
    }
    return ptr;
}

static
char* getString(void)
{
    static char buffer[128];
    while (fgets(buffer, 128, stdin) != NULL)
    {
        char *nl = strchr(buffer, '\n');
        if (nl != 0)
            *nl = '\0';
        printf("Line: [[%s]]\n", buffer);
        iNode(buffer);
    }
    return buffer;
}

int main(void)
{
    getString();
    printf("Total words: %d, total MTFR comparisons: %d\n", uniqueWords, totalMTFRComparisons);

    Node2* ptr2 = head;
    Node2* tmp2 = head;
    while (ptr2 != NULL)
    {
        printf("Freeing: [[%s]]\n", ptr2->word);
        tmp2 = ptr2->next;
        free(ptr2->word);
        free(ptr2);
        ptr2 = tmp2;
    }
}

它有一些诊断打印。换行符剥离意味着诊断输出比您在聊天中看到的要短,如果您费心查看聊天内容(字符串末尾有换行符 - 行被视为单词,包括换行符)。

【讨论】:

  • 尝试 mallocing `strlen(word) + 1' 但奇怪的循环行为仍然存在。将尝试您的其他基本诊断并立即回复。
  • 还请阅读如何创建 MCVE (How to create a Minimal, Complete, and Verifiable Example?) 我们目前拥有的不是 MCVE;我们没有结构定义(不难猜,但我们不应该猜),也没有驱动它的代码。这也不应该很难或很大。它不需要是你的完整程序——事实上,它可能不应该是。但它确实需要足够可编译和完整。
  • 这里是一些输入和输出示例:pastebin.com/QfuCm7gt 当前单词显示tmp-&gt;word 是每循环一圈
  • 更新了 OP 中的完整代码 sn-p 以匹配 MCVE 定义。
  • @Sleepless:很有趣。您对单词的定义与我对行的定义相匹配,但这是可以容忍的。它稍微简化了 MCVE。仔细看看。
【解决方案2】:
ptr->word = malloc(strlen(word));

上一行:不正确,内存少 例如:strlen("hello") = 5 而 sizeof("hello") 会给你 6

将行改为

ptr->word = malloc(sizeof(word));

那么在strcpy之后就可以正常工作了

【讨论】:

  • 这将给出 char * 的大小。基本上不会适用于所有情况。
猜你喜欢
  • 1970-01-01
  • 2012-05-01
  • 1970-01-01
  • 2013-03-19
  • 2018-04-04
  • 1970-01-01
  • 1970-01-01
  • 2019-08-06
  • 2013-12-01
相关资源
最近更新 更多