Kiedy i dlaczego wskaźniki zaczęły być postrzegane jako ryzykowne?

18

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.

DaveInCaz
źródło
1
Było to spowodowane spadkiem edukacji w zakresie sztuk wyzwolonych. Ludzie nie mogli już zrozumieć Indirect Reference, jednego z najbardziej podstawowych pomysłów w technologii komputerowej, zawartego we wszystkich procesorach.
10
Wskaźniki ryzykowne. Jak myślisz, dlaczego zmieniło się myślenie? Udoskonalono funkcje językowe i sprzętowe, które umożliwiają pisanie oprogramowania bez wskaźników, choć nie bez ograniczenia wydajności.
Przestań krzywdzić Monikę
4
@DaveInCaz O ile mi wiadomo, że szczególnym postępem było wynalezienie wskaźników.
Przestań krzywdzić Monikę
5
@nocomprende: to, co właśnie napisałeś, nie jest faktem ani dowodem, tylko opinią. W 1970 r. Było znacznie mniej programistów, nie masz żadnych dowodów na to, że populacja jest lepsza lub gorsza przy „pośredniej referencji”.
whatsisname
3
Wskaźniki zawsze były uważane za ryzykowne, od pierwszego dnia. Przeniesienie ich z języków asemblera na języki wyższego poziomu było po prostu kompromisem.
Frank Hileman

Odpowiedzi:

21

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&i int* constnaprawdę 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.

Cort Ammon - Przywróć Monikę
źródło
3
Nie jestem pewien, czy poprawne jest twierdzenie, że Java nie ma wskaźników. Nie chcę wdawać się w długą debatę na temat tego, co jest i nie jest wskaźnikiem, ale JLS mówi, że „wartością odniesienia jest wskaźnik”. Po prostu nie ma bezpośredniego dostępu ani modyfikacji wskaźników. Nie dotyczy to również bezpieczeństwa, ponieważ pomaga GC w utrzymaniu ludzi z dala od śledzenia miejsca, w którym znajduje się obiekt.
JimmyJames
6
@JimmyJames True. Na potrzeby tej odpowiedzi linia podziału między wskaźnikiem a wskaźnikiem innym nie wskazywała, czy obsługuje on operacje arytmetyczne wskaźnika, które zwykle nie są obsługiwane przez odwołania.
Cort Ammon - Przywróć Monikę
8
@JimmyJames Zgadzam się z twierdzeniem Corta, że wskaźnik jest czymś, na czym można wykonywać operacje arytmetyczne, podczas gdy odwołanie nie. Rzeczywistym mechanizmem implementującym odwołanie w językach takich jak Java jest szczegół implementacji.
Robert Harvey
3
Ogólnie rzecz biorąc, C i C ++ dobrowolnie zaakceptowały członkostwo w tym niebezpiecznym klubie, dopuszczając wiele „niezdefiniowanych zachowań” do specyfikacji.
rwong
2
Nawiasem mówiąc, nie procesory, które odróżniają od wskaźników i liczb. Np. Robi to 48-bitowy procesor CISC w IBM AS / 400. A w rzeczywistości, istnieje warstwa abstrakcji pod OS, co oznacza, że nie tylko CPU rozróżniania liczb i wskaźników i zabronić arytmetyczne na wskaźnikach, ale sam OS nawet nie wie o wskazówki w ogóle , a nie robić języki . Co ciekawe, sprawia to, że oryginalny system AS / 400 jest jednym systemem, w którym ponowne pisanie kodu z wysokiego poziomu języka skryptowego w C powoduje spowolnienie rzędu wielkości .
Jörg W Mittag
10

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.

amon
źródło
4

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.

Valerie Griffin
źródło