Il linguaggio di programmazione C è stato considerato un linguaggio di basso livello quando è uscito?


151

Attualmente C è considerato un linguaggio di basso livello , ma negli anni '70 era considerato di basso livello? Il termine era persino in uso allora?

Molte lingue popolari di livello superiore non esistevano fino alla metà degli anni '80 e oltre, quindi sono curioso di sapere se e come la natura del basso livello sia cambiata nel corso degli anni.


13
Come punto di dati, circa 1978 alcuni mentori di programmazione mi hanno descritto C come un linguaggio di assemblaggio glorificato.
Ben Crowell,

7
@BenCrowell Non sono sicuro di cosa significhi "glorificato" nel contesto della tua affermazione, ma ho sperimentato di chiamare C un linguaggio di assemblaggio universale (= indipendente dalla piattaforma) .
Melebio

13
È interessante notare che c'è un caso in cui C non è un linguaggio di basso livello , ed è meno uno adesso di quanto non fosse negli anni '70, perché la macchina astratta di C è più lontana dall'hardware moderno di quanto non fosse dal PDP-11.
Tom

5
Quando pensi a questo, potresti anche voler pensare all'origine: Unix è scritto in C, c gira su Unix e esegue la cross-compilazione di unix con altre piattaforme. Tutto ciò che non era necessario per compilare Unix era un sovraccarico non necessario e l'obiettivo principale di C era quello di rendere più semplice la scrittura / il porting di un compilatore possibile. A questo scopo era il livello corretto EXACT, quindi non penso a C come livello alto o basso, lo considero uno strumento per il porting di Unix che, come quasi tutti gli strumenti Unix, è estremamente adattabile a molti problemi.
Bill K,

4
Vale la pena notare che lisp è stato inventato nel 1958
Prime

Risposte:


144

Per rispondere agli aspetti storici della domanda:

La filosofia del design è spiegata nel linguaggio di programmazione C scritto da Brian Kernighan e dal designer C Dennis Ritchie, il "K&R" di cui potresti aver sentito parlare. La prefazione alla prima edizione dice

C non è un linguaggio "di altissimo livello", né un linguaggio "grande" ...

e l'introduzione dice

C è un linguaggio relativamente di "basso livello" ... C non fornisce alcuna operazione per gestire direttamente oggetti compositi come stringhe di caratteri, set, elenchi o matrici. Non ci sono operazioni che manipolano un intero array o stringa ...

L'elenco continua per un po 'prima che il testo continui:

Sebbene l'assenza di alcune di queste caratteristiche possa sembrare una grave carenza, ... mantenere il linguaggio fino a dimensioni modeste ha reali vantaggi.

(Ho solo la seconda edizione del 1988, ma il commento qui sotto indica che il testo citato è lo stesso nella prima edizione del 1978).

Quindi, sì, i termini "alto livello" e "basso livello" erano in uso all'epoca, ma C era progettato per cadere da qualche parte nello spettro in mezzo. Era possibile scrivere in C un codice portatile su piattaforme hardware e questo era il criterio principale per stabilire se un linguaggio fosse considerato di alto livello in quel momento. Tuttavia, a C mancavano alcune caratteristiche che erano caratteristiche di linguaggi di alto livello, e questa fu una decisione progettuale a favore della semplicità.


42
Questa è una risposta eccellente! Prove storiche verificabili + testimonianza di un attore meglio informato del significato di basso e alto livello nel periodo cui si riferisce il PO. A proposito, confermo che le citazioni erano già nell'edizione del 1978.
Christophe,

157

Questo dipende dalla tua definizione di linguaggio di alto livello e basso livello. Quando fu sviluppato il C, tutto ciò che era di livello superiore all'assemblaggio era considerato un linguaggio di alto livello. Questa è una barra bassa da cancellare. Più tardi, questa terminologia è passata al punto che alcuni oggi considererebbero persino Java un linguaggio di basso livello.

Anche nel panorama linguistico di alto livello degli anni '70, vale la pena sottolineare che C è di livello abbastanza basso. Il linguaggio C è fondamentalmente B più un semplice sistema di tipi e B non è molto più di un comodo livello di sintassi procedurale / strutturato per l'assemblaggio. Poiché il sistema di tipi è adattato in modo retrò al di sopra del linguaggio B non tipizzato, è comunque possibile tralasciare le annotazioni dei tipi in alcuni punti e intverranno assunti.

C lascia consapevolmente fuori funzionalità costose o difficili da implementare che erano già ben consolidate al momento, come ad esempio

  • gestione automatica della memoria
  • funzioni o chiusure nidificate
  • OOP di base o coroutine
  • sistemi di tipi più espressivi (ad esempio tipi con limiti di intervallo, tipi definiti dall'utente come tipi di record, digitazione forte, ...)

C ha alcune caratteristiche interessanti:

  • supporto per la ricorsione (come conseguenza delle sue variabili automatiche basate su stack, rispetto alle lingue in cui tutte le variabili hanno una durata globale)
  • puntatori a funzione
  • I tipi di dati definiti dall'utente (strutture e sindacati) sono stati implementati poco dopo il rilascio iniziale di C.
  • La rappresentazione di stringhe di C (puntatore a caratteri) è in realtà un enorme miglioramento rispetto a B che codifica più lettere in una parola macchina.
  • I file di intestazione di C costituivano una modifica dell'efficienza per mantenere piccole le unità di compilazione, ma fornivano anche un semplice sistema di moduli.
  • Puntatori senza restrizioni in stile assembly e aritmetica dei puntatori, rispetto ai riferimenti più sicuri. I puntatori sono una funzione intrinsecamente pericolosa ma anche molto utile per la programmazione di basso livello.

Al momento dello sviluppo di C, altri linguaggi innovativi come COBOL, Lisp, ALGOL (in vari dialetti), PL / I, SNOBOL, Simula e Pascal erano già stati pubblicati e / o erano ampiamente utilizzati per specifici domini problematici. Ma la maggior parte di quelle lingue esistenti erano destinate alla programmazione mainframe o erano progetti di ricerca accademica. Ad esempio, quando ALGOL-60 è stato inizialmente progettato come linguaggio di programmazione universale, la tecnologia e l'informatica necessarie per implementarlo non esistevano ancora. Alcuni di questi (alcuni dialetti ALGOL, PL / I, Pascal) erano destinati anche alla programmazione di basso livello, ma tendevano ad avere compilatori più complessi o erano troppo sicuri (ad es. Senza puntatori senza restrizioni). Pascal manca in particolare di un buon supporto per array di lunghezza variabile.

Rispetto a questi linguaggi, C rifiuta le funzioni "eleganti" e costose al fine di essere più pratiche per lo sviluppo di basso livello. C non è mai stato principalmente un progetto di ricerca sulla progettazione linguistica. Invece, è stata una derivazione dello sviluppo del kernel Unix sul minicomputer PDP-11 che era relativamente limitato dalle risorse. Per la sua nicchia (un linguaggio minimalista di basso livello per la scrittura di Unix con un compilatore single-pass che è facile da portare) C ha eccelso in modo eccellente - e oltre 45 anni dopo è ancora la lingua franca della programmazione dei sistemi.


17
Solo una piccola aggiunta per le persone che non sono consapevoli di ciò che era necessario per la famiglia ALGOL esistente e le lingue Pascal: quelle lingue avevano funzioni lessicicamente annidate da cui si poteva accedere alla variabile (locale) dichiarata nelle funzioni esterne. Ciò significava che dovevi mantenere un "display" - una matrice di puntatori a ambiti lessicali esterni - ad ogni chiamata e ritorno di funzione (che cambiava il livello lessicale) - o dovevi incatenare gli ambiti lessicali nello stack e ciascuno di tali accessi variabili richiesto più salti indiretti nello stack per trovarlo. Costoso! C gettò via tutto ciò. Mi manca ancora.
Davidid

12
@davidbak: l'ABI System V x86-64 (ovvero la convenzione di chiamata su Linux / OS X) definisce %r10il "puntatore a catena statica", che è esattamente ciò di cui stai parlando. Per C è solo un altro registro scratch che blocca le chiamate, ma immagino che Pascal lo userebbe. (Le funzioni nidificate GNU C lo usano per passare un puntatore all'ambito esterno quando tale funzione non è in linea (ad es. Se si fa un puntatore a una funzione in modo che il compilatore crei un trampolino di codice macchina nello stack): accettabilità di regolare utilizzo di r10 e r11 )
Peter Cordes,

2
@PeterCordes Fascinating! Pascal era ancora ampiamente usato quando uscì System V (anche se non so quando fu definito l'ABI SysV formale). La tua risposta collegata è molto istruttiva.
davidbak,

3
C ha tipi definiti dall'utente (struct / union). Il resto di quelle "caratteristiche" sono state lasciate fuori, sospetto, perché sono di uso zero o negativo (a meno che tu non stia partecipando a un contest di codice offuscato :-)), in quanto riducono l'obiettivo di mantenere il linguaggio sia semplice che espressivo .
jamesqf,

6
@jamesqf: ma molto presto C non aveva un incarico di struttura; Immagino che devi memcpy o copiare i membri singolarmente invece di scrivere a = b;per copiare un'intera struttura come puoi in ISO C89. Quindi, all'inizio del C, i tipi definiti dall'utente erano decisamente di seconda classe e potevano essere passati solo per riferimento come argomenti di funzione. L'avversione di C verso gli array e anche Perché C ++ supporta l'assegnazione a membri degli array all'interno di strutture, ma non in generale?
Peter Cordes,

37

All'inizio degli anni '70, C era una straordinaria boccata d'aria fresca che utilizzava costrutti moderni in modo così efficace che l'intero sistema UNIX poteva essere riscritto dal linguaggio assembly in C con spazio trascurabile o penalità di prestazione. All'epoca molti contemporanei lo definivano un linguaggio di alto livello.

Gli autori di C, principalmente Dennis Ritchie, erano più cauti e nell'articolo della rivista tecnica del Bell System affermavano che "C non è un linguaggio di alto livello". Con un sorriso ironico e con l'intenzione di essere provocatorio, Dennis Ritchie direbbe che era un linguaggio di basso livello. Il principale tra i suoi obiettivi di progettazione per C era quello di mantenere il linguaggio vicino alla macchina e allo stesso tempo fornire portabilità, ovvero l'indipendenza della macchina.

Per maggiori informazioni consultare l' articolo originale BSTJ:

Grazie Dennis. Che tu possa riposare in pace.


3
Se mi chiedete, è stato sostanzialmente un involucro tipicamente tipizzato e di sintassi migliore per l'assemblaggio PDP.
einpoklum,


2
@einpoklum Tendo ad essere d'accordo. Ho appreso C all'incirca nel 1980 all'università, su un PDP-11 / 34a, come è accaduto, ed è stato descritto da uno dei prof come un "linguaggio di assemblaggio portatile". Probabilmente perché oltre al PDP-11, avevamo diverse macchine Superbrain CP / M in laboratorio con un compilatore C disponibile su di esse. en.wikipedia.org/wiki/Intertec_Superbrain
dgnuff

2
Anche un'ottima risposta basata su prove storiche verificabili (wow! C'era una pre-versione del K&R disponibile là fuori!).
Christophe,

7
@einpoklum Non è un po 'inverosimile? Ho avuto l'opportunità di scrivere a metà degli anni '80 il codice di sistema e di applicazione in assemblatore (Z80 e M68K), un "assemblatore portatile" chiamato M (sintassi PDP11 con un registro astratto a 16 bit e un set di istruzioni) e C. Rispetto all'assemblatore , C è sicuramente un linguaggio di alto livello: la produttività della programmazione C era di un ordine di grandezza superiore a quello dell'assemblatore! Ovviamente niente stringhe (SNOBOL), nessun supporto di file di dati nativi (COBOL), nessun codice di auto-generazione (LISP), nessuna matematica avanzata (APL), nessun oggetto (SIMULA), quindi possiamo concordare sul fatto che non era molto alto
Christophe

21

Come ho scritto altrove su questo sito quando qualcuno si riferiva al modello di gestione della memoria malloc / free come "programmazione di basso livello",

Divertente come la definizione di "basso livello" cambi nel tempo. Quando stavo imparando a programmare per la prima volta, qualsiasi lingua che forniva un modello heap standardizzato che rendesse possibile un semplice modello di allocazione / libera era considerata di alto livello. Nella programmazione di basso livello, dovresti tenere traccia della memoria da solo (non delle allocazioni, ma delle posizioni di memoria stesse!), O scrivere il tuo allocatore di heap se ti senti davvero fantasioso.

Per il contesto, questo era nei primi anni '90, ben dopo l'uscita di C.


La libreria standard non era solo una cosa dalla standardizzazione alla fine degli anni '80 (beh, basata su API Unix esistenti)? Inoltre, la programmazione del kernel (che era il dominio problematico originale di C) richiede naturalmente cose di basso livello come la gestione manuale della memoria. All'epoca C doveva essere il linguaggio di programmazione di più alto livello in cui era scritto un kernel serio (penso che al giorno d'oggi il kernel NT usi anche una buona quantità di C ++).
amon,

6
@hyde, ho usato Algol 60, due dialetti FORTRAN diversi e diversi dialetti BASIC negli anni '70, e nessuna di quelle lingue aveva puntatori o un allocatore di heap.
Solomon Slow,

7
A rigor di termini, è ancora possibile programmare senza malloc()chiamare brk(2)o chiamando direttamente mmap(2)la memoria risultante. È una PITA enorme senza alcun beneficio immaginabile (a meno che non ti capiti di implementare una cosa simile a un malloc), ma puoi farlo.
Kevin,

1
@amon - ad eccezione delle straordinarie macchine basate su stack Burroughs programmate in ALGOL dal basso verso l'alto ... e molto prima anche di Unix. Oh, a proposito, Multics, che è stato l'ispirazione per Unix: scritto in PL / I. Simile ad ALGOL, livello superiore a C.
davidbak,

6
In realtà, la ragione per cui molti di noi non usano Multics oggi probabilmente ha più a che fare con il fatto che ha funzionato solo su mainframe incredibilmente costosi - vale a dire, la tipica scandalosa spesa del mainframe del giorno più la spesa extra di mezzo gabinetto di hardware specializzato per implementare la memoria virtuale con sicurezza. Quando i minicomputer a 32 bit come il VAX-11 sono usciti tutti tranne le banche e il governo sono decampati da IBM e Seven Nani e hanno portato la loro elaborazione su larga scala su "mini" computer.
Davidid

15

Molte risposte hanno già fatto riferimento a primi articoli che dicevano cose come "C non è un linguaggio di alto livello".

Non posso resistere all'accumulo, tuttavia: molti, se non la maggior parte o tutti gli HLL all'epoca - Algol, Algol-60, PL / 1, Pascal - fornivano il controllo dei limiti dell'array e il rilevamento numerico di overflow.

L'ultima volta che ho controllato gli overflow del buffer e degli interi sono stati la causa principale di molte vulnerabilità di sicurezza. ... Sì, ancora il caso ...

La situazione per la gestione dinamica della memoria era più complicata, ma comunque, in stile C malloc / free era un grande passo indietro in termini di sicurezza.

Quindi, se la tua definizione di HLL include "previene automaticamente molti bug di basso livello", beh, il triste stato di sicurezza informatica sarebbe molto diverso, probabilmente migliore, se C e UNIX non si fossero verificati.


7
Da quando mi è capitato di essere stato coinvolto nell'implementazione di Intel MPX e del compilatore di controllo puntatore / limiti che si è verificato se lo fosse, posso riferirti ai documenti sulle loro prestazioni: sostanzialmente il 5-15%. Gran parte di ciò riguarda analisi del compilatore che erano appena possibili negli anni '70 e '80, rispetto ai controlli ingenui che potrebbero essere più lenti del 50%. Tuttavia, penso che sia giusto affermare che C e UNIX hanno ritardato il lavoro su tali analisi di 20 anni - quando C è diventato il linguaggio di programmazione più popolare, c'era molta meno richiesta di sicurezza.
Krazy Glew,

9
@jamesqf Inoltre, molte macchine precedenti a C avevano un supporto hardware speciale per il controllo dei limiti e gli overflow degli interi. Dato che C non usava quell'HW, alla fine fu deprecato e rimosso.
Krazy Glew,

5
@jamesqf Ad esempio: il MIPS RISC ISA era originariamente basato sui benchmark Stanford, che erano stati originariamente scritti in Pascal, solo successivamente spostati in C. Dato che Pascal controllava l'overflow di interi con segno, quindi anche MIPS, in istruzioni come ADD. Intorno al 2010 lavoravo al MIPS, il mio capo voleva rimuovere le istruzioni non utilizzate in MIPSr6 ​​e gli studi hanno dimostrato che i controlli di overflow non venivano quasi mai utilizzati. Ma si è scoperto che Javascript ha fatto tali controlli, ma non è stato possibile utilizzare le istruzioni economiche a causa della mancanza di supporto del sistema operativo.
Krazy Glew,

3
@KrazyGlew - È molto interessante sollevarlo dal momento che in C / C ++ la lotta tra utenti e scrittori del compilatore sul "comportamento indefinito" a causa del trabocco di numeri interi firmati si sta surriscaldando, dal momento che gli autori di compilatori di oggi hanno preso il vecchio mantra "Making a wrong programma peggio non è peccato "e lo ha alzato a 11. Un sacco di post su StackOverflow e altrove riflettono questo ...
David

2
Se Rust avesse elementi intrinseci SIMD come C, potrebbe essere un linguaggio di assemblaggio portatile moderno quasi ideale. C sta peggiorando come linguaggio di assemblaggio portatile a causa dell'aggressiva ottimizzazione basata su UB e del fallimento nell'esporre in modo portabile nuove operazioni primitive supportate dalle moderne CPU (come popcnt, contare zeri iniziali / finali, bit-reverse, byte-reverse, saturazione matematica). Fare in modo che i compilatori C riescano in modo efficiente per questi su CPU con supporto HW richiede spesso intrinsechi non portatili. Fare in modo che il compilatore emuli popcnt su target senza di esso è meglio del riconoscimento del linguaggio popcnt.
Peter Cordes,

8

Considera le lingue più vecchie e molto più alte che hanno preceduto C (1972):

Fortran - 1957 (livello non molto più alto di C)

Lisp - 1958

Cobol - 1959

Fortran IV - 1961 (livello non molto più alto di C)

PL / 1 - 1964

APL - 1966

Più un linguaggio di medio livello come RPG (1959), principalmente un linguaggio di programmazione per sostituire i sistemi di registrazione delle unità basati su plugboard.

Da questo punto di vista, C sembrava un linguaggio di livello molto basso, solo un po 'al di sopra dei macroassemblatori utilizzati sui mainframe al momento. Nel caso dei mainframe IBM, le macro assembler sono state utilizzate per l'accesso al database come BDAM (metodo di accesso al disco di base), poiché le interfacce del database non erano state portate su Cobol (a quel tempo) risultando in un lascito di un mix di assembly e I programmi Cobol sono ancora in uso oggi sui mainframe IBM.


2
Se vuoi elencare lingue più vecchie e più alte, non dimenticare LISP .
Deduplicatore,

@Deduplicator: l'ho aggiunto all'elenco. Mi stavo concentrando su ciò che veniva utilizzato sui mainframe IBM e non ricordo che LISP fosse così popolare, ma APL era anche un linguaggio di nicchia per i mainframe IBM (tramite console di time sharing) e IBM 1130. Simile a LISP, APL è uno dei le lingue più esclusive di alto livello, ad esempio guarda quanto poco codice serve per creare il gioco della vita di Conway con una versione attuale di APL: youtube.com/watch?v=a9xAKttWgP4 .
rcgldr,

2
Non considero RPG o COBOL come di livello particolarmente alto, almeno non prima di COBOL-85. Quando graffi la superficie di COBOL, vedi che si tratta essenzialmente di una raccolta di macro di assemblatori molto avanzate. Tanto per cominciare, manca sia di funzioni, procedure e ricorsione, sia di ogni tipo di scoping. Tutto lo spazio di archiviazione deve essere dichiarato all'inizio del programma che porta a aperture estremamente lunghe o a un riutilizzo variabile doloroso.
idrougge,

Ho dei brutti ricordi dell'uso di Fortran IV, non ricordo che sia apprezzabilmente "di livello superiore" rispetto a C.
DaveG,

@idrougge - COBOL e RPG, e anche i set di istruzioni per mainframe includevano il supporto completo per BCD imballati o non imballati, un requisito per il software finanziario in paesi come gli Stati Uniti. Considero gli operatori nativi correlati come "sposta corrispondente" di alto livello. Il gioco di ruolo era insolito in quanto si specificava il collegamento tra campi di input non elaborati e campi di output formattati e / o accumulatori, ma non l'ordine delle operazioni, in modo simile alla programmazione dei plug-in che ha sostituito.
rcgldr,

6

La risposta alla tua domanda dipende da quale linguaggio C sta chiedendo.

La lingua descritta nel Manuale di riferimento C del 1974 di Dennis Ritchie era un linguaggio di basso livello che offriva alcune delle comodità di programmazione di linguaggi di livello superiore. Allo stesso modo, i dialetti derivati ​​da quel linguaggio erano tendenzialmente linguaggi di programmazione di basso livello.

Quando lo standard C del 1989/1990 fu pubblicato, tuttavia, non descriveva il linguaggio di basso livello che era diventato popolare per la programmazione di macchine reali, ma invece descriveva un linguaggio di livello superiore che poteva essere - ma non doveva essere- -attuato in termini di livello inferiore.

Come gli autori della nota C Standard, una delle cose che ha reso utile il linguaggio è stata che molte implementazioni potevano essere trattate come assemblatori di alto livello. Poiché C era anche usato come alternativa ad altri linguaggi di alto livello e poiché molte applicazioni non richiedevano la capacità di fare cose che i linguaggi di alto livello non potevano fare, gli autori dello Standard permisero alle implementazioni di comportarsi in modo arbitrario se i programmi tentassero di usare costrutti di basso livello. Di conseguenza, il linguaggio descritto dallo standard C non è mai stato un linguaggio di programmazione di basso livello.

Per comprendere questa distinzione, considera come Ritchie's Language e C89 visualizzerebbero lo snippet di codice:

struct foo { int x,y; float z; } *p;
...
p[3].y+=1;

su una piattaforma in cui "char" è 8 bit, "int" è big-endian a 16 bit, "float" è 32 bit e le strutture non hanno particolari requisiti di riempimento o allineamento, quindi la dimensione di "struct foo" è di 8 byte.

Su Ritchie's Language, il comportamento dell'ultima istruzione prenderebbe l'indirizzo memorizzato in "p", aggiungerebbe 3 * 8 + 2 [ie 26] byte ad esso e otterrebbe un valore a 16 bit dai byte a quell'indirizzo e al successivo , aggiungine uno a quel valore e quindi riscrivi quel valore a 16 bit negli stessi due byte. Il comportamento sarebbe definito come agendo sul 26 ° e 27 ° byte dopo quello all'indirizzo p, indipendentemente dal tipo di oggetto memorizzato lì.

Nel linguaggio definito dallo standard C, nel caso in cui * p identifichi un elemento di "struct foo []" seguito da almeno altri tre elementi completi di quel tipo, l'ultima istruzione ne aggiungerebbe uno al membro y di il terzo elemento dopo * p. Il comportamento non sarebbe definito dalla norma in nessun'altra circostanza.

Il linguaggio di Ritchie era un linguaggio di programmazione di basso livello perché, mentre permetteva a un programmatore di usare astrazioni come matrici e strutture quando era conveniente, definiva il comportamento in termini di layout sottostante degli oggetti in memoria. Al contrario, il linguaggio descritto da C89 e successivi standard definisce le cose in termini di un'astrazione di livello superiore e definisce solo il comportamento del codice che è coerente con quello. Le implementazioni di qualità adatte alla programmazione di basso livello si comporteranno utilmente in più casi di quelli previsti dalla norma, ma non esiste un documento "ufficiale" che specifichi cosa deve fare un'implementazione per essere adatta a tali scopi.

Il linguaggio C inventato da Dennis Ritchie è quindi un linguaggio di basso livello ed è stato riconosciuto come tale. Il linguaggio inventato dal C Standards Committee, tuttavia, non è mai stato un linguaggio di basso livello in assenza di garanzie fornite dall'implementazione che vanno al di là dei mandati dello Standard.

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.