为什么打印常量会改变单芯/工艺应用程序的结果



我写了一个简短的(450行)程序,可以计算Connect Four游戏中实际发生的一些情况。该程序试图构建游戏树,并识别是否已经发生了情况(如果只有对称版本发生,我也会跳过分支)。

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

添加

printf("abcdefghijklm"); 

更改我的alreadyCounter的结果!

我不使用其他核心/进程/线程。我用编译C程序

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

完整的源代码在GitHub。(很抱歉,我无法显著缩短代码)

看看connectfour.cconnectfour-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++)

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

最新更新