3

我编写了一个简短的(450 行)程序,它计算了四人连线游戏中实际可能发生的一些情况。该程序尝试构建游戏树并识别是否已经发生情况(如果仅发生对称版本,我也会跳过分支)。

我数了数我遇到已经发生的情况的次数int alreadyCounter。但是我在执行时注意到了一些奇怪的事情:

添加

printf("abcdefghijklm"); 

改变我的结果alreadyCounter

我不使用其他核心/进程/线程。我用

gcc -g -W -Wall -Werror -std=c99 -o connectfour

完整源代码位于GitHub。(对不起,我无法使代码显着缩短)

看看connectfour.cconnectfour-strange.c。它们仅在第 386 行有所不同。

但他们给出了——除了许多“abcdefghijklm”——不同的结果:

连接四:

[...]abcdefghijklmabcdefghijklm########################Finish:
Maximum of 20000 reached
alreadyCounter: 1547
mirroredCounter: 0

连接四怪:

[...]
########################Finish:
Maximum of 20000 reached
alreadyCounter: 1566
mirroredCounter: 0

当我只添加一些恒定输出时,为什么我在单个内核/进程/线程程序上得到不同的结果?

4

4 回答 4

4

我在学校担任助理的工作中已经多次看到这个问题。

一些学生的程序段错误,除非他们在某处添加了 printf。

Printf 分配一些内存并用它做一些疯狂的事情。

我的猜测是您在某处有缓冲区溢出,或者您没有为结构分配足够的内存,并且 printf 将其擦除。

尝试运行 valgrind 以查看是否出现问题。

于 2012-11-29T17:59:13.690 回答
1

我不知道这是否是您观察的原因,但在第 334 行,

char mirrored[BOARD_WIDTH][BOARD_HEIGHT];
for (int x = 0; x<BOARD_WIDTH; x++) {
    for (int y=0; y<BOARD_HEIGHT; y++) {
        mirrored[x+1-BOARD_WIDTH][y] = board[x][y];
    }
}

您在分配的内存之外写入(您的行索引从-(BOARD_WIDTH)-1),从而调用未定义的行为。

此外,这并不反映董事会。你很可能是说

mirrored[BOARD_WIDTH - 1 - x][y] = board[x][y];

那里。除此之外,我没有看到像越界写入这样的明显候选者。

isBoardFinished(第 43 行)中,您在到达第 0 行/第 0 列之前停止检查:

while (xTemp > 0)

(同样适用y于自上而下的检查,以及对角线检查)。那应该是x >= 0使用全板。但这不能覆盖alreadyCounter,但它仍然可能有用。

但是在打开警告和优化的情况下编译代码显示了可能的原因:

connect-four.c: In function ‘getFirstIndex’:
connect-four.c:202:19: warning: ‘index’ may be used uninitialized in this function [-Wuninitialized]
connect-four.c: In function ‘getNewIndex’:
connect-four.c:202:19: warning: ‘index’ may be used uninitialized in this function [-Wuninitialized]
connect-four.c:199:18: note: ‘index’ was declared here
connect-four.c: In function ‘getMyIndex’:
connect-four.c:202:19: warning: ‘index’ may be used uninitialized in this function [-Wuninitialized]
connect-four.c:199:18: note: ‘index’ was declared here
connect-four.c: In function ‘makeTurns’:
connect-four.c:202:19: warning: ‘index’ may be used uninitialized in this function [-Wuninitialized]
connect-four.c:199:18: note: ‘index’ was declared here
connect-four.c:202:19: warning: ‘index’ may be used uninitialized in this function [-Wuninitialized]
connect-four.c:199:18: note: ‘index’ was declared here

所以警告和随附的注释(由于内联,它多次出现,具有不同的函数名称但位置相同)告诉我们它应该是

unsigned int getFirstIndex(char board[BOARD_WIDTH][BOARD_HEIGHT]) {
    unsigned int index = 0;
    //               ^^^^^^^

getFirstIndex通过调用相同的板来获得确定的结果。由于您在返回索引之前取模MAXIMUM_SITUATIONS,因此返回的索引不应超出范围,因此不会因此而崩溃。但是当你打印出一些东西时,这可能会导致堆栈分配(不需要,参数和返回地址index可以在寄存器中传递),如果是这样,那会影响分配位置的位模式下次getFirstIndex叫。这会改变返回的值,并且您在 中查找错误的插槽,因此您错过了先前出现的相同板,因此与产生database该值相比,您获得的重复次数更少getFirstIndex取决于传入的板(而不是碰巧占据特定堆栈位置的位)。

请注意 - 对于我的 gcc 版本 - 必须同时调整警告和优化才能获得警告。clang即使将警告和优化调到最高级别,我也不会对此发出警告。其他版本的 gcc 和 clang,以及其他编译器在识别问题方面可能有不同的成功。

index初始化为 0 in ,我得到getFirstIndex一个一致的

########################Finish:
Maximum of 20000 reached
alreadyCounter: 4412

与是否打印字符串和优化级别无关。如果没有初始化, 的值alreadyCounter取决于这两个因素。

于 2012-11-29T20:03:02.127 回答
0

我不会通过 500 行代码来找到它,但这几乎总是一个内存问题。

通常,它类似于堆栈损坏,您正在索引超出末尾的数组,因此 print 语句修改堆栈,因此越界访问结果被更改。

通过像 valgrind 这样的工具运行它,它应该有助于查明违规行为。

于 2012-11-29T17:58:58.833 回答
0

我没有找到它的实例,但这是使用宏并将 i++ 之类的东西传递给它时的常见结果。这ABS(outcome)是一个插入代码的宏, (outcome) (outcome < 0 ? -outcome : outcome) 如果结果作为 i++ 传递,那么你会得到 (i++) (i++ < 0 ? -i++ : i++)

也许在线程版本中,宏被编译器以某种方式引入

于 2012-11-29T18:51:40.220 回答