Ich werde Ihre Frage als zwei Fragen interpretieren: 1) warum ->
überhaupt existiert und 2) warum .
der Zeiger nicht automatisch dereferenziert wird. Die Antworten auf beide Fragen haben historische Wurzeln.
Warum gibt ->
es überhaupt?
In einer der allerersten Versionen der C-Sprache (die ich als CRM bezeichnen werde für " C Reference Manual " bezeichnen werde, das im Mai 1975 mit der 6. Ausgabe von Unix geliefert wurde) hatte der Operator ->
eine sehr exklusive Bedeutung, nicht gleichbedeutend mit *
und .
Kombination
Die von CRM beschriebene C-Sprache unterschied sich in vielerlei Hinsicht stark von der modernen C. In CRM haben Strukturmitglieder das globale Konzept von implementiert Byte-Offsets , das ohne Typeinschränkungen zu jedem Adresswert hinzugefügt werden kann. Das heißt, alle Namen aller Strukturmitglieder hatten eine unabhängige globale Bedeutung (und mussten daher eindeutig sein). Zum Beispiel könnten Sie deklarieren
struct S {
int a;
int b;
};
und name a
würde für Offset 0 stehen, während nameb
für Offset 2 stehen würde (unter int
der Annahme, dass Typ 2 und keine Polsterung vorhanden sind). Die Sprache erfordert, dass alle Mitglieder aller Strukturen in der Übersetzungseinheit entweder eindeutige Namen haben oder für denselben Versatzwert stehen. ZB in derselben Übersetzungseinheit könnten Sie zusätzlich deklarieren
struct X {
int a;
int x;
};
und das wäre OK, da der Name a
durchweg für Offset 0 stehen würde. Aber diese zusätzliche Deklaration
struct Y {
int b;
int a;
};
wäre formal ungültig, da es versucht hat, "neu zu definieren" a
als Offset 2 und b
als Offset 0 .
Und hier ist die ->
Operator ins Spiel. Da jeder Name eines Strukturmitglieds eine eigene autarke globale Bedeutung hatte, unterstützte die Sprache solche Ausdrücke
int i = 5;
i->b = 42; /* Write 42 into `int` at address 7 */
100->a = 0; /* Write 0 into `int` at address 100 */
Die erste Zuweisung wurde vom Compiler als "Adresse übernehmen 5
, Offset hinzufügen 2
und 42
dem int
Wert an der resultierenden Adresse zuweisen " interpretiert . Dh die oben würde zuweisen 42
zuint
Wert an der Adresse7
. Beachten Sie, dass sich diese Verwendung von ->
nicht um den Typ des Ausdrucks auf der linken Seite kümmerte. Die linke Seite wurde als numerische Adresse mit r-Wert interpretiert (sei es ein Zeiger oder eine ganze Zahl).
Diese Art von Betrug war mit *
und nicht möglich.
Kombination . Das kannst du nicht machen
(*i).b = 42;
da *i
ist schon ein ungültiger Ausdruck. Das*
Operator stellt, da er von diesem getrennt ist .
, strengere Typanforderungen an seinen Operanden. Um diese Einschränkung umgehen zu können, hat CRM den ->
Operator eingeführt, der unabhängig vom Typ des linken Operanden ist.
Wie Keith in den Kommentaren feststellte, wird dieser Unterschied zwischen ->
und *
+ .
Kombination von CRM in 7.1.8 als "Lockerung der Anforderung" bezeichnet: Mit Ausnahme der Lockerung der Anforderung, die vom Zeigertyp ist , der AusdruckE1
E1−>MOS
genau äquivalent zu(*E1).MOS
Später wurden in K & R C viele ursprünglich in CRM beschriebene Funktionen erheblich überarbeitet. Die Idee von "Strukturelement als globaler Versatzbezeichner" wurde vollständig entfernt. Und die Funktionalität des ->
Bedieners wurde vollständig identisch mit der Funktionalität *
und .
Kombination.
Warum kann .
der Zeiger nicht automatisch dereferenziert werden?
Auch in der CRM-Version der Sprache musste der linke Operand des .
Operators ein Wert sein . Dies war die einzige Anforderung, die an diesen Operanden gestellt wurde (und das machte ihn anders ->
als oben erläutert). Beachten Sie, dass CRM hat nicht erfordern den linken Operanden von .
einem Strukturtyp zu haben. Es musste nur ein Wert sein, ein beliebiger Wert. Dies bedeutet, dass Sie in der CRM-Version von C Code wie diesen schreiben können
struct S { int a, b; };
struct T { float x, y, z; };
struct T c;
c.b = 55;
In diesem Fall würde der Compiler 55
in einen int
Wert schreiben, der am Byte-Offset 2 im kontinuierlichen Speicherblock positioniert ist c
, obwohl bekannt ist , obwohl der Typ struct T
kein Feld benannt hat b
. Der Compiler würde sich überhaupt nicht um den tatsächlichen Typ kümmern c
. Alles, was es interessierte, ist dasc
war, es sich um einen Wert handelte: eine Art beschreibbaren Speicherblock.
Beachten Sie nun, dass Sie dies getan haben
S *s;
...
s.b = 42;
Der Code wird als gültig angesehen (da er s
auch ein Wert ist) und der Compiler würde einfach versuchen, Daten in den Zeiger selbst zu schreibens
mit Byte-Offset 2 . Unnötig zu Dinge leicht zu einem Speicherüberlauf führen können, aber die Sprache beschäftigte sich nicht mit solchen Angelegenheiten.
Dh in dieser Version der Sprache würde Ihre vorgeschlagene Idee zum Überladen von Operatoren .
für Zeigertypen nicht funktionieren: Operator.
Zeigertypen bereits eine sehr spezifische Bedeutung, wenn sie mit Zeigern (mit l-Wert-Zeigern oder mit irgendwelchen l-Werten überhaupt) verwendet wurden. Es war zweifellos eine sehr seltsame Funktionalität. Aber es war zu der Zeit da.
Natürlich ist diese seltsame Funktionalität kein sehr starker Grund gegen die Einführung eines überladenen .
Operators für Zeiger (wie Sie vorgeschlagen haben) in der überarbeiteten Version von C - K & R C. Aber es wurde nicht getan. Vielleicht gab es zu dieser Zeit einen alten Code in der CRM-Version von C, der unterstützt werden musste.
(Die URL für das C-Referenzhandbuch von 1975 ist möglicherweise nicht stabil. Eine weitere Kopie, möglicherweise mit geringfügigen Unterschieden, befindet sich hier .)