C.C si trova dappertutto e all'interno di qualsiasi cosa. Nel rover marziano Curiosity, nel sistema operativo di ogni computer, di ogni smartphone, nelle virtual machine Java, in Google Chrome, nei bancomat, nelle auto, nei robot delle sale operatorie, nei computer che hanno programmato i robot delle sale operatorie, nei computer che hanno programmato questi ultimi e infine C alimenta se stesso in quanto linguaggio di implementazione di se stesso.
Pubblicità
Quando la civiltà tecnologica umana sarà estinta—forse a causa di una guerra nucleare programmata in C o come risultato di un'epidemia causata da un qualche batterio super-resistente scoperto da un software implementato in C—e noi per sopravvivere torneremo ad abitare le caverne rosicchiando ossa o contendendoci qualsiasi carogna ci capiti a tiro, da qualche parte ci sarà ancora un programma scritto in C in esecuzione.Tutto questo non solamente perché a molti piace programmare in C, anche se le stime dicono che quasi il 20 percento dei programmatori lo utilizza (guardate il video sotto). C'è molto più di quello a cui comunemente si pensa riferendosi ai "linguaggi di programmazione". Alcuni linguaggi che consideriamo più o meno fondamentali come Java, Python, Ruby, Lisp, eccetera, vengono utilizzati per moltissimi scopi. Anche C è sfruttabile nei campi più vari ma ciò che lo rende speciale è il suo essere diventato de facto il linguaggio stesso delle macchine, che si tratti di un microcontroller da due soldi o di una navicella spaziale.Una singola puntata di Know Your Language dedicata a C non sarebbe sufficiente. Leggersi 10 minuti di Processing dovrebbe equivalere a sei ore di lettura di C. Io non posso ambire a fornire la stessa quantità di informazioni neanche in due o tre puntate ma posso almeno cercare di avvicinarmi a delineare cosa sia questo fondamentale linguaggio di programmazione.
Pubblicità
Partiamo dall'inizio. Da dove viene C? Com'è diventato la forza della natura che quasi sicuramente continuerà a essere?Pura fortuna? Tempismo? Preveggenza?Principalmente la necessità che aguzza l'ingegno.
EVOLVERE UNIX
Pubblicità
Il tutto consisteva in una semplice interfaccia a righe di comando con un compilatore ed un interprete. L'innovazione di questa versione di Unix era l'autosufficienza: il software poteva essere scritto e testato sullo stesso computer a cui era destinato. In precedenza, il PDP-7 pre-Unix richiedeva che si programmasse su una macchina differente, la GE-635, stampando il tutto per poi darlo in pasto al computer per cui il programma era stato pensato.Ora la PDP-7 poteva essere utilizzata per sviluppare i suoi stessi programmi e ancora meglio per continuare a sviluppare il suo stesso sistema operativo Unix."Il programma assembler del PDP-7 di Thompson superava in semplicità persino quello della DEC", ricorda Ritchie, "valutava espressioni e restituiva gli output corrispondenti. Non c'erano librerie, nessun loader o link editor: l'intero programma sorgente veniva sottoposto all'assembler, il file che produceva come output aveva un nome specifico ed era direttamente eseguibile".Subito dopo, Malcolm Douglas McIlroy, un tecnico del Bell Labs che sarebbe diventato una delle figure cruciali nello sviluppo di Unix, scrisse un linguaggio di alto livello per Unix, chiamato TMG, uno strumento che ebbe vita breve concepito per essere un "generatore di parser": un programma per scrivere nuovi compilatori, i meta-programmi fondamentali che convertono linguaggi di alto livello (come Fortran all'epoca) in linguaggio assembly.
Pubblicità
TMG diede lo spunto a Thompson per creare un linguaggio di programmazione di sistema (SPL) per quello che poi si sarebbe chiamato Unix. Un SPL programma i software per interfacciarsi con la macchina stessa: sistemi operativi, utility, driver, compilatori. È un intermediario tra qualsiasi altro linguaggio di programmazione e il lavoro svolto dalle macchine.Un linguaggio di programmazione di sistema si trova ad un livello di astrazione superiore rispetto al linguaggio assembly e alle istruzioni macchina ma, a loro differenza, è indipendente dalla macchina. Trattandosi di un'astrazione, opera al livello di un sistema idealizzato, il semplice schema di quello che un computer è nella realtà. Non è come una virtual machine, tipo Java, piuttosto fornisce le corrispondenze tra linguaggio naturale e quello della macchina senza la necessità di specificare i dettagli del computer su cui verrà eseguito, questo ultimo processo avviene quando il sistema stesso compila il linguaggio di programmazione di sistema in linguaggio Assembly.Potremmo dire che C è un linguaggio di calcolo di per sé, situato ad un livello di astrazione tale da potersi definire universale ma con l'abilità di comunicare con l'hardware.Questo è il perché dell'importanza di C, la sua eleganza, longevità e onnipresenza, che è come dire il perché dei computer stessi e lo capiremo più in dettaglio proseguendo con il nostro racconto.
Pubblicità
Il PDP-7. Immagine: WikiQuindi, Thompson scrisse B, il discendente di un altro linguaggio chiamato BCPL, un altro generatore di parser che può essere visto come un linguaggio di programmazione di sistema. Nella scala gerarchica dei linguaggi andava a posizionarsi tra il compilatore, il livello in cui il codice più astratto viene convertito in assembly e tutto il resto del computer.B non era in circolazione da molto tempo quando Ritchie completò C. Il nuovo linguaggio si trattava essenzialmente di B con l'aggiunta dei "tipi di dato". In B, tutti i dati erano la stessa cosa (numeri interi, caratteri, numeri in virgola mobile) e riferirsi ad essi con una variabile significava semplicemente indicare un indirizzo di memoria, qualsiasi cosa contenesse. In pratica, non era molto diverso dal modo in cui linguaggi interpretati come JavaScript gestiscono i dati, ma era comunque limitante in quanto ogni dichiarazione di variabile richiedeva un singola porzione di memoria, indipendentemente da quanta ne fosse effettivamente richiesta.In C, quindi, un programmatore può specificare se questa o quella unità di dati necessitino di un singolo byte o otto o più byte di memoria. Questo rispondeva ad un nuovo tipo di necessità, dato che i computer più recenti erano in grado di manipolare dati al livello di singoli byte invece che riferirsi ai pacchetti di due byte noti come word. C è arrivato al momento giusto per approfittare di questo cambiamento.
A, B, C
Pubblicità
Coinvolgente vero? Tenete a mente: tipi di dati.Forse è riduttivo porla in questi termini ma è questo l'aspetto cruciale che portò C a diventare così importante. B concepiva la memoria come una serie di "celle" delle stesse dimensioni, che corrispondevano a due bytes di dati, detti "word" a livello hardware. Nel 1970 circa, nacquero nuove macchine, in particolare la PDP-11, che resero questo concetto sempre più incasinato, richiedendo procedure complesse per elaborare stringhe di dati. Il primo passo in avanti giunse nel 1971 grazie a NB o "new B," un linguaggio agile creato da Ritchie che aggiungeva qualche tipo di dati.I tipi di dati erano "int" e "char," utilizzati per memorizzare numeri interi e numeri interi che corrispondono a caratteri (lettere). NB consentì ai programmatori di indicare singole lettere e numeri ma intere serie di essi. Così nel caso in cui avessi voluto memorizzare questo mio articolo, avrei potuto specificare una singola variabile con un singolo nome, facendo sì che quella singola variabile corrispondesse ad una lista di diverse migliaia di caratteri consecutivi. Questi nomi di variabili singole venivano associate a degli indirizzi di memoria consentendo un livello di organizzazione dei dati flessibile e comprensibile per la macchina.Durante la transizione da B a C vennero sviluppare altre migliorie, tipi di dati più generalizzabili, traducibilità diretta in linguaggio assembly, insomma, Ritchie era andato oltre B e "NB" creando qualcosa di diverso: C.
Pubblicità
Dopo il battesimo del linguaggio le cose cominciarono a muoversi molto velocemente e C a somigliare sempre di più a quello che utilizziamo oggi, inclusa l'aggiunta di operatori booleani come && e || per specificare due espressioni differenti e confrontarle. L'operatore booleano && fa sì che il computer restituisca come output "true" se entrambe le espressioni sono vere o "false" se non lo sono. L'operatore ||, invece, restituisce "true" nel caso in cui siano vere solo una o entrambe. Questi mattoncini della programmazione sono fondamentali non solo per C ma per qualsiasi altro linguaggio di programmazione successivo.Nello stesso periodo emerse anche il concetto di "preprocessore" che consentì di includere in un programma interi file e librerie di codice utilizzando la semplice istruzione "#include". Un'altra innovazione fu la definizione delle macro, praticamente insiemi di istruzioni: tramite l'espressione "#define", un breve frammento di codice viene associato ad un nome. Dal punto di vista concettuale il preprocessore è un modo per ordinare al compilatore di eseguire del lavoro extra prima di compilare il programma.Ancora una volta: ci troviamo di fronte alla nascita di uno strumento di programmazione fondamentale che in seguito ha preso piede. Il livello di complessità raggiunto era pronto a svilupparsi nelle sfilze di righe codice che divennero i software successivi.
IL LIBRO BIANCO
Nella decade seguente, vennero scritti compilatori in C per mainframe, minicomputer, microcomputer e infine anche per il PC della IBM. Nel 1983, l'American National Standards Institute (ANSI) iniziò a lavorare su una specifica per rendere C uno standard, con il risultato raggiunto finalmente nel 1989 sotto forma di ANSI C, essenzialmente l'unico e vero C, assicura ai programmatori che il linguaggio si comporti nello stesso modo indipendentemente da come o da dove viene implementato. ANSI C è il primo C definitivo.Il C definitivo assomiglia molto alla definizione astratta di computer. Potrei scrivere proprio ora un programma che manipoli i singoli bit all'interno di un indirizzo di memoria attraverso una macchina concreta, di qualsiasi macchina si tratti, utilizzando degli operatori standard di C. Posso predire con un margine ragionevole di certezza come il mio codice in C sarà tradotto in istruzioni macchina e posso utilizzare questa conoscenza per scrivere del codice più veloce e agile, oppure per scrivere linguaggi di programmazione completamente nuovi. Un alfabeto per scrivere nuovi alfabeti: A,B,C.