Der dot ( .
) -Operator wird verwendet, um auf ein Mitglied einer Struktur zuzugreifen, während der Pfeiloperator ( ->
) in C verwendet wird, um auf ein Mitglied einer Struktur zuzugreifen, auf das der betreffende Zeiger verweist.
Der Zeiger selbst hat keine Mitglieder, auf die mit dem Punktoperator zugegriffen werden kann (es ist eigentlich nur eine Zahl, die einen Ort im virtuellen Speicher beschreibt, also hat er keine Mitglieder). Es würde also keine Mehrdeutigkeit geben, wenn wir nur den Punktoperator definieren würden, um den Zeiger automatisch zu dereferenzieren, wenn er für einen Zeiger verwendet wird (eine Information, die dem Compiler zur Kompilierungszeit bekannt ist).
Warum haben die Sprachschöpfer beschlossen, die Dinge durch Hinzufügen dieses scheinbar unnötigen Operators komplizierter zu gestalten? Was ist die große Designentscheidung?
quelle
.
Operator eine höhere Priorität als der*
Operator? Wenn nicht, könnten wir * ptr.member und var.member haben.Antworten:
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.
KombinationDie 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
und name
a
würde für Offset 0 stehen, während nameb
für Offset 2 stehen würde (unterint
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 deklarierenund das wäre OK, da der Name
a
durchweg für Offset 0 stehen würde. Aber diese zusätzliche Deklarationwäre formal ungültig, da es versucht hat, "neu zu definieren"
a
als Offset 2 undb
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ückeDie erste Zuweisung wurde vom Compiler als "Adresse übernehmen
5
, Offset hinzufügen2
und42
demint
Wert an der resultierenden Adresse zuweisen " interpretiert . Dh die oben würde zuweisen42
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 machenda
*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önnenIn diesem Fall würde der Compiler
55
in einenint
Wert schreiben, der am Byte-Offset 2 im kontinuierlichen Speicherblock positioniert istc
, obwohl bekannt ist , obwohl der Typstruct T
kein Feld benannt hatb
. Der Compiler würde sich überhaupt nicht um den tatsächlichen Typ kümmernc
. 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
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 .)
quelle
*i
an Adresse 5 kein Wert eines Standardtyps (int?)? Dann hätte (* i) .b genauso funktioniert.struct stat
) ihren Feldern (zst_mode
. B. ) ein Präfix voranstellen .Abgesehen von historischen (guten und bereits gemeldeten) Gründen gibt es auch ein kleines Problem mit der Priorität von Operatoren: Der Punktoperator hat eine höhere Priorität als der Sternoperator. Wenn Sie also eine Struktur haben, die Zeiger auf Struktur enthält, die Zeiger auf Struktur enthält ... Diese beiden sind äquivalent:
Aber der zweite ist deutlich besser lesbar. Der Pfeiloperator hat die höchste Priorität (genau wie der Punkt) und wird von links nach rechts zugeordnet. Ich denke, dies ist klarer als die Verwendung des Punktoperators sowohl für Zeiger auf struct als auch auf struct, da wir den Typ aus dem Ausdruck kennen, ohne auf die Deklaration achten zu müssen, die sich sogar in einer anderen Datei befinden könnte.
quelle
a.b.c.d
als interpretiert werden könnte(*(*(*a).b).c).d
, was den->
Operator unbrauchbar macht. Die Version (a.b.c.d
) des OP ist also gleichermaßen lesbar (im Vergleich zua->b->c->d
). Deshalb beantwortet Ihre Antwort die Frage des OP nicht.a.b.c.d
unda->b->c->d
wie zwei sehr verschiedene Dinge: Das ist zunächst ein einzelner Speicherzugriff auf ein verschachteltes Unterobjekt (es gibt nur ein einziges Speicherobjekt in diesem Fall ), der zweite ist drei Speicherzugriffe, die Zeiger durch vier wahrscheinlich unterschiedliche Objekte verfolgen. Das ist ein großer Unterschied im Speicherlayout, und ich glaube, dass C richtig ist, um diese beiden Fälle sehr sichtbar zu unterscheiden.C macht auch einen guten Job darin, nichts mehrdeutig zu machen.
Sicher, der Punkt könnte überladen sein, um beides zu bedeuten, aber der Pfeil stellt sicher, dass der Programmierer weiß, dass er mit einem Zeiger arbeitet, genau wie wenn der Compiler nicht zulässt, dass Sie zwei inkompatible Typen mischen.
quelle