Wydaje się, że nastąpiła stopniowa zmiana myślenia o stosowaniu wskaźników w językach programowania, tak że ogólnie przyjęto, że wskaźniki są uważane za ryzykowne (jeśli nie wręcz „złe” lub podobne powiększenie).
Jakie były wydarzenia historyczne dla tej zmiany myślenia? Czy były jakieś szczególne, przełomowe wydarzenia, badania lub inne wydarzenia?
Na przykład powierzchowne spojrzenie na przejście z C do C ++ na Javę wydaje się wykazywać tendencję do uzupełniania, a następnie całkowitego zastępowania wskaźników referencjami. Jednak prawdziwy łańcuch wydarzeń był prawdopodobnie o wiele bardziej subtelny i złożony niż ten, i nie tak bardzo sekwencyjny. Funkcje, które przeszły na te języki głównego nurtu, mogły powstać gdzie indziej, być może na długo wcześniej.
Uwaga: nie pytam o rzeczywiste zalety wskaźników vs. referencji vs. czegoś innego. Skupiam się na uzasadnieniu tej pozornej zmiany.
Odpowiedzi:
Uzasadnieniem było opracowanie alternatyw dla wskaźników.
Pod maską każdy wskaźnik / odnośnik / itp. Jest implementowany jako liczba całkowita zawierająca adres pamięci (inaczej wskaźnik). Kiedy pojawiło się C , ta funkcjonalność została ujawniona jako wskaźniki. Oznaczało to, że wszystko, co podstawowy sprzęt może zrobić, aby zająć się pamięcią, można wykonać za pomocą wskaźników.
To zawsze było „niebezpieczne”, ale niebezpieczeństwo jest względne. Podczas tworzenia programu na 1000 linii lub gdy stosowane są procedury jakości oprogramowania klasy IBM, można łatwo rozwiązać to niebezpieczeństwo. Jednak nie całe oprogramowanie było opracowywane w ten sposób. Jako takie pojawiło się pragnienie prostszych struktur.
Jeśli się nad tym zastanowić, to
int&
iint* const
naprawdę mają ten sam poziom bezpieczeństwa, ale jeden ma znacznie ładniejszą składnię niż drugi.int&
może być również bardziej wydajny, ponieważ może odnosić się do int zapisanego w rejestrze (anachronizm: tak było w przeszłości, ale nowoczesne kompilatory są tak dobre w optymalizacji, że możesz mieć wskaźnik do liczby całkowitej w rejestrze, o ile nigdy nie korzystasz z żadnej funkcji wymagającej rzeczywistego adresu, np.++
)Przechodząc na Javę , przechodzimy na języki, które dają pewne gwarancje bezpieczeństwa. C i C ++ pod warunkiem brak. Java gwarantuje, że wykonywane będą tylko czynności prawne. Aby to zrobić, Java całkowicie pozbyła się wskaźników. Odkryli, że zdecydowana większość operacji wskaźnika / referencji wykonywanych w prawdziwym kodzie była rzeczą, do której referencje były więcej niż wystarczające. Tylko w kilku przypadkach (takich jak szybka iteracja po tablicy) wskaźniki były naprawdę potrzebne. W takich przypadkach Java wykonuje działanie środowiska wykonawczego, aby uniknąć ich użycia.
Ten ruch nie był monotoniczny. C # ponownie wprowadził wskaźniki, choć w bardzo ograniczonej formie. Są oznaczone jako „ niebezpieczne ”, co oznacza, że nie mogą być używane przez niezaufany kod. Mają też wyraźne zasady dotyczące tego, co można i nie można wskazać (na przykład, to po prostu nieważny , aby zwiększyć wskaźnik poza końcem tablicy). Okazało się jednak, że było kilka przypadków, w których potrzebna była wysoka wydajność wskaźników, więc włożyli je z powrotem.
Interesujące byłyby również języki funkcjonalne, które w ogóle nie mają takiej koncepcji, ale to zupełnie inna dyskusja.
źródło
Pewna pośrednia potrzeba jest w przypadku złożonych programów (np. Struktury danych rekurencyjnych lub zmiennych wielkości). Jednak nie jest konieczne wdrażanie tej pośredniczości za pomocą wskaźników.
Większość języków programowania wysokiego poziomu (tj. Nie asemblerowych) jest dość bezpieczna dla pamięci i uniemożliwia nieograniczony dostęp do wskaźnika. Rodzina C jest tutaj dziwna.
C ewoluowało z B, co było bardzo cienką abstrakcją w stosunku do surowego zestawu. B miał jeden typ: słowo. Słowo może być użyte jako liczba całkowita lub jako wskaźnik. Te dwa są równoważne, gdy cała pamięć jest postrzegana jako pojedyncza ciągła tablica. C zachował to dość elastyczne podejście i nadal wspierał z natury niebezpieczną arytmetykę wskaźników. Cały system typu C jest bardziej przemyślany. Ta elastyczność dostępu do pamięci sprawiła, że C jest bardzo odpowiedni do jego podstawowego celu: prototypowania systemu operacyjnego Unix. Oczywiście Unix i C okazały się dość popularne, dlatego C jest również stosowany w aplikacjach, w których takie podejście do pamięci na niskim poziomie nie jest tak naprawdę potrzebne.
Jeśli spojrzymy na języki programowania występujące przed C (np. Dialekty Fortran, Algol, w tym Pascal, Cobol, Lisp,…), niektóre z nich obsługują wskaźniki podobne do C. Warto zauważyć, że koncepcja zerowego wskaźnika została wynaleziona dla Algola W w 1965 roku. Jednak żaden z tych języków nie próbował być językiem C, wydajnym językiem systemów o niskiej abstrakcji: Fortran był przeznaczony do obliczeń naukowych, Algol opracował kilka dość zaawansowanych koncepcji, Lisp był bardziej projekt badawczy niż język klasy przemysłowej, a Cobol koncentrował się na aplikacjach biznesowych.
Wywóz śmieci istniał od późnych lat 50., tj. Na długo przed C (wczesne lata 70.) GC wymaga bezpieczeństwa pamięci do prawidłowego działania. Języki przed i po C używały GC jako normalnej funkcji. Oczywiście sprawia to, że język jest znacznie bardziej skomplikowany i być może wolniejszy, co było szczególnie zauważalne w czasach komputerów mainframe. Języki GC były zorientowane na badania (np. Lisp, Simula, ML) i / lub wymagały wydajnych stacji roboczych (np. Smalltalk).
Dzięki mniejszym, bardziej wydajnym komputerom ogólnie, a języki GC stały się bardziej popularne. W przypadku aplikacji nie działających w czasie rzeczywistym (a czasem nawet wtedy) GC jest teraz preferowanym podejściem. Ale algorytmy GC były również przedmiotem intensywnych badań. Alternatywnie, poprawiono także bezpieczeństwo pamięci bez GC, szczególnie w ostatnich trzech dekadach: istotnymi innowacjami są RAII i inteligentne wskaźniki w C ++ oraz system sprawdzania / pożyczania Rust przez cały okres eksploatacji.
Java nie wprowadziła innowacji, ponieważ jest bezpiecznym językiem programowania: w zasadzie wzięła semantykę języka GCed, bezpiecznego języka Smalltalk i połączyła je ze składnią i statycznym typowaniem C ++. Następnie został wprowadzony na rynek jako lepszy, prostszy C / C ++. Ale to tylko powierzchownie potomek C ++. Brak wskaźników w Javie zawdzięcza znacznie bardziej obiektowemu modelowi Smalltalk niż odrzuceniu modelu danych C ++.
Tak więc „nowoczesnych” języków, takich jak Java, Ruby i C #, nie należy interpretować jako przezwyciężających problemy surowych wskaźników, takich jak w C, ale należy je postrzegać jako czerpiące z wielu tradycji - w tym C, ale także z bezpieczniejszych języków, takich jak Smalltalk, Simula, lub Lisp.
źródło
Z mojego doświadczenia wynika, że wskaźniki były ZAWSZE wyzwaniem dla wielu osób. W 1970 r. Uniwersytet, do którego uczęszczałem, miał Burroughs B5500, i wykorzystaliśmy Extended Algol do naszych projektów programistycznych. Architektura sprzętowa została oparta na deskryptorach i niektórych kodach w górnej części słów danych. Zostały one wyraźnie zaprojektowane, aby umożliwić tablicom używanie wskaźników, bez możliwości odejścia od celu.
Ożywiliśmy dyskusje w klasie na temat odniesienia nazwy i wartości oraz tego, jak działały tablice B5500. Niektórzy z nas natychmiast otrzymali wyjaśnienie. Inni nie.
Później było trochę szoku, że sprzęt nie chronił mnie przed niekontrolowanymi wskaźnikami - szczególnie w języku asemblera. W mojej pierwszej pracy po ukończeniu szkoły pomogłem naprawić problemy w systemie operacyjnym. Często jedyną dokumentacją, jaką mieliśmy, był wydrukowany zrzut awaryjny. Stworzyłem talent do znajdowania źródła niekontrolowanych wskaźników w zrzutach pamięci, więc każdy dał mi „niemożliwe” zrzuty, aby się zorientować. Więcej problemów mieliśmy z powodu błędów wskaźnika niż z jakiegokolwiek innego rodzaju błędu.
Wiele osób, z którymi pracowałem, zaczęło pisać FORTRAN, potem przeniosło się do C, napisało C, podobnie jak FORTRAN, i unikało wskazówek. Ponieważ nigdy nie internalizowały wskaźników i referencji, Java stwarza problemy. Często programiści FORTRAN mają trudności ze zrozumieniem, jak naprawdę działa przypisywanie obiektów.
Współczesne języki znacznie ułatwiły robienie rzeczy, które wymagają wskazówek „pod maską”, jednocześnie chroniąc nas przed literówkami i innymi błędami.
źródło