Quando e perché i puntatori iniziano a essere considerati rischiosi?

17

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

Quali sono stati gli sviluppi storici di questo cambiamento di pensiero? Ci sono stati eventi specifici, seminali, di ricerca o altri sviluppi?

Ad esempio, uno sguardo superficiale alla transizione 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 amp; complesso di questo, e non quasi così sequenziale. Le caratteristiche che l'hanno trasformato in quelle lingue tradizionali potrebbero aver avuto origine altrove, forse molto tempo prima.

Nota: non sto chiedendo i veri meriti dei puntatori rispetto ai riferimenti rispetto a qualcos'altro. La mia attenzione è rivolta alle ragioni per questo apparente cambiamento.

    
posta DaveInCaz 26.09.2017 - 19:20
fonte

3 risposte

21

La logica era lo sviluppo di alternative ai puntatori.

Sotto il cofano, qualsiasi puntatore / riferimento / etc viene implementato come un intero contenente un indirizzo di memoria (noto anche come puntatore). Quando C è uscito, questa funzionalità è stata esposta come puntatori. Ciò significava che tutto ciò che l'hardware sottostante poteva fare per indirizzare la memoria poteva essere fatto con i puntatori.

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

Se ci pensi, un int& e un int* const hanno davvero lo stesso livello di sicurezza, ma uno ha una sintassi molto più bella dell'altro. int& potrebbe anche essere più efficiente perché potrebbe fare riferimento a un int memorizzato in un registro (anacronismo: questo era vero in passato, ma i compilatori moderni sono così bravi a ottimizzare che è possibile avere un puntatore a un intero in un registro, finché non utilizzi mai nessuna delle funzionalità che richiederebbe un indirizzo effettivo, come ++ )

Passando a Java , passiamo alle lingue che forniscono alcune garanzie di sicurezza. C e C ++ forniti nessuno. Java garantisce che vengano eseguite solo operazioni legali. Per fare ciò, java ha eliminato completamente i puntatori. Quello che hanno scoperto è che la stragrande maggioranza delle operazioni di puntatore / riferimento fatte nel codice reale erano cose a cui i riferimenti erano più che sufficienti. Solo in una manciata di casi (come l'iterazione veloce attraverso un array) i riferimenti erano veramente necessari. In questi casi, java prende un colpo di runtime per evitare di usarli.

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

Interessanti sarebbero anche i linguaggi funzionali, che non hanno affatto questo concetto, ma questa è una discussione molto

.

    
risposta data 26.09.2017 - 19:30
fonte
10

Per i programmi complessi è necessaria una sorta di indiretta (ad esempio strutture dati ricorsive o di dimensioni variabili). Tuttavia, non è necessario implementare questa indiretta tramite puntatori.

La maggior parte dei linguaggi di programmazione di alto livello (ad esempio, non Assembly) è abbastanza sicura per la memoria e non consente l'accesso senza restrizioni al puntatore. La famiglia C è la 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 usata come un intero o come puntatore. Questi due sono equivalenti quando l'intera memoria è vista come una singola matrice contigua. C ha mantenuto questo approccio piuttosto flessibile e ha continuato a supportare aritmetica del puntatore intrinsecamente non sicura. L'intero sistema di tipi di C è più di un ripensamento. Questa flessibilità all'accesso alla memoria ha reso C molto adatto allo scopo principale: la prototipazione del sistema operativo Unix. Ovviamente Unix e C si sono rivelati molto popolari, quindi anche C viene utilizzato in applicazioni in cui questo approccio alla memoria di basso livello non è realmente necessario.

Se guardiamo ai linguaggi di programmazione precedenti a C (ad esempio, Fortran, dialoghi di Algol inclusi Pascal, Cobol, Lisp, ...) alcuni di questi supportano i puntatori a forma di C. In particolare, il concetto di puntatore nullo è stato inventato per Algol W nel 1965. Ma nessuno di questi linguaggi ha cercato di essere un linguaggio di sistemi a basso livello di astrazione efficiente e C: Fortran era pensato per il calcolo scientifico, Algol ha sviluppato alcuni concetti abbastanza avanzati, Lisp era più di un progetto di ricerca che un linguaggio di livello industriale, e Cobol era focalizzato 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 hanno utilizzato il GC come caratteristica normale. Ovviamente questo rende la lingua molto più complicata e forse più lenta, cosa particolarmente evidente al tempo dei mainframe. I linguaggi GC tendevano ad essere orientati alla ricerca (ad es. Lisp, Simula, ML) e / o richiedono potenti workstation (ad esempio Smalltalk).

Con computer più piccoli e potenti il computing in generale e i linguaggi GC sono diventati più popolari. Per le applicazioni non in tempo reale (e talvolta anche in quel momento) GC è ora l'approccio preferito. Ma gli algoritmi GC sono stati anche oggetto di intense ricerche. In alternativa, è stata ulteriormente sviluppata una migliore sicurezza della memoria senza GC, specialmente negli ultimi trent'anni: innovazioni notevoli sono RAII e puntatori intelligenti in C ++ e nel sistema di controllo del prestito / prestito a vita di Rust

.

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

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

    
risposta data 27.09.2017 - 00:47
fonte
3

Nella mia esperienza, i puntatori sono SEMPRE stati un concetto stimolante per molte persone. Nel 1970, l'università che frequentavo aveva un Burroughs B5500 e usavamo l'Algol esteso per i nostri progetti di programmazione. L'architettura hardware era basata su descrittori e alcuni codici nella parte superiore delle parole di dati. Questi sono stati progettati in modo esplicito per consentire agli array di utilizzare i puntatori senza essere autorizzati a uscire dalla fine.

Abbiamo avuto animate discussioni in aula sul nome vs valore di riferimento e su come funzionavano gli array B5500. Alcuni di noi hanno la spiegazione immediatamente. Altri no.

Più tardi, è stato un po 'uno shock che l'hardware non mi proteggesse da puntatori in fuga - specialmente nel linguaggio assembly. Nel mio primo lavoro dopo la laurea, ho aiutato a risolvere i problemi in un sistema operativo. Spesso l'unica documentazione disponibile era la discarica stampata. Ho sviluppato un talento per trovare la fonte dei puntatori in fuga in discariche di memoria, quindi tutti hanno dato le discariche "impossibili" a me per capire. Più dei problemi che abbiamo avuto sono stati causati da errori del puntatore rispetto a qualsiasi altro tipo di errore.

Molte delle persone con cui ho lavorato iniziarono a scrivere FORTRAN, poi si spostarono in C, scrissero C che era molto simile a FORTRAN e evitò i puntatori. Poiché non hanno mai interiorizzato puntatori e riferimenti, Java pone problemi. Spesso, è difficile capire per i programmatori FORTRAN come funziona davvero l'assegnazione degli oggetti.

Le lingue moderne hanno reso molto più facile fare cose che hanno bisogno di indicazioni "sotto il cofano", pur preservandoci da errori di battitura e altri errori.

    
risposta data 02.06.2018 - 08:00
fonte

Leggi altre domande sui tag