Joelが指摘するように、 スタックオーバーフローポッドキャスト#34 、 Cプログラミング言語 (別名:K&R)、Cの配列のこのプロパティについての言及があります。a[5] == 5[a]
Joelは、ポインタ演算のせいだと言っていますが、まだわかりません。 なぜa[5] == 5[a]
ですか?
C標準では、[]
演算子を次のように定義しています。
a[b] == *(a + b)
したがってa[5]
は次のように評価されます。
*(a + 5)
そして5[a]
は以下のように評価されます。
*(5 + a)
a
は、配列の最初の要素へのポインタです。 a[5]
はa
からさらに5要素の値です。これは*(a + 5)
と同じであり、小学校の数学でも同じです(加算は 可換です ).
配列へのアクセスはポインタによって定義されるからです。 a[i]
は交換可能な*(a + i)
を意味するように定義されています。
他の答えで何かが見逃されていると思います。
はい、p[i]
は定義上*(p+i)
と等価です。これは(加算が可換であるため)*(i+p)
と等価です(これも[]
演算子の定義による)はi[p]
と等価です。
(そしてarray[i]
では、配列名は暗黙的に配列の最初の要素へのポインタに変換されます。)
しかし、この場合、加法の可換性はそれほど明白ではありません。
両方のオペランドが同じ型である場合、または共通の型にプロモートされる異なる数値型である場合でも、可換性は完全に理にかなっています:x + y == y + x
。
しかし、この場合は、一方のオペランドがポインタで、もう一方が整数であるポインタ演算について具体的に説明しています。 (整数+整数は異なる演算であり、ポインター+ポインターは意味がありません。)
C標準の+
演算子( N1570 6.5.6)に関する説明には、次のように記載されています。
さらに、両方のオペランドが算術型を持つか、または一方のオペランドが完全なオブジェクト型へのポインタで、もう一方が整数型を持つ必要があります。
それは同じくらい簡単に言ったかもしれません:
さらに、両方のオペランドが算術型を持つか、または左オペランドが完全なオブジェクト型へのポインタであり、右オペランドが持つ整数型です。
その場合、i + p
とi[p]
の両方は違法になります。
C++の用語では、実際には2組のオーバーロードされた+
演算子があります。
pointer operator+(pointer p, integer i);
そして
pointer operator+(integer i, pointer p);
そのうちの最初のものだけが本当に必要です。
それでは、なぜそれはこのようなのでしょうか。
C++はBから得たCからこの定義を継承しました(配列の索引付けの可換性は、1972 ユーザーのためのB への参照で明示的に言及されています)。それは BCPL (1967年のマニュアル日付)から入手したもので、これは以前の言語からでも入手できた可能性があります(CPL?Algol?)。
そのため、配列の索引付けは足し算で定義され、その足し算や整数であってもその足し算は交換可能であるという考えは、Cの先祖言語に遡ります。
これらの言語は、現代のCよりもはるかに厳密に型付けされていません。特に、ポインタと整数の区別はしばしば無視されていました。 (初期のCプログラマは、unsigned
キーワードが言語に追加される前は、ポインタを符号なし整数として使用することがありました。)したがって、オペランドが異なるタイプであるため加算を非可換にするという考えは、おそらくそれらの言語の設計者にはなかったでしょう。 。ユーザーが2つの「もの」を追加したい場合、それらの「もの」が整数、ポインター、またはその他のものであるかどうかにかかわらず、それを防ぐのは言語の責任ではありませんでした。
そして何年にもわたって、その規則へのどんな変更も既存のコードを壊したでしょう(1989年のANSI C規格は良い機会であったかもしれませんが)。
CまたはC++、あるいはその両方をポインタを左側に、整数を右側に置くように変更すると、既存のコードが壊れる可能性がありますが、実際の表現力を失うことはありません。
したがって、arr[3]
と3[arr]
はまったく同じことを意味しますが、後者の形式は IOCCC の外側には表示されません。
そしてもちろん
("ABCD"[2] == 2["ABCD"]) && (2["ABCD"] == 'C') && ("ABCD"[2] == 'C')
これが主な理由は、Cが設計された70年代には、コンピュータのメモリが不足していたため(64KBが多かった)、Cコンパイラでは構文チェックがあまり行われなかったためです。そのため、 "X[Y]
"はやや盲目的に "*(X+Y)
"に変換されました。
これは "+=
"と "++
"の構文についても説明しています。 "A = B + C
"という形式のすべてのものは、同じコンパイル済み形式です。しかし、BがAと同じオブジェクトであれば、アセンブリレベルの最適化が利用可能でした。しかし、コンパイラはそれを認識するのに十分明るくなかったので、開発者は(A += C
)をしなければなりませんでした。同様に、C
が1
である場合、異なるアセンブリレベルの最適化が利用可能であり、コンパイラがそれを認識していなかったので、やはり開発者はそれを明示的にする必要がありました。 (ごく最近ではコンパイラはそうしているので、これらの構文は最近はほとんど不要です)
sizeof
に関するDinahの問題について誰も言及していないようです。
ポインタには整数しか追加できません。2つのポインタを一緒に追加することはできません。このように、整数へのポインタ、またはポインタへの整数を追加するとき、コンパイラはどのビットが考慮される必要があるサイズを持っているか常に知っています。
文字通り質問に答えるために。 x == x
が常に正しいとは限りません。
double zero = 0.0;
double a[] = { 0,0,0,0,0, zero/zero}; // NaN
cout << (a[5] == 5[a] ? "true" : "false") << endl;
版画
false
いい質問/答え。
Cのポインタと配列は同じではないことを指摘したいだけですが、この場合は違いは本質的ではありません。
次の宣言を考えてください。
int a[10];
int* p = a;
a.outでは、シンボルaがそのアドレスにあります。配列の先頭にあり、symbolpはポインタが格納されているアドレスにあり、そのメモリ位置のポインタの値はの先頭にあります。配列.
私は、この醜い構文が「役に立つ」、または同じ配列内の位置を参照するインデックスの配列を扱いたいときには少なくとも遊ぶのが非常に楽しいかもしれないことを見つけました。ネストした角括弧を置き換えてコードを読みやすくすることができます。
int a[] = { 2 , 3 , 3 , 2 , 4 };
int s = sizeof a / sizeof *a; // s == 5
for(int i = 0 ; i < s ; ++i) {
cout << a[a[a[i]]] << endl;
// ... is equivalent to ...
cout << i[a][a][a] << endl; // but I prefer this one, it's easier to increase the level of indirection (without loop)
}
もちろん、実際のコードではその使用例はないと確信していますが、とにかく面白かったです:)
Cのポインタについては、
a[5] == *(a + 5)
そしてまた
5[a] == *(5 + a)
それゆえa[5] == 5[a].
は本当です
答えではありませんが、考えるべき食べ物がいくつかあります。クラスがインデックス/添字演算子をオーバーロードしている場合、式0[x]
は機能しません。
class Sub
{
public:
int operator [](size_t nIndex)
{
return 0;
}
};
int main()
{
Sub s;
s[0];
0[s]; // ERROR
}
intクラスにアクセスできないので、これはできません。
class int
{
int operator[](const Sub&);
};
それはTed JensenによるCのポインターと配列のチュートリアルでとても良い説明があります。
Ted Jensenはそれを次のように説明しています。
実際、これは事実です。つまり、どこに
a[i]
を書いても問題なく*(a + i)
に置き換えることができます。実際、コンパイラはどちらの場合も同じコードを作成します。したがって、ポインタ演算は配列のインデックス付けと同じものであることがわかります。どちらの構文でも同じ結果が得られます。これは、ポインタと配列が同じものではないということではありません。配列の特定の要素を識別するためには、配列インデックスを使用する構文とポインタ演算を使用する構文の2つの構文のどちらかを選択する必要があると言っているだけです。
さて、この最後の式、その一部を見ると
(a + i)
は+演算子を使った簡単な追加であり、Cの規則はそのような式は可換であると述べています。つまり、(a + i)は(i + a)
と同じです。したがって、*(i + a)
と同じくらい簡単に*(a + i)
を書くことができます。しかし*(i + a)
はi[a]
から来たのかもしれません!これらすべてから、次のような奇妙な真実が生まれます。char a[20];
書き込み
a[3] = 'x';
書くことと同じです
3[a] = 'x';
私は質問が答えられたことを知っています、しかし私はこの説明を共有することに抵抗できませんでした。
a
がint
配列で、int
のサイズが2バイトで、a
のベースアドレスが1000であるとしましょう。
a[5]
のしくみ - >
Base Address of your Array a + (5*size of(data type for array a))
i.e. 1000 + (5*2) = 1010
そう、
同様に、cコードが3アドレスコードに分割されると、5[a]
は - >になります。
Base Address of your Array a + (size of(data type for array a)*5)
i.e. 1000 + (2*5) = 1010
したがって、基本的に両方のステートメントはメモリ内の同じ場所を指しているため、a[5] = 5[a]
です。
この説明は、配列内の負のインデックスがCで機能する理由でもあります。
すなわち、私がa[-5]
にアクセスした場合、それは私に与えるでしょう
Base Address of your Array a + (-5 * size of(data type for array a))
i.e. 1000 + (-5*2) = 990
それは私にオブジェクトを場所990に返します。
C配列 では、arr[3]
と3[arr]
は同じで、同等のポインタ表記は*(arr + 3)
から*(3 + arr)
です。しかし、それどころか、[arr]3
や[3]arr
は有効ではないため、(arr + 3)*
または(3 + arr)*
は正しくなく、構文エラーになります。その理由は、間接参照演算子は、アドレスの後ではなく、式によって生成されたアドレスの前に配置する必要があるためです。
cコンパイラで
a[i]
i[a]
*(a+i)
配列内の要素を参照するさまざまな方法があります。 (NOT AT ALL WEIRD)
少し歴史があります。他の言語の中でも、BCPLはCの初期開発にかなり大きな影響を与えました。次のようにBCPLで配列を宣言したとします。
let V = vec 10
これは実際には10ワードではなく11ワードのメモリを割り当てていました。通常、Vが最初で、直後のワードのアドレスが含まれていました。そのため、Cとは異なり、Vという名前を付けてその場所に行き、配列の0番目の要素のアドレスを取得しました。したがって、BCPLの配列間接指定は、次のように表されます。
let J = V!5
配列のベースアドレスを取得するためにVを取得する必要があるので、実際には(BCPL構文を使用して)J = !(V + 5)
を実行する必要がありました。したがってV!5
と5!V
は同義語でした。事例観察として、WAFL(Warwick Functional Language)はBCPLで書かれており、私の記憶の及ぶ限りでは、データストレージとして使用されるノードにアクセスするために前者よりも後者の構文を使用する傾向がありました。これは35年から40年前のどこかに由来するものだと思いますので、私の記憶は少し錆びています。 :)
余分なストレージワードを使用せずに、名前が付けられたときにコンパイラにアレイのベースアドレスを挿入させるという技術革新は後になってきました。 Cの歴史論文によると、これは構造体がCに追加された頃に起こりました。
BCPLの!
は、単項の前置演算子でも二項の中置演算子でもあり、どちらの場合もインダイレクションを行います。バイナリ形式では、間接演算を実行する前に2つのオペランドが加算されています。 BCPL(およびB)のWord指向の性質を考えると、これは実際にはかなり意味がありました。 Cではデータ型を取得する際に "ポインタと整数"の制限が必要になり、sizeof
がものになりました。
Cで
int a[]={10,20,30,40,50};
int *p=a;
printf("%d\n",*p++);//output will be 10
printf("%d\n",*a++);//will give an error
ポインタは「変数」です
配列名は "ニーモニック"または "同義語"です。
p++;
は有効ですがa++
は無効です
a[2]
は2 [a]に等しい
「ポインタ演算」は、内部で次のように計算されます。
*(a+3)
は*(3+a)
と同じです
まあ、これは言語サポートのためにのみ可能な機能です。
コンパイラはa[i]
を*(a+i)
として解釈し、式5[a]
は*(5+a)
に評価されます。足し算は交換可能なので、両者は等しいことがわかります。そのため、式はtrue
と評価されます。