Quando e perché i puntatori hanno iniziato a essere considerati rischiosi?


18

Sembra che ci sia stato un graduale cambiamento nel pensare all'uso dei puntatori nei linguaggi di programmazione in modo tale che è stato generalmente accettato che i puntatori fossero considerati rischiosi (se non addirittura "malvagi" o simili ingrandimenti).

Quali sono stati gli sviluppi storici per questo cambiamento nel modo di pensare? Ci sono stati eventi seminali specifici, ricerche o altri sviluppi?

Ad esempio, uno sguardo superficiale al passaggio da C a C ++ a Java sembra mostrare una tendenza a integrare e quindi sostituire completamente i puntatori con riferimenti. Tuttavia, la vera catena di eventi era probabilmente molto più sottile e complessa di così, e non così sequenziale. Le caratteristiche che lo hanno trasformato in quelle lingue tradizionali potrebbero essere nate altrove, forse molto prima.

Nota: non sto chiedendo i meriti reali dei puntatori rispetto ai riferimenti rispetto a qualcos'altro. Il mio focus è sulle motivazioni di questo apparente cambiamento.


1
Era dovuto al declino dell'educazione alle arti liberali. Le persone non potevano più comprendere il riferimento indiretto, una delle idee fondamentali nella tecnologia informatica, inclusa in tutte le CPU.

10
I puntatori sono rischiosi. Perché pensi che ci sia stato un cambiamento nel modo di pensare? Sono stati apportati miglioramenti alle funzionalità del linguaggio e all'hardware che consentono di scrivere software senza puntatori, sebbene non senza alcuna penalità di prestazione.
Smetti di fare del male a Monica il

4
@DaveInCaz Per quanto ne so, lo sviluppo specifico è stato l'invenzione dei puntatori.
Smetti di fare del male a Monica il

5
@nocomprende: ciò che hai appena scritto non sono fatti né prove, solo opinioni. C'erano molti meno programmatori nel 1970, non hai alcuna prova che la popolazione oggi sia migliore o peggiore al "riferimento indiretto".
whatsisname

3
I puntatori sono sempre stati considerati rischiosi, sin dal primo giorno. È stato semplicemente un compromesso spostarli dall'assembly alle lingue di livello superiore.
Frank Hileman,

Risposte:


21

La logica era lo sviluppo di alternative ai puntatori.

Sotto il cofano, qualsiasi puntatore / riferimento / ecc. Viene implementato come un numero intero contenente un indirizzo di memoria (aka puntatore). Quando è uscito C , questa funzionalità è stata esposta come puntatore. Ciò significava che qualsiasi cosa l'hardware sottostante potesse fare per indirizzare la memoria poteva essere fatta con i puntatori.

Questo è sempre stato "pericoloso", ma il pericolo è relativo. Quando stai realizzando un programma a 1000 linee o quando hai implementato procedure di qualità del software di livello IBM, questo pericolo potrebbe essere facilmente risolto. Tuttavia, non tutti i software venivano sviluppati in questo modo. Come tale, è nato il desiderio di strutture più semplici.

Se ci pensate, an int&e a int* consthanno davvero lo stesso livello di sicurezza, ma uno ha una sintassi molto più bella dell'altro. int&potrebbe anche essere più efficiente perché potrebbe riferirsi a un int memorizzato in un registro (anacronismo: questo era vero in passato, ma i compilatori moderni sono così bravi nell'ottimizzare che puoi avere un puntatore a un numero intero in un registro, purché non usi mai nessuna delle funzionalità che richiederebbe un indirizzo reale, come ++)

Mentre passiamo a Java , ci spostiamo in lingue che forniscono alcune garanzie di sicurezza. C e C ++ non hanno fornito nessuno. Java garantisce che vengono eseguite solo le operazioni legali. Per fare questo, Java ha eliminato completamente i puntatori. Ciò che hanno scoperto è che la stragrande maggioranza delle operazioni di puntatore / riferimento eseguite nel codice reale erano cose per cui i riferimenti erano più che sufficienti. Solo in una manciata di casi (come una rapida iterazione attraverso un array) erano veramente necessari i puntatori. In questi casi, java subisce un hit di runtime per evitare di usarli.

La mossa non è stata monotona. Puntatori reintrodotti in C # , sebbene in una forma molto limitata. Sono contrassegnati come " non sicuri " , il che significa che non possono essere utilizzati da codice non attendibile. Hanno anche regole esplicite su ciò che possono e non possono indicare (ad esempio, è semplicemente non valido incrementare un puntatore oltre la fine di un array). Tuttavia, hanno scoperto che c'erano una manciata di casi in cui erano necessarie le elevate prestazioni dei puntatori, quindi li hanno rimessi.

Interessanti sarebbero anche i linguaggi funzionali, che non hanno affatto un tale concetto, ma questa è una discussione molto diversa.


3
Non sono sicuro che sia corretto dire che Java non ha puntatori. Non voglio entrare in un lungo dibattito su ciò che è e non è un puntatore, ma la JLS afferma che "il valore di un riferimento è un puntatore". Non è consentito l'accesso diretto o la modifica dei puntatori. Questo non è solo per la sicurezza, tenere le persone fuori dagli affari per tenere traccia di dove si trova un oggetto in qualsiasi momento è utile per GC.
JimmyJames,

6
@JimmyJames True. Ai fini di questa risposta, la linea di demarcazione tra puntatore e non puntatore era se supportava le operazioni aritmetiche del puntatore, che in genere non sono supportate dai riferimenti.
Cort Ammon - Ripristina Monica il

8
@JimmyJames Concordo con l'affermazione di Cort secondo cui un puntatore è qualcosa su cui è possibile eseguire operazioni aritmetiche, mentre un riferimento no. L'attuale meccanismo che implementa un riferimento in linguaggi come Java è un dettaglio di implementazione.
Robert Harvey,

3
In generale, C e C ++ avevano accettato volontariamente l'appartenenza a questo pericoloso club, consentendo un sacco di "comportamenti indefiniti" nelle specifiche.
rwong,

2
A proposito, ci sono CPU, che distinguono tra puntatori e numeri. Ad esempio, la CPU CISC originale a 48 bit nell'IBM AS / 400 lo fa. E infatti, non v'è un livello di astrazione al di sotto del sistema operativo, il che significa che non solo la CPU distinguere tra numeri e puntatori e non voglia l'aritmetica su puntatori, ma il sistema operativo stesso non sa nemmeno su puntatori a tutti e nemmeno le lingue . È interessante notare che questo rende l'AS / 400 originale un sistema, in cui riscrivere il codice da un linguaggio di scripting di alto livello in C rende gli ordini di grandezza più lenti .
Jörg W Mittag,

10

È necessario un qualche tipo di riferimento indiretto per programmi complessi (ad esempio strutture di dati ricorsive o di dimensioni variabili). Tuttavia, non è necessario implementare questa indiretta tramite puntatori.

La maggior parte dei linguaggi di programmazione di alto livello (vale a dire non Assembly) sono abbastanza sicuri per la memoria e non consentono l'accesso puntatore senza restrizioni. La famiglia C è quella strana qui.

C si è evoluto da B, che era un'astrazione molto sottile rispetto all'assemblaggio grezzo. B aveva un solo tipo: la parola. La parola potrebbe essere utilizzata come numero intero o come puntatore. Questi due sono equivalenti quando l'intera memoria è vista come un singolo array contiguo. C ha mantenuto questo approccio piuttosto flessibile e ha continuato a supportare l'aritmetica del puntatore intrinsecamente non sicura. L'intero sistema di tipi di C è più un ripensamento. Questa flessibilità di accesso alla memoria ha reso C molto adatto al suo scopo principale: la prototipazione del sistema operativo Unix. Naturalmente Unix e C si sono rivelati piuttosto popolari, quindi C è usato anche in applicazioni in cui questo approccio di basso livello alla memoria non è realmente necessario.

Se osserviamo i linguaggi di programmazione che precedevano C (es. Fortran, dialetti Algol inclusi Pascal, Cobol, Lisp, ...) alcuni di questi supportano i puntatori C-like. In particolare, il concetto di puntatore nullo è stato inventato per Algol W nel 1965. Ma nessuna di quelle lingue ha cercato di essere un linguaggio di sistemi a bassa astrazione di tipo C, efficiente: Fortran era pensato per il calcolo scientifico, Algol ha sviluppato alcuni concetti abbastanza avanzati, Lisp era più di un progetto di ricerca che di un linguaggio di livello industriale, e Cobol si è concentrato sulle applicazioni aziendali.

La raccolta dei rifiuti esisteva dalla fine degli anni '50, vale a dire ben prima di C (primi anni '70). GC richiede che la sicurezza della memoria funzioni correttamente. Le lingue prima e dopo C usavano GC come una caratteristica normale. Ovviamente ciò rende un linguaggio molto più complicato e forse più lento, il che era particolarmente evidente nel tempo dei mainframe. I linguaggi GC tendevano a essere orientati alla ricerca (ad esempio Lisp, Simula, ML) e / o richiedono potenti stazioni di lavoro (ad esempio Smalltalk).

Con i computer più piccoli e potenti, i computer in generale e le lingue GC in particolare sono diventate più popolari. Per applicazioni non in tempo reale (e talvolta anche allora) GC è ora l'approccio preferito. Ma anche gli algoritmi GC sono stati oggetto di intense ricerche. In alternativa, è stata ulteriormente sviluppata una migliore sicurezza della memoria senza GC, soprattutto negli ultimi tre decenni: innovazioni notevoli sono RAII e puntatori intelligenti in C ++ e il sistema di controllo della durata / prestito di Rust.

Java non ha innovato essendo un linguaggio di programmazione sicuro per la memoria: fondamentalmente ha preso la semantica del linguaggio GCted, Smalltalk sicuro per la memoria e li ha combinati con la sintassi e la tipizzazione statica del C ++. È stato quindi commercializzato come C / C ++ migliore e più semplice. Ma è solo superficialmente un discendente del C ++. La mancanza di puntatori di Java è dovuta molto più al modello a oggetti Smalltalk che a un rifiuto del modello di dati C ++.

Quindi i linguaggi "moderni" come Java, Ruby e C # non dovrebbero essere interpretati come il superamento dei problemi di puntatori grezzi come in C, ma dovrebbero essere visti come attingendo da molte tradizioni, tra cui C, ma anche da linguaggi più sicuri come Smalltalk, Simula, o Lisp.


4

Nella mia esperienza, gli indicatori sono sempre stati un concetto stimolante per molte persone. Nel 1970, l'università a cui frequentavo aveva un Burroughs B5500 e abbiamo usato Extended Algol per i nostri progetti di programmazione. L'architettura hardware si basava su descrittori e alcuni codici nella parte superiore delle parole dei dati. Questi sono stati progettati esplicitamente per consentire agli array di utilizzare i puntatori senza essere autorizzati a uscire dall'estremità.

Abbiamo avuto animate discussioni in classe sul nome rispetto al riferimento di valore e su come funzionavano le matrici B5500. Alcuni di noi hanno ricevuto immediatamente la spiegazione. Altri no.

Più tardi, è stato un po 'scioccante che l'hardware non mi abbia protetto dai puntatori fuggitivi, soprattutto nel linguaggio assembly. Nel mio primo lavoro dopo la laurea, ho aiutato a risolvere i problemi in un sistema operativo. Spesso l'unica documentazione che avevamo era il dump di crash stampato. Ho sviluppato un talento per trovare la fonte di puntatori in fuga nei dump della memoria, quindi tutti mi hanno dato i dump "impossibili" per farmi capire. Più problemi che abbiamo avuto sono stati causati da errori del puntatore che da qualsiasi altro tipo di errore.

Molte delle persone con cui ho lavorato hanno iniziato a scrivere FORTRAN, poi si sono trasferiti in C, hanno scritto C che assomigliava molto a FORTRAN ed evitavano i suggerimenti. Poiché non hanno mai interiorizzato puntatori e riferimenti, Java pone problemi. Spesso, per i programmatori FORTRAN è difficile capire come funziona davvero l'assegnazione degli oggetti.

I linguaggi moderni hanno reso molto più facile fare cose che hanno bisogno di puntatori "sotto il cofano", proteggendoci da errori di battitura e altri errori.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.