遍历 C 指针列表:奇怪的 printf 行为

Posted

技术标签:

【中文标题】遍历 C 指针列表:奇怪的 printf 行为【英文标题】:Traversing C pointer list: weird printf behaviour 【发布时间】:2017-02-06 19:33:49 【问题描述】:

我声明了一个用 C 实现的链表如下:

struct node_List 
    int i;
    char * name;
    struct node_List* next;
;

typedef struct node_List nodeList;

然后我将列表头全局声明为:

nodeList list;        // head of the list - does not contain relevant data

最后,我有一个函数id(char * s),它的唯一参数是字符串s

nodeType id(char *s)

    nodeType *p;         // another List type

    if ((p = malloc(sizeof(nodeType))) == NULL) 
        // error: out of memory;
    
    nodeList * node = &list;

    // printf(" ");

    while (node->next != NULL)
        node = node->next;
        if (strcmp(node->name, s) == 0)
            // printf(" ");
            // assign node to an attribute in p
            return p;
       
    
    // error: not found;

问题是,当我运行这个程序并调用foo("somestring") 时,程序执行error: not found 部分并中止执行,尽管字符串somestring 在列表中。 我尝试通过插入一些printf() 来执行相同的程序以进行调试,它运行良好,除了它会在输出中打印额外的字符。

每次我添加一些打印行时都会发生这种情况,例如如果我取消注释我在上面的示例中编写的两个printf()s(其中一个或两个,我得到相同的成功结果)。如果 printf 被调用时不带参数或使用空字符串 "",则它不起作用。

我不知道发生了什么,我仔细检查了列表创建和填充功能,我完全确定它们可以正常工作。我尝试更改 while 中断条件,但这也不起作用。我在 Linux(使用 gcc)和 Windows(使用 CodeBlocks 编辑器的集成编译器)上都观察到了类似的行为

printf 指令怎么会对程序产生如此大的影响?

编辑:此代码是用 Yacc 编写的语法分析器的一部分。整个代码可以在下面找到。读了很久,还没有完成,但是上面的代码已经过测试并且可以按照说明工作。

词法分析器:http://pastebin.com/1TEzzHie

解析器:http://pastebin.com/vwCtMhX4

【问题讨论】:

您似乎跳过了列表的第一个元素。在您使用的第一步中: node = node->next; 该行为可能还取决于您将list 初始化为什么,以及您如何构造链表的其余部分。你没有表现出来 我们无法调试部分代码。请提供minimal reproducible example。 只是澄清一下-您是说:使用发布的代码找不到字符串,但是如果未注释两个printf,则找到字符串? 您很可能遇到了内存损坏问题。或者严重误入歧途的事情。使用带有Valgrind 的机器并运行它。听起来,您已经完成了足够多的工作,知道这很奇怪,并且您当前尝试调试它的方式不起作用。因此,您需要进入不同的调试模式。使代码可测试;单独测试它。确保使用 Valgrind 隔离它是干净的。然后考虑将其重新插入到更复杂的场景中。未定义的行为包括“大部分情况下似乎可以工作”的选项。 【参考方案1】:

查看提供的源代码时,探索链表的算法有两种方法在while循环比较中错过节点。

方式 1 - 仅从列表的第二个节点开始。

在比较之前放置node = node->next; 将强制第一个比较为&(list)->next 而不是&(list)

要从第一个节点开始,只需将node = node->next; 放在后面 比较。

方式 2 - 永远不会结束到列表的最后一个节点。

在while条件中使用(node->next != NULL)会在比较最后一个节点之前强制退出循环=>node->next = NULL;

要以最后一个节点结束,只需将 while 条件更改为 (node != NULL)

解决方案:

while (node != NULL) // end from the last node
    if (strcmp(node->name, s) == 0)
        // printf(" ");
        // assign node to an attribute in p
        return p;
    
    node = node->next; // explore link after comparison

【讨论】:

正如评论和声明中所写,list 是一个空头,其值应被忽略。通过将node = node->next; 放在while 之后,我获得了与您描述的相同的结果。这里的问题是,为什么单个非空 printf 语句会如此大地改变代码的行为,@JonathanLeffler 可能在上面的 cmets 中以最好的方式回答了这个问题【参考方案2】:

实际的错误是函数返回的变量的类型声明错误:

nodeType* createPoint(char* l)
    nodeList* p;

    if((p=malloc(sizeof(nodeList))) == NULL)
         yyerror("out of memory");
     else 
        // do stuff with p
    

    return p;

函数返回值为nodeType*,p 被实例化为nodeList*。 这两种类型的声明非常简单,这就是程序可以运行的原因。

工作代码可以在here找到。

printf() 的奇怪行为可能是由 printf 的参数所需的堆空间引起的:因为这个函数接受任意数量的参数,所以它将它们保存在一个列表中。这个列表在堆中被实例化,在那里覆盖了由于 createPoint 的错误实现留下的旧数据。

【讨论】:

以上是关于遍历 C 指针列表:奇怪的 printf 行为的主要内容,如果未能解决你的问题,请参考以下文章

调用fread后奇怪的printf行为

c-51 指针遍历数组 二维数组

奇怪的指针行为 C++ / 继承

指针行为将值设为 1

C++ while循环奇怪的行为

jQuery可拖动+可排序:奇怪的鼠标偏移行为