Sommario
< Home
Stampa

Linguaggi di programmazione

Il computer esegue programmi scritti esclusivamente in linguaggio macchina. Questo linguaggio è composto da istruzioni molto semplici, scritte in codice binario, ma che può essere tradotto in linguaggio Assembly per essere leggibile da un programmatore.

Le istruzioni Assembly svolgono operazioni basilari, come il calcolo di espressioni, la verifica di condizioni e salti all’interno del programma. Il codice Assembly dipende dall’architettura hardware della macchina, quindi due processori di due computer differenti avranno set di istruzioni differenti tra loro, e quindi in realtà esistono molti linguaggi Assembly.

Il codice Assembly richiede molte istruzioni per svolgere anche algoritmi semplici per la sua natura “a basso livello”, dipendente cioè dall’architettura del processore e della macchina. Questo è un problema perché nei software di maggiore complessità il programma può diventare troppo grande e complesso per essere gestibile da un programmatore.

Per questo fin dagli albori dell’informatica sono stati progettati linguaggi, detti “ad alto livello” utilizzabili in modo più efficace e che richiedono un tempo accettabile di sviluppo. Questi linguaggi contengono istruzioni complesse, più distanti dall’architettura del computer, ma più vicine al modo di pensare di un essere umano. Vengono infatti introdotte un insieme di astrazioni che consentono di realizzare algoritmi più complessi, e accedere alle risorse del computer senza scrivere un numero eccessivo di istruzioni. Grazie a questi linguaggi i programmi diventano più semplici da scrivere, leggere e modificare.

Per consentire di essere eseguiti dal computer, questi programmi ad alto livello vengono tradotti in linguaggio macchina tramite software automatici.

A seconda della tipologia di linguaggio, sono previste due tecniche tra loro alternative: la compilazione e l’interpretazione.

Compilazione

La compilazione consiste nel tradurre il codice ad alto livello direttamente in codice macchina. Il file sorgente viene analizzato da un software detto compilatore che le traduce in istruzioni e simboli di codice macchina, in modo ottimizzato per una specifica architettura. La compilazione viene effettuata una volta soltanto e procuce un file detto oggetto. Se sono presenti errori, il compilatore li rileva e li segnala al programmatore, interrompendo l’operazione.

Il compilatore deve essere specifico per l’architettura hardware dove verrà eseguito il codice. Quindi lo stesso programma dovrà essere compilato separatamente per ogni piattaforma (es. Windows, Linux, ecc.).

Il codice oggetto però non è sufficiente. Per essere eseguito un programma ha bisogno di utilizzare un insieme di librerie esterne per accedere a determinate risorse (ad esempio per scrivere su file, per accedere alla rete, ecc.). Queste di norma sono fornite insieme al compilatore.

E’ necessaria quindi una operazione chiamata linking, che consiste ne creare un pacchetto contenente sia il codice oggetto che le librerie. Il risultato viene chiamato build, ed assume la forma di una applicazione eseguibile (un file .exe, una app mobile, un eseguibile per server, ecc.).

Qui lo schema delle azioni intraprese:

I compilatori odierni sono estremamente efficienti e si riesce ad ottenere codice macchina che ha prestazioni quasi identiche a quelle che si avrebbero con un programma scritto direttamente in assembly.

Interpretazione

L’interpretazione è una modalità alternativa per cui il programma non viene compilato ma subito eseguito. E’ previsto un software, detto runtime, che crea un ambiente di esecuzione per il programma e al suo interno esiste un interprete che legge il codice una riga alla volta e lo esegue nel runtime, memorizzando le variabili, eseguendo le operazioni di input/output, ecc. Se sono presenti errori, l’esecuzione si interrompe e viene segnalato l’errore all’utente (alcuni interpreti fanno una pre-analisi del codice per verificare che non ci siano errori prima dell’esecuzione).

Se un programma utilizza librerie esterne (le librerie interne sono già presenti nel runtime), può essere necessario preparare prima la build, che contiene tutto il codice che deve essere eseguito.

L’esecuzione di codice interpretato ha il grande vantaggio di non dover scrivere un compilatore. Questo significa però prestazioni inferiori, perchè ad ogni esecuzione il codice deve essere prima interpretato e poi eseguito.

A partire da questi due modelli, negli ultimi decenni sono state sviluppate due tecniche ibride di grande diffusione: la VM e la compilazione JIT.

La Virtual Machine e la compilazione JIT

Alcuni linguaggi (mondo Java e mondo .NET C#) hanno introdotto un meccanismo basato su un doppio passaggio.

Con questa tecnologia il codice originale viene compilato in un linguaggio macchina speciale, detto Bytecode in Java e IL in .NET. Questo codice macchina è quello di una macchina virtuale (JVM Java Virtual Machine per Java e CLR Common Languare Runtime per .NET). E’ quindi necessario installare questa macchina virtuale nel computer per poter eseguire il programma, ma siccome i programmi scritti in Java o .NET sono molti, è spesso già preinstallata col sistema operativo.

L’obiettivo è la portabilità: non serve più un compilatore specifico per piattaforma, basta avere la virtual machine. Tuttavia lo svantaggio sono le prestazioni, più simili a quelle di un linguaggio interpretato.

Pertanto tutte le JVM e i CLR si sono evoluti, ed hanno implementato un meccanismo di compilazione automatica. La prima volta che viene eseguito il codice della macchina virtuale questo viene compilato in codice macchina nativo, eliminando quindi il problema delle prestazioni. Questa tecnica si chiama compilazione JIT (“Just in time”) e cerca di unire i vantaggi della portabilità a quelli delle prestazioni. 

La compilazione JIT è stata poi estesa anche a molti linguaggi interpretati, in particolare su Javascript, che offre oggi prestazioni simili a quelle di un programma compilato.

Conclusioni

La tipologia di linguaggio se interpretato o compilato ha impatti sul tipo di applicazione che si vuole realizzare:

  • se si vogliono le massime prestazioni, è necessario usare un linguaggio compilato, e tra questi sicuramente il C++ rappresenta la soluzione migliore. Infatti è utilizzato nella maggior parte delle applicazioni di calcolo, nei videogiochi, nel machine learning, nelle applicazioni real time. Non solo ma molte librerie esterne di linguaggi interpretati sono scritte in C++ proprio per garantire loro efficienza, nelle attività dove c’è bisogno di prestazioni. Ne sono un esempio Javascript e Python, dove nel primo caso l’interprete è scritto in C++, e nel secondo sono scritte le principali librerie.
  • i linguaggi interpretati sono in generale più ad alto livello dei compilati, hanno un maggior numero di librerie semplici. La compilazione JIT garantisce, in particolare per Javascript, prestazioni eccellenti risultando un buon compromesso.

In generale comunque la scelta del linguaggio non dipende solo dal fatto che sia interpretato o compilato, ma anche da altri fattori di natura tecnica:

  • il tipo di applicazione che si vuole realizzare;
  • le librerie a disposizione: alcuni linguaggi offrono librerie specifiche per determinati classi di problemi, risultando più adatti.
  • le competenze dei programmatori: C++ è certamente più difficile di Python e per svolgere la stessa operazione ci vogliono più righe di codice;
  • il paradigma di programmazione, come vedremo, che a sua volta è determinato sia da una visione di insieme del programmatore, sia da esigenze specifiche del progetto;
  • concetti come manutenibilità, scalabilità, estensibilità e sicurezza di un linguaggio. Alcuni linguaggi sono più facili da debuggare, altri si prestano meglio nel gestire problematiche di sicurezza.

Esistono poi fattori di natura non tecnica ma organizzativa come ad esempio:

  • il know how del team di sviluppo;
  • vincoli imposti dal committente;
  • la presenza di altri software scritti con un altro linguaggio con cui si vuole mantenere compatibilità.