Zinterpretuję twoje pytanie jako dwa pytania: 1) dlaczego w ->
ogóle istnieje, i 2) dlaczego .
nie wyłacza automatycznie wskaźnika. Odpowiedzi na oba pytania mają historyczne korzenie.
Dlaczego w ->
ogóle istnieje?
W jednej z pierwszych wersji języka C (który będę określał jako CRM dla „ C Reference Manual ”, który pojawił się wraz z 6. edycją Unix w maju 1975 r.), Operator ->
miał bardzo ekskluzywne znaczenie, nie był synonimem *
i .
kombinacją
Język C opisany przez CRM pod wieloma względami bardzo różnił się od współczesnego C. W CRM struct członkowie zaimplementowali globalną koncepcję przesunięcia bajtów , którą można dodać do dowolnej wartości adresu bez ograniczeń typu. To znaczy wszystkie nazwiska wszystkich członków struktury miały niezależne znaczenie globalne (i dlatego musiały być unikalne). Na przykład możesz zadeklarować
struct S {
int a;
int b;
};
a nazwa a
oznaczałaby przesunięcie 0, a nazwa b
oznaczałaby przesunięcie 2 (przy założeniu, że int
rozmiar 2 nie ma wypełnienia). Język wymagał, aby wszyscy członkowie wszystkich struktur w jednostce tłumaczenia posiadali unikalne nazwy lub oznaczali tę samą wartość przesunięcia. Np. W tej samej jednostce tłumaczeniowej, którą można dodatkowo zadeklarować
struct X {
int a;
int x;
};
i to by było OK, ponieważ nazwa a
konsekwentnie oznaczałaby offset 0. Ale ta dodatkowa deklaracja
struct Y {
int b;
int a;
};
byłby formalnie nieważny, ponieważ próbował „przedefiniować” a
jako przesunięcie 2 i b
jako przesunięcie 0.
I tu pojawia się ->
operator. Ponieważ nazwa każdego członka struktury ma swoje własne samowystarczalne znaczenie globalne, język obsługiwał takie wyrażenia jak te
int i = 5;
i->b = 42; /* Write 42 into `int` at address 7 */
100->a = 0; /* Write 0 into `int` at address 100 */
Pierwszy przydział został zinterpretowany przez kompilator jako „adres odbioru 5
, dodać przesunięcie 2
do niego i przypisać 42
do int
wartości uzyskanej w adresie”. Czyli powyżej byłoby przypisać 42
do int
wartości w adresie 7
. Zauważ, że to użycie ->
nie dbało o rodzaj wyrażenia po lewej stronie. Lewa strona została zinterpretowana jako adres numeryczny wartości (może to być wskaźnik lub liczba całkowita).
Tego rodzaju oszustwo nie było możliwe przy użyciu *
i .
kombinacji. Nie mogłeś zrobić
(*i).b = 42;
ponieważ *i
jest już niepoprawnym wyrażeniem. *
Operatora, ponieważ jest oddzielone od .
nakłada bardziej rygorystyczne wymogi pisania na jej argumentu. Aby zapewnić możliwość obejścia tego ograniczenia, CRM wprowadził ->
operatora, który jest niezależny od rodzaju operandu po lewej stronie.
Jak zauważył Keith w komentarzach, ta różnica między kombinacją ->
a *
+ .
jest tym, co CRM nazywa „rozluźnieniem wymogu” w 7.1.8: Z wyjątkiem rozluźnienia wymogu E1
typu wskaźnikowego, wyrażenie E1−>MOS
jest dokładnie równoważne z(*E1).MOS
Później w K&R C wiele funkcji pierwotnie opisanych w CRM zostało znacznie przerobionych. Idea „struktury członka jako globalnego identyfikatora przesunięcia” została całkowicie usunięta. A funkcjonalność ->
operatora stała się w pełni identyczna z funkcjonalnością *
i .
kombinacją.
Dlaczego nie można .
wyrejestrować wskaźnika automatycznie?
Ponownie, w wersji CRM języka lewy argument z .
operatorem musiała być lwartość . To był jedyny wymóg nałożony na ten operand (i to go odróżniało ->
, jak wyjaśniono powyżej). Zauważ, że CRM nie wymagał, aby lewy operand .
miał typ struktury. Wymagało tylko, aby była to wartość, każda wartość. Oznacza to, że w CRM w wersji C można napisać taki kod
struct S { int a, b; };
struct T { float x, y, z; };
struct T c;
c.b = 55;
W tym przypadku kompilator będzie pisać 55
do o int
wartości umieszczonej w bajcie offsetu 2 w ciągłym bloku pamięci znanego jako c
, choć typ struct T
nie miał pole o nazwie b
. Kompilator nie przejmuje się c
w ogóle faktycznym typem . Chodziło tylko o to, żeby c
była to wartość: jakiś zapisywalny blok pamięci.
Teraz zauważ, że jeśli to zrobiłeś
S *s;
...
s.b = 42;
kod będzie uznane za ważne (ponieważ s
jest również lwartością) oraz kompilator po prostu próba zapisu danych do wskaźnika s
samego , co bajt przesunięcie 2. Oczywiście, takie rzeczy mogą łatwo spowodować przekroczenie pamięci, ale język nie zajmował się takimi sprawami.
Tj. W tej wersji języka twój pomysł na przeciążanie operatora .
dla typów wskaźników nie zadziałałby: operator .
miał już bardzo konkretne znaczenie, gdy był używany ze wskaźnikami (ze wskaźnikami wartości lub w ogóle wartościami). To była bardzo dziwna funkcjonalność, bez wątpienia. Ale to było wtedy.
Oczywiście, ta dziwna funkcjonalność nie jest bardzo silnym powodem, aby nie wprowadzać przeciążonego .
operatora wskaźników (jak sugerowałeś) w przerobionej wersji C - K&R C. Ale tego nie zrobiono. Być może w tym czasie był napisany jakiś starszy kod w CRM w wersji C, który musiał być obsługiwany.
(Adres do 1975 C Instrukcja referencyjnych nie może być stabilna. Innym kopii, ewentualnie z pewnymi subtelnych różnic jest tutaj ).