Массивы в Си это указатели на область памяти некоторого размера. Точнее просто указатели, размер, хоть где-то и есть (free ведь откуда-то знает сколько памяти освободить), но точно не в указателе.
Рядом с массивом - за его границами, лежат какие-то данные. Для вас это ничего не значащий мусор, но это могут быть другие переменные, или адреса возврата из функции, если массив лежит на стэке.
Так вот, ни в коем случае не модифицируйте и не читайте память, которая лежит за границами массива. В лучшем случае, вы наткнётесь на свободный участок, заполненный мусором. В худшем - перезапишете значение другой переменной и создадите себе баг, который без отладчика не найдёте.
Что касается предметной области:
char symbols[] = "XO";
uint8_t winner_index;
for(uint8_t char_index=0; char_index<2; char_index++){
for(uint8_t i=0; i<3; i++){
// rows
if(a[i][0] == symbols[char_index] && a[i][1] == symbols[char_index] && a[i][2] == symbols[char_index]){
winner_index = char_index;
break 2;
}
// columns
if(a[0][i] == symbols[char_index] && a[1][i] == symbols[char_index] && a[2][i] == symbols[char_index]){
winner_index = char_index;
break 2;
}
}
// diagonal 1
if(a[0][0] == symbols[char_index] && a[1][1] == symbols[char_index] && a[2][2] == symbols[char_index]){
winner_index = char_index;
break;
}
// diagonal 2
if(a[0][2] == symbols[char_index] && a[1][1] == symbols[char_index] && a[2][0] == symbols[char_index]){
winner_index = char_index;
break;
}
}