int a[5][5] が実際に何であるかを調べることから始めるべきです。関連するタイプは次のとおりです。
関連するintの配列[25]はありません。
sizeof セマンティクスが、配列全体が連続していることを暗示しているのは正しいことです。int の配列 [5] は 5*sizeof(int) でなければならず、再帰的に適用される a[5][5] は 5*5*sizeof(int) でなければなりません。追加のパディングの余地はありません。
さらに、sizeof を指定して memset、memmove、または memcpy に指定した場合、配列全体が機能している必要があります。(char *) を使用して配列全体を反復処理することも可能でなければなりません。したがって、有効な反復は次のとおりです。
int a[5][5], i, *pi;
char *pc;
pc = (char *)(&a[0][0]);
for (i = 0; i < 25; i++)
{
pi = (int *)pc;
DoSomething(pi);
pc += sizeof(int);
}
(int *) で同じことを行うと、定義されていない動作になります。前述のように、int の配列 [25] が含まれていないためです。クリストフの答えのようにユニオンを使用することも有効です。しかし、これをさらに複雑にする別の点があります。それは等値演算子です。
6.5.9.6 2 つのポインタが等しく比較されるのは、両方がヌル ポインタであり、両方が同じオブジェクト (オブジェクトへのポインタとその先頭のサブオブジェクトを含む) または関数へのポインタであり、両方が最後の要素の 1 つ後ろを指すポインタである場合のみです。同じ配列オブジェクト、または 1 つは 1 つの配列オブジェクトの末尾を過ぎたものへのポインターであり、もう 1 つは別の配列オブジェクトの先頭へのポインターであり、アドレス空間内の最初の配列オブジェクトの直後にたまたま続きます。91)
91) 2 つのオブジェクトがメモリ内で隣接している可能性があります。これは、それらがより大きな配列の隣接する要素または構造体の隣接するメンバーであり、それらの間にパディングがないため、または実装がそれらを配置することを選択したためです。以前の無効なポインター操作 (配列境界外へのアクセスなど) が未定義の動作を生成した場合、その後の比較でも未定義の動作が生成されます。
これは、次のことを意味します。
int a[5][5], *i1, *i2;
i1 = &a[0][0] + 5;
i2 = &a[1][0];
i1 は i2 と等しいと比較されます。ただし、(int *) を使用して配列を反復処理する場合、最初のサブ配列から派生しているため、まだ未定義の動作です。魔法のように 2 番目のサブ配列へのポインターに変換されるわけではありません。
これをしている時も
char *c = (char *)(&a[0][0]) + 5*sizeof(int);
int *i3 = (int *)c;
役に立ちません。比較すると i1 および i2 と等しくなりますが、どの部分配列からも派生していません。これは、せいぜい単一の int または int の配列 [1] へのポインターです。
私はこれを標準のバグとは考えていません。これは逆です。これを許可すると、配列の型システムまたはポインター演算の規則、またはその両方に違反する特殊なケースが導入されます。定義の欠落と見なされる場合がありますが、バグではありません。
したがって、a[5][5] のメモリ レイアウトが a[25] のレイアウトと同一であり、(char *) を使用するまったく同じループを使用して両方を反復処理できる場合でも、実装は失敗する可能性があります。一方が他方として使用されている場合はアップします。なぜそれが必要なのか、またはそのような実装を知っているのかわかりません。また、標準には、これまで言及されていない単一の事実があり、それが明確に定義された動作になっている可能性があります。それまでは、未定義と見なし、安全側にとどまります。