多次元配列で行と列の添字を取り違えて違う値を読む
arr[row][col]とarr[col][row]は多くの場合どちらもコンパイルは通り実行もできますが、正方でない配列では違う位置の値を読んでしまいます。
なぜエラーが出ないのか
出力: 7
(エラーなし)- Cは何も報告しません。コンパイルも実行も正常に完了するためです
出力: 7- 実際の挙動 — 期待した結果と食い違っている箇所
見つけ方- エラーが出ないので、出力を目で確かめるか、コードを目で追うしかありません。この種の誤りが最も発見が遅れます
このエラーが出る典型パターン
パターン1
1 #include <stdio.h> 2 3 int main(void) { 4 int grid[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; 5 int row = 0, col = 2; 6 printf("%d\n", grid[col][row]); ^ 7 return 0; 8 }
grid[row][col]とgrid[col][row]はどちらも配列の範囲内なのでエラーにはなりませんが、対称でない行列では違う位置の値を読んでしまいます。
直し方: col][row を row][col にします。
パターン2
1 #include <stdio.h> 2 3 int main(void) { 4 int table[3][3] = {{10, 20, 30}, {40, 50, 60}, {70, 80, 90}}; 5 int row = 1, col = 0; 6 printf("%d\n", table[col][row]); ^ 7 return 0; 8 }
行と列のどちらを先に書くかは、配列を宣言したときの次元の順序と揃える必要があります。
直し方: col][row を row][col にします。
パターン3
1 #include <stdio.h> 2 3 int main(void) { 4 int m[3][3] = {{2, 4, 6}, {8, 10, 12}, {14, 16, 18}}; 5 int row = 2, col = 1; 6 printf("%d\n", m[col][row]); ^ 7 return 0; 8 }
添字を取り違えても実行時エラーにはならず、たまたま範囲内の別の値が読めてしまうため発見が遅れます。
直し方: col][row を row][col にします。
パターン4
1 #include <stdio.h> 2 3 int main(void) { 4 int board[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; 5 int row = 0, col = 1; 6 printf("%d\n", board[col][row]); ^ 7 return 0; 8 }
添字の順序を配列の次元と揃えないと、意図しない位置の値を読んでしまいます。
直し方: col][row を row][col にします。
パターン5
1 #include <stdio.h> 2 3 int main(void) { 4 int mat[3][3] = {{5, 6, 7}, {8, 9, 10}, {11, 12, 13}}; 5 int row = 2, col = 0; 6 printf("%d\n", mat[col][row]); ^ 7 return 0; 8 }
行と列の添字を入れ替えても配列の範囲内なのでエラーにはならず、静かに違う値を返します。
直し方: col][row を row][col にします。
よくある誤解
配列のサイズが行数と列数で違う場合、添字を逆にすると本来アクセスできないはずの範囲まで読めてしまうことがあり、たまたま動いているように見えるため発見が遅れます。行と列、どちらを先に書くかは配列を宣言したときの次元の順序と揃える必要があります。
実務での勘所
Cの多次元配列は、実際には1本の連続したメモリ領域を、行優先(row-major)という順序でまとめて確保しているだけです。arr[i][j]は、コンパイラの内部ではarr + i * 列数 + jという1次元の位置計算に変換されています。行と列の添字を入れ替えるということは、この位置計算の式でiとjの掛ける係数を取り違えることを意味し、正方(行数=列数)でない配列では計算結果がまったく違うメモリ位置を指すため、無関係な値を読んでしまいます。