我写了一个简短的(450行)程序,可以计算Connect Four游戏中实际发生的一些情况。该程序试图构建游戏树,并识别是否已经发生了情况(如果只有对称版本发生,我也会跳过分支)。
我数了数我遇到int alreadyCounter
已经发生的情况的次数。但我在执行时注意到了一些奇怪的东西:
添加
printf("abcdefghijklm");
更改我的alreadyCounter
的结果!
我不使用其他核心/进程/线程。我用编译C程序
gcc -g -W -Wall -Werror -std=c99 -o connectfour
完整的源代码在GitHub。(很抱歉,我无法显著缩短代码)
看看connectfour.c
和connectfour-strange.c
。它们只在第386行有所不同。
但除了许多"abcdefghijklm"之外,他们给出了不同的结果:
connectfour:
[...]abcdefghijklmabcdefghijklm########################Finish:
Maximum of 20000 reached
alreadyCounter: 1547
mirroredCounter: 0
connectfour奇怪:
[...]
########################Finish:
Maximum of 20000 reached
alreadyCounter: 1566
mirroredCounter: 0
当我只添加一些常量输出时,为什么我在单个核心/进程/线程程序上得到不同的结果
我在学校担任助理时,已经多次遇到这个问题。
有些学生的程序出现了问题,除非他们在某个地方添加了printf。
Printf分配一些内存并用它做一些疯狂的事情。
我的猜测是,你在某个地方有缓冲区溢出,或者你没有为一个结构分配足够的内存,printf会擦除它
试着运行valgrind,看看是否出了问题。
我不知道这是否是您观察到的原因,但在第334ff行中,
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
(第43ff行)中,在到达行/列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;
// ^^^^^^^
以通过调用具有相同板的CCD_ 11来获得确定性结果。由于您在返回索引之前获取了相对于MAXIMUM_SITUATIONS
的模数,因此返回的索引不应越界,因此不会因此而发生崩溃。但是,当您打印出某些内容时,这可能会导致堆栈上的分配(不需要,参数和返回地址可以都在寄存器中传递),如果是这样,则可能会影响下次调用getFirstIndex
时分配index
的位置的位模式。这将改变返回的值,并且您在database
中查找错误的插槽,因此您错过了相同板的前一次出现,因此与值getFirstIndex
仅产生时相比,您获得的重复次数更少取决于传入的板(而不取决于占据特定堆栈位置的位)。
注意,在我的gcc版本中,有必要同时启用警告和优化,以获得警告。我的clang
即使在警告和优化达到最高级别的情况下也不会对此发出警告。gcc和clang的其他版本以及其他编译器在识别问题方面可能会取得不同的成功。
在getFirstIndex
中将index
初始化为0时,我得到了一致的
########################Finish:
Maximum of 20000 reached
alreadyCounter: 4412
与是否打印字符串和优化级别无关。在没有初始化的情况下,alreadyCounter
的值取决于这两个因素。
我不打算通过500行代码来找到它,但它几乎总是一个内存问题。
通常情况下,这类似于堆栈损坏,您正在对超出末尾的数组进行索引,因此print语句会修改堆栈,从而更改越界访问结果。
通过像valgrind这样的工具运行它,它应该有助于确定违规行为。
我没有找到它的实例,但当使用宏并向它传递类似I++的东西时,这是一个常见的结果。ABS(outcome)
是插入代码的宏(outcome) (outcome < 0 ? -outcome : outcome)
如果结果被传递为i++,那么你会得到(i++) (i++ < 0 ? -i++ : i++)
也许在线程版本中,宏以某种方式被编译器引入