[解決済み】C言語の矢印(->)演算子はなぜ存在するのですか?
質問
ドット(
.
) 演算子は構造体のメンバにアクセスするために使用され、矢印演算子 (
->
C言語では、当該ポインタが参照する構造体のメンバにアクセスするために使用されます。
ポインタ自身はドット演算子でアクセスできるようなメンバを持っていません(実際には仮想メモリ上の位置を表す数値に過ぎないので、メンバは持っていません)。つまり、ポインタに対してドット演算子が使われた場合、自動的にポインタの参照が解除されるように定義すれば、曖昧さはなくなります(コンパイラがコンパイル時に知っている情報です)。
では、なぜ言語開発者は、この一見不要な演算子を追加して、物事をより複雑にすることにしたのでしょうか?設計上の大きな判断は何なのだろうか?
どのように解決するのか?
ご質問を2つの質問に分けて解釈します。1) なぜ
->
が存在する理由と、2)なぜ
.
は自動的にポインタの参照を解除しない。この2つの疑問に対する答えは、歴史的なルーツにある。
なぜ
->
は存在するのでしょうか?
C言語の最初のバージョンの1つ(ここでは「"」のCRMと呼ぶことにします。
C言語リファレンスマニュアル
1975年5月に第6版Unixに付属していた ")、演算子
->
とは同義ではなく、非常に排他的な意味を持っていました。
*
と
.
組み合わせ
CRMで記述されたC言語は、現代のC言語とは多くの点で大きく異なっていた。CRMでは、構造体のメンバは、グローバルな概念である バイトオフセット このアドレスは、型の制約を受けずに、どのようなアドレス値にも付加することができる。つまり、すべての構造体メンバの名前は、独立したグローバルな意味を持っていました(したがって、一意でなければなりませんでした)。例えば、次のように宣言できます。
struct S {
int a;
int b;
};
と名前
a
はオフセット0を表し、name
b
はオフセット2を表します(仮に
int
型がサイズ2でパディングなし)。この言語では、翻訳ユニット内のすべての構造体のすべてのメンバが一意の名前を持つか、同じオフセット値を表すことが要求されます。たとえば、同じ翻訳ユニット内で、さらに次のように宣言できます。
struct X {
int a;
int x;
};
という名前なので、それでOKでしょう。
a
は一貫してオフセット 0 を表します。 しかし、この追加宣言は
struct Y {
int b;
int a;
};
は形式的に無効である。なぜなら、これは "redefine" を試みたからである。
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
. なお、この
->
は、左辺の式の型を気にしませんでした。左辺はrvalueの数値アドレス(ポインタであれ整数であれ)として解釈された。
このようなトリックが可能なのは
*
と
.
の組み合わせになります。を行うことはできませんでした。
(*i).b = 42;
から
*i
はすでに無効な表現です。そのため
*
演算子とは別のものであるため
.
このため、オペランドにはより厳しい型要求が課せられます。この制限を回避する機能を提供するために,CRMは
->
演算子で、左側のオペランドの型に依存しない。
Keithがコメントで指摘しているように、この
->
と
*
+
.
の組み合わせは、CRMが7.1.8で言うところの「要求事項の緩和」である。
という要件が緩和された以外は
E1
はポインタ型であるため、式
E1−>MOS
とは全く同じ意味です。
(*E1).MOS
その後、K&R Cでは、CRMで説明した機能の多くが大幅に作り直された。構造体メンバをグローバルオフセット識別子とする考え方は、完全に削除されました。また
->
演算子の機能と完全に同一になりました。
*
と
.
の組み合わせになります。
なぜ
.
は自動的にポインタを参照するのですか?
繰り返しになりますが、CRMバージョンの言語では、左オペランドが
.
演算子は
値
. それが
だけ
という要件がそのオペランドに課せられています(そして、それが
->
ということである。) なお、CRMは
ではなく
の左オペランドを必要とします。
.
は構造体型である必要があります。それは単にlvalueであることを要求しているだけです。
任意の
lvalueを使用します。つまり、CRMバージョンのC言語では、次のようなコードを書くことができます。
struct S { int a, b; };
struct T { float x, y, z; };
struct T c;
c.b = 55;
この場合、コンパイラは次のように書きます。
55
を
int
という連続したメモリブロックのバイトオフセット2に位置する値です。
c
であるにもかかわらず、タイプ
struct T
という名前のフィールドはありませんでした。
b
. コンパイラは、実際の型である
c
を使用します。気になるのは
c
はlvalueであり、書き込み可能なメモリブロックの一種である。
ここで、もしあなたがこのようなことをした場合に注意してください。
S *s;
...
s.b = 42;
の場合、コードは有効であるとみなされます。
s
は lvalue でもあるため、コンパイラは単にデータ
をポインタ
s
それ自体
バイトオフセット 2 である。言うまでもなく、このようなことは簡単にメモリオーバーになるのだが、この言語はそのようなことには無頓着であった。
つまり、そのバージョンの言語では、あなたの提案した演算子のオーバーロードに関するアイデアは
.
はポインタ型には使えません。
.
は、ポインタ(lvalue ポインタまたは任意の lvalue)で使用される場合、すでに非常に特殊な意味を持っていました。これは非常に奇妙な機能であることは間違いありません。しかし、当時はそれがあったのです。
もちろん、この奇妙な機能は、オーバーロードされた
.
演算子でポインタを指定できるようにすることを提案しました。たぶん当時はCRMバージョンのCで書かれたレガシーコードがあり、それをサポートする必要があったのでしょう。
(1975年版C言語リファレンスマニュアルのURLは安定していない可能性があります。別のコピー(おそらく微妙な違いがある)は、次のとおりです。 こちら .)
関連
-
[解決済み】Valgrind が "Invalid write of size 8" で文句を言う。
-
[解決済み] [Solved] なぜこのようなエラーが発生するのでしょうか。「データ定義に型またはストレージクラスがない」?
-
[解決済み】MPI通信でMPI_Bcastを使用する場合
-
[解決済み] mallocの結果はキャストするのですか?
-
[解決済み] C言語では「?」演算子は何をするのですか?
-
[解決済み] なぜ、オブジェクトそのものではなく、ポインタを使用しなければならないのですか?
-
[解決済み] 配列の場合、なぜ a[5] == 5[a] になるのでしょうか?
-
[解決済み] const int*、const int * const、int const *の違いは何ですか?
-
[解決済み] ポインタの「デリファレンス」とはどういう意味ですか?
-
[解決済み】C/C++の"-->"演算子とは何ですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】stdinとSTDIN_FILENOの違いは何ですか?
-
[解決済み】エラー:cの入力の最後に期待される宣言またはステートメント
-
[解決済み】エラー:'for'ループの初期宣言はC99モードでしかできない【重複
-
[解決済み】デバッガgdbの使用時に不明な終了シグナルが発生する。
-
[解決済み】argv[]をint型として取得するには?
-
[解決済み] エラー:整数が期待されるところで集約値が使用された
-
[解決済み】MPI通信でMPI_Bcastを使用する場合
-
[解決済み] テスト
-
[解決済み】makefile:4。*** missing separator. 停止する
-
[解決済み】シンプルなC言語のscanfが機能しない?重複