Ada è un linguaggio di programmazione nato per volontà del DoD (il ministero della difesa degli Stati Uniti) per impieghi su sistemi embedded in applicazioni di tipo militare. In seguito è stato utilizzato in svariati campi quali avionica civile, nei sistemi di controllo di volo, nella robotica…
Il linguaggio prende il nome dalla contessa Ada Lovelace, considerata la prima programmatrice della storia, e risponde, in modo stringente, ai requisiti di correttezza, sicurezza, manutenibilità e affidabilità.
CENNI STORICI
Perché il DoD impose l’uso di un linguaggio come Ada? Nel 1974 il Dipartimento della Difesa USA si accorse che i costi per lo sviluppo di software erano troppo elevati e la parte maggiore dei costi era dovuta ai sistemi “embedded”. Il Dipartimento condusse, poi, un’indagine sui vari linguaggi allora utilizzati. Da questa analisi si scoprì che due linguaggi erano maggiormente diffusi ognuno con la propria sfera d’attribuzioni: il COBOL per le elaborazioni gestionali e il FORTRAN per il calcolo scientifico e tecnico. Ma la cosa più interessante era il settore embedded. In quest’area il numero dei linguaggi era enorme: la situazione era davvero caotica.
Tutto questo provocava una dilatazione delle spese: dall’acquisto dei compilatori alle spese di manutenzione e addestramento. In una parola: nei sistemi embedded non esisteva una standardizzazione. A questo punto il Dipartimento decise di finanziare una ricerca volta a definire un nuovo linguaggio. In un primo momento si decise, in attesa di avere un unico linguaggio di programmazione, di approvare e introdurre alcuni linguaggi: CMS2Y, CMS2M, SPL/1, TACPOL, JOVIAL J3, JOVIAL J73 e, ovviamente, COBOL e FORTRAN. Il primo passo fu la creazione di un documento ufficiale di riferimento nel quale venivano esposti i requisiti: nel 1975 fu pubblicata la prima versione di Ada con la denominazione di “Strawman”. Il 1983, dopo varie fasi, il lavoro si concluse con la pubblicazione del manuale di riferimento: Language Reference Manual. Il linguaggio fu approvato anche dall’ANSI. I punti focali del linguaggio Ada possono essere così riassunti:
- è un linguaggio fortemente tipizzato;
- prevede una libreria di tasking system integrata con il linguaggio stesso;
- il compilatore deve sottostare ad un processo di validazione per essere utilizzato;
- è supportato il concetto di modularità e di execption.
La distribuzione AVR-Ada è disponibile in codice sorgente e in forma binaria.
LA DISTRIBUZIONE
L’obiettivo della distribuzione AVR-Ada è di utilizzare l’architettura base GCC (il compilatore GNAT). In questa versione la distribuzione ha le seguenti caratteristiche e/o limitazioni:
- un Ada run-time system minimale;
- una AVR support library;
- il compilatore GNAT è basato su gcc.
Probabilmente la pecca maggiore di questa distribuzione è che l’Ada run time system (RTS) non è veramente un run-time system, ma è più un compile time system: la conseguenza di questo, è che il compilatore non supporta la gestione delle eccezioni e nemmeno un tasking system (e quindi un multithreading). Per compensare queste limitazioni, il compilatore dispone di una serie di utility che rendeno il suo uso abbastanza facile e immediato. Tali utility forniscono interfacce per l’accesso in eeprom, la gestione dei timing, la gestione delle periferiche come UART e molto altro ancora.
UN PICCOLO ESEMPIO
Nel listato 1 è riportato un esempio scritto in Ada che implementa l’operazione aritmetica di addizione tra due numeri.
with Ada.Text_IO, Ada.Integer_Text_IO; procedure Somma is s : INTEGER := 0; N : INTEGER; begin Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => “Dare un numero intero tra 1 e 1000: “); Ada.Integer_Text_IO.Get(Item => N); Ada.Text_IO.New_Line(Spacing => 2); if (N >= 1) and (N <= 1000) then for i in INTEGER range 1..N loop s := s + i; end loop; Ada.Text_IO.Put(Item => “La somma dei primi “); Ada.Integer_Text_IO.Put(Item => N, Width => 0); Ada.Text_IO.Put(Item => “ numeri interi positivi vale “); Ada.Integer_Text_IO.Put(Item => s, Width => 0); else Ada.Text_IO.Put(Item => “Il numero dato non e’ valido!”); end if; end Somma;
Listato 1 |
Una volta scritto il programma per compilarlo dobbiamo utilizzare il comando
gcc –c esempio.adb
I programmi scritti in Ada hanno l’estensione .adb o .ads (almeno quelli della distribuzione GCC): i files con estensione .ads sono di solito gli Ada specification file, mentre quelli con .adb sono chiamati Ada body. Di solito un file specification contiene dichiarazioni di variabili, di tipo, interfacce di procedure/funzioni o quant’altro di accesso globale, mentre la parte body sono, di solito, le unità di programma (il body delle procedure e/o funzioni). Se ci sono errori in fase di compilazione, il compilatore produce un diagnostico sul terminale di uscita indicando la riga e la colonna dove si è riscontro l’errore:
main.adb:4:06:”a”is undefined
dove 4 è il numero della riga, 06 è la colonna dov’è presente l’errore, e main.adb è il nome del file. Il messaggio “a” is undefined ci mostra l’uso scorretto della variabile “a” che è stata utilizzata ma mai dichiarata: come vediamo, in Ada la diagnostica è abbastanza precisa e completa. Se, invece, non ci sono errori in compilazione viene generato un file con estensione .ali L’operazione di linking, per produrre l’eseguibile, è attivata dal seguente comando:
gnatbl main.ali
a questo punto si ottiene il file eseguibile.
CONCLUSIONE
Questa distribuzione probabilmente non potrà essere utilizzata in applicazioni commerciali perché è ancora carente (forse ormai obsoleta), ma certamente rappresenta un valido strumento per iniziare a utilizzarlo in un sistema embedded. Questa distribuzione ha comunque il pregio di essere la prima Open per microcontrollori di fascia media piccola.
Uno dei punti di forza dell’Ada è la gestione della memoria. Una botte di ferro rispetto ad altri linguaggi.
Mi interesserebbe cmq capire meglio cosa intendi per “botte di ferro”. Io sono un programmatore C e preferisco avere il controllo pieno della memoria, quando come e se allocarla/deallocarla nello stack o nell’heap.
Non conosco altri linguaggi che offrano possibilità simili (tranne ovviamente l’Assembly), gia’ lo stesso Java o l’ambiente .NET (copiato da Java in molti concetti) offre un garbage collector generico che tendo a evitare come la peste 🙂
Mi spiego meglio: la maggior parte degli sviluppatori (C/C++) che conosco non da’ peso al tipo di allocazione che usa. Questo fa un’enorme differenza quando si vuole scrivere un programma performante rispetto alla velocità. Quando lavoravo in Milestone, una delle direttive principali che tutti dovevano seguire era di evitare le new/malloc come la peste, soprattutto se usata in loop. La New stessa, infatti, su Windows alloca 100 bytes per l’heap manager nativo che ha Windows ed e’ abbastanza lenta. Per quanto questo sia in linea di principio corretto, spostare tutta l’allocazione sullo Stack e’ cmq sbagliato. Prima di tutto perche’ cmq lo stack ha dimensioni limitate, anche se sulle macchine moderne non si nota praticamente piu’. Ma se lavori su embedded, questo si nota eccome. I problemi causati da stack sporco, sono i piu’ complicati da risolvere, poiche’ randomici e insidiosi.
E’ quindi il giusto balance (dato dall’esperienza) per ogni situazione che permette di tirare le viti delle ottimizzazioni in modo da ottenere i risultati voluti. Ecco perche’ la mia esperienza mi dice che uno sviluppatore prima dovrebbe conoscere la memoria e i metodi di allocazione come le sue tasche, e controllarla fino al singolo bit. Quando si e’ “domata” questa fase, ci si puo’ anche affidare a garbage collector, ma sempre con l’occhio vigile e severo nel lasciare che qualcun altro gestisca la memoria per te.
Ciao Giovanni,
Ada mi ha sempre incuriosito e mi ero ripromesso di studiarla, magari compilando su Raspberry. Vista la tua esperienza con Ada, si potrebbe magari pensare a un articolo su questo linguaggio, che ne pensi? Magari poi c’e’ gia’ e non l’ho visto…
Possibilmente lo pianificheremo in un immediato futuro.
Ottimo grazie. Anche qualcosa sul buon vecchio Fortran non sarebbe male (ancora molto usato nei gli algoritmi con forti connotazioni matematiche).