Dlaczego operator arrow ( - > ) w C istnieje?

Operator dot (.) jest używany do uzyskania dostępu do elementu struktury, podczas gdy operator strzałki (->) W C jest używany do uzyskania dostępu do elementu struktury, do którego odnosi się dany wskaźnik.

Sam wskaźnik nie ma żadnych członków, do których można uzyskać dostęp za pomocą operatora kropki (w rzeczywistości jest to tylko liczba opisująca lokalizację w pamięci wirtualnej, więc nie ma żadnych członków). Nie byłoby więc niejednoznaczności, gdybyśmy zdefiniowali operator kropki na automatycznie usuwa wskaźnik, jeśli jest on używany na wskaźniku (informacja, która jest znana kompilatorowi w czasie kompilacji afaik).

Dlaczego twórcy języka postanowili skomplikować sprawę dodając ten pozornie niepotrzebny operator? Co to jest duża decyzja projektowa?

Author: Yu Hao, 2012-11-13

3 answers

Zinterpretuję twoje pytanie jako dwa pytania: 1) Dlaczego -> w ogóle istnieje, oraz 2) Dlaczego . nie zmienia automatycznie wskaźnika. Odpowiedzi na oba pytania mają korzenie historyczne.

Dlaczego w ogóle istnieje?

W jednej z pierwszych wersji języka C (który będę nazywać CRM dla "C Reference Manual ", który przyszedł z 6th Edition Unix w maju 1975 roku), operator -> miał bardzo ekskluzywne znaczenie, nie jest synonimem * i . kombinacja

Język C opisany przez CRM bardzo różnił się od współczesnego C pod wieloma względami. W CRM członkowie struct zaimplementowali globalną koncepcję przesunięcia bajtów , która może być dodana do dowolnej wartości adresu bez ograniczeń typu. Tzn. wszystkie nazwy wszystkich członków struct miały niezależne globalne znaczenie (a zatem musiały być unikalne). Na przykład możesz zadeklarować

struct S {
  int a;
  int b;
};

I nazwa a oznaczałaby offset 0, natomiast nazwa b oznaczałaby przesunięcie 2 (przy założeniu int typu rozmiaru 2 i braku wypełnienia). Język wymagał, aby wszystkie elementy składowe wszystkich struktur w jednostce tłumaczeniowej miały unikalne nazwy lub oznaczały tę samą wartość offsetu. Np. w tej samej jednostce tłumaczeniowej można dodatkowo zadeklarować

struct X {
  int a;
  int x;
};

I byłoby to w porządku, ponieważ nazwa a konsekwentnie oznacza offset 0. Ale ta dodatkowa deklaracja

struct Y {
  int b;
  int a;
};

Byłoby formalnie nieważne, ponieważ próbowało "przedefiniować" a jako offset 2 i b jako offset 0.

I tu wchodzi operator ->. Ponieważ każda nazwa elementu struct miała swoje własne, samowystarczalne globalne znaczenie, język obsługiwał wyrażenia takie jak:]}

int i = 5;
i->b = 42;  /* Write 42 into `int` at address 7 */
100->a = 0; /* Write 0 into `int` at address 100 */

Pierwsze przypisanie zostało zinterpretowane przez kompilator jako "pobranie adresu 5, dodanie offsetu 2 do niego i przypisanie 42 do wartości int pod adresem wynikowym". Np. powyższe przypisywałoby 42 do int wartość pod adresem 7. Zauważ, że to użycie -> nie obchodziło o rodzaju wyrażenia po lewej stronie. Lewa strona została zinterpretowana jako adres liczbowy rvalue (czy to wskaźnik, czy liczba całkowita).

Tego rodzaju oszustwa nie były możliwe przy kombinacji * i .. You could not do

(*i).b = 42;

Ponieważ *i jest już niepoprawnym wyrażeniem. Operator *, ponieważ jest oddzielny od ., nakłada na swój operand bardziej rygorystyczne wymagania dotyczące typu. Aby zapewnić możliwość pracy wokół tego ograniczenia CRM wprowadzono operator ->, który jest niezależny od typu lewego operanda.

Jak zauważył Keith w komentarzach, ta różnica między -> a *+. combination jest tym, co CRM określa jako "rozluźnienie wymogu" w 7.1.8: Z Wyjątkiem rozluźnienia wymogu, który E1 jest typu wskaźnika, wyrażenie {[38] } jest dokładnie równoważne (*E1).MOS

Później w K&R c wiele funkcji pierwotnie opisanych w CRM było znacząco przerobione. Idea "struct member as global offset identifier" została całkowicie usunięta. Funkcjonalność operatora -> stała się w pełni identyczna z funkcjonalnością kombinacji * i ..

Dlaczego . nie może automatycznie odczytać wskaźnika?

Ponownie, w wersji języka CRM lewym operandem operatora . był wymagany lvalue . To byłtylko wymóg narzucony temu operandowi (i to właśnie odróżniało go od ->, jak wyjaśniono powyżej). Należy zauważyć, że CRM Nie wymagał, aby lewy operand . miał Typ struct. Wymagało to tylko, aby był to lvalue, dowolny lvalue. Oznacza to, że w CRM w wersji C można napisać kod w ten sposób

struct S { int a, b; };
struct T { float x, y, z; };

struct T c;
c.b = 55;

W tym przypadku kompilator zapisywałby 55 do wartości int umieszczonej w offsecie bajtów 2 w bloku pamięci ciągłej znanym jako c, nawet jeśli Typ struct T nie miał pola o nazwie b. Kompilator w ogóle nie dba o rzeczywisty typ c. Dbał tylko o to, że c był lvalue: jakimś zapisywalnym blokiem pamięci.

Teraz zauważ, że jeśli to zrobiłeś

S *s;
...
s.b = 42;

Kod byłby uważany za poprawny (ponieważ s jest również lvalue), a kompilator po prostu spróbowałby zapisać dane do wskaźnika s samego , przy przesunięciu bajtów 2. Nie trzeba dodawać, że takie rzeczy mogą łatwo spowodować przekroczenie pamięci, ale język nie zajmował się takimi sprawami.

Tzn. w tej wersji języka zaproponowany przez Ciebie pomysł na przeciążenie operatora . dla typów wskaźników nie zadziała: operator . miał już bardzo specyficzne znaczenie, gdy był używany ze wskaźnikami(ze wskaźnikami lvalue lub z dowolnymi wskaźnikami lvalue). To była bardzo dziwna funkcjonalność, bez wątpienia. Ale to było tam w tym czasie.

Oczywiście ta dziwna funkcjonalność nie jest zbyt mocnym powodem przeciwko wprowadzeniu przeciążonego operatora . dla Wskaźników (jak sugerowałeś) w przerobionej wersji C-K&R C. Ale nie zostało to zrobione. Być może w tym czasie nie było jakieś legacy kod napisany w CRM wersji C, który musiał być obsługiwany.

(adres URL podręcznika z 1975 roku może nie być stabilny. Inną kopią, prawdopodobnie z pewnymi subtelnymi różnicami, jest tutaj .)

 375
Author: AnT,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-03 16:32:54

Poza historycznymi (dobrymi i już zgłoszonymi) przyczynami, istnieje również mały problem z pierwszeństwem operatorów: operator kropki ma wyższy priorytet niż operator Gwiazdy, więc jeśli masz strukturę zawierającą wskaźnik do struktury zawierającą wskaźnik do struktury... Te dwa są równoważne:

(*(*(*a).b).c).d

a->b->c->d

Ale drugi jest wyraźnie bardziej czytelny. Operator strzałki ma najwyższy priorytet (podobnie jak kropka) i od lewej do prawej. Myślę, że jest to jaśniejsze niż użycie operatora dot zarówno dla wskaźników do struct i struct, ponieważ znamy typ z wyrażenia bez konieczności patrzenia na deklarację, która może być nawet w innym pliku.

 49
Author: effeffe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-21 13:28:09

C robi również dobrą robotę, nie czyniąc niczego dwuznacznego.

Jasne, że kropka może być przeciążona, aby oznaczać obie rzeczy, ale strzałka upewnia się, że programista wie, że działa na wskaźniku, tak jak wtedy, gdy kompilator nie pozwala mieszać dwóch niekompatybilnych typów.

 19
Author: mukunda,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-03 03:58:49