私はあなたの質問を2つの質問として解釈します:1)なぜ->
存在するのか、2)なぜ.
自動的にポインターを逆参照しないのか。両方の質問への回答には歴史的なルーツがあります。
なぜ->
存在するのですか?
(私は「のためのCRMとして参照するC言語の非常に最初のバージョンのいずれかでCリファレンス・マニュアル 1975年5月に第6版のUnixで来た」、)、オペレータは->
と同義ではない、非常に排他的な意味を持っていた*
と.
の組み合わせ
CRMによって記述されたC言語は、多くの点で現代のC言語とは非常に異なっていました。CRM構造体のメンバーは、バイトオフセットのグローバルコンセプトを実装しました。バイトオフセットは、タイプの制限なしに任意のアドレス値に追加できます。つまり、すべての構造体メンバーのすべての名前は、独立したグローバルな意味を持っていました(したがって、一意である必要がありました)。たとえば、次のように宣言できます
struct S {
int a;
int b;
};
name a
はオフセット0を表し、name はb
オフセット2を表します(int
サイズ2のタイプでパディングなしと想定)。この言語では、翻訳単位のすべての構造体のすべてのメンバーに一意の名前を付けるか、同じオフセット値を表す必要があります。たとえば、同じ宣言単位でさらに宣言できます
struct X {
int a;
int x;
};
名前a
は常にオフセット0を表すため、これで問題ありません。しかし、この追加の宣言
struct Y {
int b;
int a;
};
a
オフセット2およびb
オフセット0 として「再定義」しようとしたため、正式には無効になります。
そして、ここが->
演算子の出番です。すべての構造体メンバー名には独自の十分なグローバル意味があるため、言語はこれらのような式をサポートしました
int i = 5;
i->b = 42; /* Write 42 into `int` at address 7 */
100->a = 0; /* Write 0 into `int` at address 100 */
最初の割り当ては、「アドレスを取ると、コンパイラによって解釈された5
、オフセット追加2
それと割り当てるため42
にint
生じたアドレスの値」。つまり、上記はアドレスの値に割り当て42
られint
ます7
。このの使用は->
、左側の式のタイプを気にしなかったことに注意してください。左側は右辺値の数値アドレス(ポインターまたは整数)として解釈されました。
この種の策略は、*
およびの.
組み合わせでは不可能でした。できなかった
(*i).b = 42;
以降は、*i
すでに無効な式です。*
それから分離されているので、オペレータは.
、そのオペランドに厳しくタイプの要件を課します。この制限を回避する機能を提供するために、CRMは->
演算子を導入しました。これは、左側のオペランドのタイプから独立しています。
キースはコメントで述べたように、この差の->
と*
+ .
の組み合わせは、どのようなCRMは7.1.8で、「要件の緩和」と言及されている。その要件の緩和を除きE1
、式はポインタ型のものE1−>MOS
とまったく同じです(*E1).MOS
その後、K&R Cでは、CRMで最初に説明された多くの機能が大幅に作り直されました。「グローバルオフセット識別子としての構造体メンバー」の概念は完全に削除されました。そして、->
オペレーターの機能は、の機能*
と.
組み合わせと完全に同一になりました。
.
ポインタを自動的に逆参照できないのはなぜですか?
この場合も、CRMバージョンの言語では、.
演算子の左のオペランドは左辺値である必要がありました。それがそのオペランドに課された唯一の要件でした(そしてそれが、上で->
説明したように、それがとの違いをもたらしたものです)。CRM の左のオペランドが構造体型を持つ必要がないことに注意してください.
。左辺値、任意の左辺値である必要があります。つまり、CのCRMバージョンでは、次のようなコードを記述できます。
struct S { int a, b; };
struct T { float x, y, z; };
struct T c;
c.b = 55;
この場合、型にはという名前のフィールドがなくても、コンパイラーはと呼ばれる連続メモリブロックのバイトオフセット2に位置55
するint
値に書き込みます。コンパイラは実際のタイプをまったく気にしません。それが気にしたのはそれが左辺値でした:ある種の書き込み可能なメモリブロックです。c
struct T
b
c
c
これを行った場合、
S *s;
...
s.b = 42;
コードは有効であると見なされ(これs
も左辺値であるため)、コンパイラは単純にバイトオフセット2でデータをポインタs
自体に書き込もうとします。言うまでもなく、このようなことは簡単にメモリオーバーランを引き起こす可能性がありますが、言語そのような事柄には関心がありませんでした。
つまり、そのバージョンの言語では、.
ポインタ型の演算子のオーバーロードについて提案したアイデアは機能しません。演算子.
は、ポインタ(左辺値ポインタまたは任意の左辺値)と一緒に使用すると、非常に特定の意味をすでに持っていました。それは間違いなく非常に奇妙な機能でした。しかし、当時はそこにありました。
もちろん、この奇妙な機能は.
、C-K&R Cのリワークバージョンにポインタのオーバーロードされた演算子を導入することに対して非常に強い理由ではありません。しかし、それは行われていません。たぶん当時は、CバージョンのCRMで記述されたレガシーコードがいくつかサポートされていたはずです。
(1975 CリファレンスマニュアルのURLは安定していない場合があります。微妙な違いがあると思われる別のコピーがここにあります。)