これらの答えは確かにもう少し深さを要求します。ポインタをよく理解すればするほど、書く悪いコードは少なくなります。
配列とポインタは、同じである場合を除いて、同じではありません。頭のてっぺんから:
int a[2][2] = { 1, 2, 3, 4 };
int (* p)[2] = a;
ASSERT (p[1][1] == a[1][1]);
配列「a」は、ポインタ「p」とまったく同じように機能します。そしてコンパイラは、それぞれから、具体的にはアドレスと、インデックス付きアドレスを計算する方法を知っています。ただし、配列aは実行時に新しい値を取ることができないのに対し、pは取ることができることに注意してください。したがって、aの「ポインタ」の側面は、プログラムが実行されるまでになくなり、配列のみが残ります。逆に、p自体は単なるポインタであり、実行時に何でも何も指し示すことができません。
ポインタ宣言の構文は複雑であることに注意してください。(それが、今日私が最初にstackoverflowに来た理由です。)しかし、必要性は単純です。最初の列を超える要素のアドレスを計算する方法をコンパイラーに指示する必要があります。(右端のインデックスには「列」を使用しています。)この場合、インデックス[1] [1]にアドレス((2 * 1)+ 1)をインクリメントする必要があると想定できます。
ただし、コンパイラーが知っていること(願わくば)が他にもいくつかありますが、知らないかもしれません。
コンパイラは2つのことを知っています:1)要素がメモリに順番に格納されているかどうか、2)ポインタの追加の配列があるかどうか、または配列の先頭へのポインタ/アドレスが1つだけあるかどうか。
一般に、コンパイル時の配列は、次元に関係なく、追加のポインターなしで順番に格納されます。ただし、確かに、コンパイラのドキュメントを確認してください。したがって、コンパイラがa [0] [2]のインデックス付けを許可している場合、それは実際にはa [1] [0]などです。ただし、実行時配列は作成します。選択した長さの1次元配列を作成し、それらのアドレスを、選択した長さの他の配列に配置できます。
そしてもちろん、これらのいずれかをいじくりまわす理由の1つは、実行時の乗算、シフト、またはポインターの逆参照を使用して配列にインデックスを付けることを選択しているためです。ポインターの逆参照が最も安価な場合は、ポインターの配列を作成する必要があるため、行アドレスを計算するために算術演算を行う必要はありません。1つの欠点は、追加のポインタを格納するためにメモリが必要になることです。また、列の長さが2の累乗である場合、アドレスは乗算ではなくシフトで計算できることに注意してください。したがって、これは長さを埋める良い理由かもしれません-そしてコンパイラは、少なくとも理論的には、あなたに言わずにこれを行うことができます!また、速度とスペースのどちらの最適化を選択するかによって異なる場合があります。
「モダン」および「パワフル」と表現されているアーキテクチャは、おそらく間接参照と同じ速さで乗算され、コードが正しいかどうかを除いて、これらの問題は完全に解消されます。