I have written a short (450 lines) program that calculates some situations that can actually occur in a Connect Four game. The program tries to build the game tree and it recognizes if a situation already occured (also I skip branches if only the symmetric version happened).
I count the number of times that I come to an situation that already happened with int alreadyCounter. But I’ve noticed something strange when executing:
Adding
printf("abcdefghijklm");
changes the result of my alreadyCounter!
I don’t use other cores/ processes / threads. I compile the C-program with
gcc -g -W -Wall -Werror -std=c99 -o connectfour
The full source is at GitHub. (I’m sorry, I couldn’t make the code significantly shorter)
Take a look at connectfour.c and connectfour-strange.c.
They only differ in line 386.
But they give – apart from many “abcdefghijklm” – different results:
connectfour:
[...]abcdefghijklmabcdefghijklm########################Finish:
Maximum of 20000 reached
alreadyCounter: 1547
mirroredCounter: 0
connectfour-strange:
[...]
########################Finish:
Maximum of 20000 reached
alreadyCounter: 1566
mirroredCounter: 0
Why do I get different results on a single core / process / thread program when I only add some constant output?
I don’t know if that is the cause for your observations, but in lines 334ff,
you write outside the allocated memory (your row index goes from
-(BOARD_WIDTH)to-1), thus invoking undefined behaviour.Also, that doesn’t mirror the board. You very probably meant
there. Apart from that, I see no obvious candidates like out-of-bounds writes.
In
isBoardFinished(line 43ff), you stop checking before you have reached row/column 0:(ditto for
yin the top-down check, and both in the diagonals checks). That should bex >= 0to use the full board. But this could not overwritealreadyCounter, still it may be useful.But compiling the code with warnings turned on and optimisations shows the probable cause:
So the warning and accompanying note (it appears multiple times with different function names but the same location due to inlining) tell us that it should be
to get deterministic results from invoking
getFirstIndexwith identical boards. Since you take the modulus with respect toMAXIMUM_SITUATIONSbefore returning an index, the returned index shouldn’t be out of bounds, however, so no crash is to be expected as a consequence of that. But when you print out something, that may cause allocation on the stack (need not, the arguments and return address could be all passed in registers), and if it does, that can influence the bit-pattern at the place whereindexis allocated the next timegetFirstIndexis called. That would change the returned value, and you look in the wrong slot indatabase, so you miss a previous occurrence of an identical board, thus you get fewer duplicates than if the valuegetFirstIndexproduces only depends on the passed-in board (and not also on what bits happened to occupy a particular stack location).Note that it is – with my version of gcc – necessary to have both, warnings tuned on and optimisations, to get the warning. My
clangdoesn’t warn about it even with warnings and optimisations turned to the highest level. Other versions of gcc and clang, and other compilers may have different success in identifying the problem.With
indexinitialised to 0 ingetFirstIndex, I get a consistentindependent from whether the string is printed and the optimisation level. Without the initialisation, the value of
alreadyCounterdepends on both factors.