【问题标题】:C Infinite Pointer Loop (Caused By Duplicate Value?)C无限指针循环(由重复值引起?)
【发布时间】:2013-09-26 06:45:05
【问题描述】:

使用 -O3 -Wall -c -fmessage-length=0 -std=c99 使用 MinGW 编译

嗯,这就是我认为的问题所在......这是细分:

我有一个我使用构建的链接列表

typedef struct Coordinate{
    int x;
    int y;
    struct Coordinate *next;
} Coordinate;

我在 6x6 棋盘(矩阵)上添加“有效动作”(在黑白棋/黑白棋游戏中)。我检查一个动作是否有效的逻辑工作得很好——它在列表中添加了让我陷入麻烦的东西。

出于显而易见的原因,我想避免将重复值添加到列表中。但是,我尝试编写的每个函数(看起来应该可以工作)只会使应用程序崩溃,从而在漫长的一天中产生段错误。

所以这是我尝试编写的一个函数:

int inList(Coordinate *list, int x, int y) {

    if (list == NULL) return 0;

    while (list != NULL) {
        if (list->x == x && list->y == y) return 1;
        else list = list->next;
    }
    return 0;
}

并称它为:

Coordinate *validMoves = createNewCoordinate(-1, -1); // Just so that there is *something*

if (!inList(validMoves, 1, 1)) {
validMoves->next = createNewCoordinate(1, 1);
validMoves = validMoves->next;
}

据我所知,这应该可以完美运行。我已经在网上查找了示例,到目前为止,我在这个特定程序中对指针的所有真正令人费解的使用都没有遇到任何问题。

无论如何,真正的问题是,如果我不阻止重复项进入同一个列表(通过指针连接),那么我会得到一个无限循环(我想这是由于两个元素被认为是 equal 造成的,因为它们的非指针类型是相等的)。

我已经在 pastebin 上发布了代码的所有三个部分以供完整参考(不用担心,开源,伙计!): othello.c othello_engine.c othello_engine.h

我尝试过调试,但我不是很擅长,我真的没有看到任何值得一提的东西。谁能解释可能发生的事情和/或举例说明如何避免链表中的重复? (我试过很多方法让我的大脑受伤)

编辑:我确实知道,当我稍后遍历列表时(在游戏中多次有效“回合”之后),由于以下输出,我让自己陷入了循环引用:

{1, 4} {3, 4} {1, 4} {3, 4} {1, 4} {3, 4}

我不知道theList = theList->next = theList(伪正确)是如何进入那里的,也许我们在矩阵中......

【问题讨论】:

  • “我已经尝试过调试,但我并不擅长” -- 现在是时候提升你的水平了 -- 你不会走得太远如果你不能调试你的代码,请找程序员。
  • 是的,我知道。我讨厌抱怨,但现在是凌晨 3 点,我从早上 6 点起就醒了,我需要在 3 小时后再次醒来。在过去的 5 个小时里,我也一直在盯着这段代码,并且在这个特定问题上停留了大约一个小时。这就是我说我真的尝试过的地方,现在我希望真正懂这门语言的人帮助我理解为什么我的系统失败了^^当然,我前几天才开始使用C,所以请剪掉我一些松弛;P
  • 你知道数据的范围还是可以是任何东西?
  • 它必须在0 <= value < size 的约束范围内,其中size 是偶数6 <= size <= 10。不过,这些实际上只是任意值(嗯,0 并不是那么随意)。但是,对于这种特殊情况,这些值受其他语句的约束之前 inList() 被调用和/或Coordinates 被创建。
  • 检查你的 createNewCoordinate() 实现,它 malloc 的内存并设置 x 和 y,但从不将 next 设置为 NULL。因此,您将其保留为 indeterminate 指针。当您使用它时,您正在调用未定义的行为,可能会离开列表的末尾并进入未定义的行为。将new->next = NULL; 添加到该函数。

标签: c pointers linked-list duplicates infinite-loop


【解决方案1】:

如何将新坐标添加到链表中存在问题。

validMoves 是指向有效移动列表中第一个 Coordinate 的指针,因此最初您的链接列表如下所示:

validMoves -> [1st_move] -> [2nd_move] -> [3rd_move] -> ... -> [last_move]

回想一下,这些箭头来自存储在结构中的next 指针(例如,[1st_move] 的next 指针指向[2nd_move],而[last_move] 的next 指针是@ 987654329@)。

现在让我们看看当代码运行时会发生什么,它将新坐标添加到列表中(特别是标记为2 的行):

if (!inList(validMoves, 1, 1)) {
validMoves->next = createNewCoordinate(1, 1); //2
validMoves = validMoves->next;
}

在2 行中,发生了两件事:

  • createNewCoordinate(1, 1) 分配一个新的Coordinate,然后返回一个指向上述Coordinate 的指针,初始内容设置为{1,1,NULL}。
  • 然后,validMoves 指向的结构的next 指针(即[1st_move] 的next 指针)被覆盖,并设置为指向这个{1,1,NULL} 结构。

这会使您的链接列表看起来像:

validMoves -> [1st_move] -> [new struct with content {1,1,NULL}]

??? -> [2nd_move] -> [3rd_move] -> ... -> [last_move]

[1st_move] 的next 指针,以前指向[2nd_move],现在指向你新建的坐标,现在nothing 指向[2nd_move]!所以,从[2nd_move] 开始的原始链表已经被孤立了。

gdb 有助于调试此类问题。一个好的起点是在可疑代码区域之前添加一个断点,将重要的变量放在你的 display 列表中(如validMoves、validMoves->next、validMoves->next->next),然后是逐步执行可疑区域并查看变量的打印值是否在每一步都有意义。

要解决这个问题,我们可以将链表遍历到末尾,然后在此处添加指向 {1,1,NULL} 的指针,但在开头添加新的Coordinate 更容易(也更快)列表,但您需要一个临时指针变量,如下所示:

if (!inList(validMoves, 1, 1)) {
    Coordinate *temp = createNewCoordinate(1, 1);
    temp->next = validMoves; // the new Coordinate now becomes {1,1,&[1st_move]}
    validMoves = temp;
}

现在新添加的Coordinate在链表的开头,旧的[1st_move]移到第二个位置,[2nd_move]移到第三个,依此类推(链表在与添加它们的顺序相反,但我认为在这个用例中元素的顺序无关紧要)。


您所说的申请是针对奥赛罗的。为什么不分配size*sizechars 的连续数组来存储位置是否是有效移动,而不是使用链表?

char * validmoves = malloc(size*size);

这仅使用每个棋盘单元 1 字节的内存(您可以将其设置为 1 或 0 以指示移动是否有效),并且您无需遍历链表来确定是否移动已经存在,只需导航到validmoves[(row*size)+col] 并检查字节是否已设置;只记得在使用前初始化数组。

编码愉快!

编辑:在我上面的回答中,我假设您的 createNewCoordinate 函数将创建的结构的 next 指针的值初始化为 NULL,但现在已经注意到您在othello_engine.c 使其未初始化。您可能确实想将其初始化为NULL。

【讨论】:

  • 非常感谢您对问题的非常详细的解释!我有点迷失在我正在处理的大量指针中。问题确实出在createNewCoordinate 函数中——没有将新坐标的next 值设置为NULL 是问题所在,并且添加了一行修复了段错误和无限循环!我现在意识到,虽然我有一些不合时宜的错误,并且需要更多的约束才能让游戏按照规则集运行。但我可以处理这个没问题,谢谢你的帮助贝瑞:)
【解决方案2】:

分段错误通常是指向错误位置(可能为空)的取消引用指针。每次使用指针值时添加检查(类似于 if ptr != NULL)。

在您的代码中,我注意到一些看起来有点错误的东西。当您找到要添加的新动作时,您将其分配:

validMovees->next = createNewCoordinate(x, y);

然后通过调用使列表本身指向最后一个节点:

validMoves = validMoves->next;

所以现在您的 validMoves 列表实际上只包含一个元素(最后一个),因为它指向列表的末尾。您根本不想更改 validMoves 指针,这应该始终是列表的开头。取而代之的是另一个指向列表末尾的指针,类似于:

Coordinate *validMoves = createNewCoordinate(-1, -1); // Just so that there is *something*
Coordinate *listEnd = validMoves ;


if (!inList(validMoves, 1, 1)) {
listEnd->next = createNewCoordinate(1, 1);
listEnd = listEnd->next;
}

这可能会导致您无限添加相同的动作?? - 不确定没有看到你所有的代码

----- 编辑-----

Coordinate *validMoves;
Coordinate *listEnd = validMoves;

if (!inList(validMoves, 1, 1))
{
    if (validMoves == NULL)
    {
        validMoves = createNewCoordinate(1, 1); // first time only
        listEnd = validMoves;
    }
    else
    {
        listEnd->next = createNewCoordinate(1, 1); // add new element to the end
        listEnd = listEnd->next; // Move listEnd to last element
    }
}

【讨论】:

  • 哦,是的....并坚持调试(printfs)行,这样您就可以看到您的程序到达哪里了:)
  • 我确实有很多行printfs,事实上我所有的代码都在底部的问题中(通过pastebin)。我将函数称为listEnd->next = createNewCoordinate(int x, int y),因为有一个垃圾值是列表的头部。如果我删除该垃圾值(不初始化列表)并尝试使用 createNewCoordinate() 函数创建坐标,我最终会出现段错误。老实说,我不知道为什么,我已经尝试过 10 种不同的方法,但我仍然不断遇到段错误:P
  • 我没有在您的粘贴箱中看到调用 inList 函数的位置...但是无论如何,您不希望列表头部有垃圾值。理想情况下,将 validMoves 创建为空,然后第一次检查是否 validMoves == null 如果是,则分配 createNewCoordinate(1, 1);给它。 listEnd 应该始终指向 validMoves->next(除了在 listEnd = validMoves 的开头。
【解决方案3】:

我问了你的数据范围,以便我可以想一些其他的方法来检查数据的重复。如果内存对您没有限制。您可以执行以下操作。这是一种检查重复数据值的逻辑类型。我有一个简单的数据值查找表,其索引和值相同,并且有一个计数字段。如果计数字段为零,则表示可以输入唯一值。删除数据时,减去计数。这样您就可以跟踪计数并确保值的唯一性。因为,它也是一个不需要遍历的数组。必须为此管理实施一些额外的代码。但是,如果设计得好,应该是可能的。

【讨论】:

  • 这将是处理重复项的一个很好的行动计划 - 在我将正常运行的程序交给我的教授后,我将尝试修改我的代码。目前,该列表具有/显示重复值,但除了令人讨厌之外,它们不会引起任何问题。谢谢你的建议:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-06-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-20
  • 1970-01-01
  • 2012-09-16
相关资源
最近更新 更多