Capitolo 1 - Ciclo di sviluppo di un programma

 

 

 

 

            1.1 - La scrittura del testo

 

 

                Lo svolgimento ordinato delle operazioni della macchina richiede un programma, che viene scritto nella forma di un testo leggibile, in genere utilizzando un ordinario text-editor, ad esempio il programma edit del sistema MS-DOS.

 

                Col termine text-editor si indica un programma capace di produrre file direttamente stampabili con il comando print e leggibili a video, detti file di testo, o ASCII-standard[1]; in tali testi non è possibile generare caratteristiche grafiche speciali, come corsivo, grassetto, caratteri variabili in colori, spaziatura, grandezza e tipo (font).

 

                In un file ASCII-standard ogni byte (carattere) contiene solo valori corrispondenti a simboli leggibili (valori ASCII 32-127), con le sole eccezioni dei segnali associati convenzionalmente a funzioni specifiche, come tabulazione verticale e ritorno a capo (ASCII 10 e 13), inizio pagina (ASCII 12), e fine testo (ASCII 26), oltre eventualmente tabulazione (ASCII 9), tutti evidentemente necessari per la rappresentazione ordinata del testo.

 

                Col termine wordprocessor si indicano invece strumenti più complessi, come WordStar, Word o Write per Windows, WordPerfect, e simili, capaci di organizzare il testo secondo sofisticate esigenze grafiche, cosa che richiede l'impiego di codici ASCII al di fuori dell'intervallo dei caratteri rappresentabili, utilizzati inoltre in sequenze complesse. E' pure possibile che un simile programma sia predisposto per la trattazione di caratteri secondo la convenzione detta Unicode, che dedica maggior spazio alla rappresentazione di ogni carattere grafico e rende possibile la scrittura in alfabeti più estesi, ad esempio cinese o giapponese.

 

                Pure contenendo testi, i file ottenuti con un wordprocessor non rientrano nella categoria dei file di testo, ma in quella più generale dei file binari, che non pongono restrizioni sul contenuto (valore) di ogni singolo byte.

 

                I testi generati da un wordprocessor nella propria modalità normale non sono in genere tali da essere trattati dai programmi essenziali per la programmazione, detti compilatori, che possono interpretare solo sequenze di caratteri ASCII-standard.

 

                Anche se è possibile fare in modo che un wordprocessor generi testi con queste caratteristiche, le procedure di attivazione ed uscita di simili programmi, specialmente con la trattazione di eccezioni come la generazione di file di testo, li rendono scomodi ai fini della programmazione, da cui la preferenza per i semplici text-editor.

 

                Vi sono eccezioni, come ad esempio i vecchi ambienti Quick e gli attuali Visual della Microsoft (non ne sono menzianati altri simili di altri produttori), in cui il testo del programma viene rappresentato con colori e caratteristiche grafiche varie al fine di evidenziarne le diverse componenti strutturali: commenti, parole chiave, procedure, ecc.

 

                Questo diverso tipo di compilatori è nato per semplificare l'approccio alla programmazione, nascondendo almeno in parte la sequenza di operazioni concettualmente e logicamente distinte da attivare nel corso dello sviluppo del programma. Attualmente essi costituiscono l'ambiente dominante nella programmazione, che avviene principalmente in ambiente Windows, o negli X-windows, che ne costituiscono una specie di equivalente in sistemi Unix.

 

                La loro semplice impostazione iniziale é però stata rapidamente superata dalla evoluzione degli strumenti, ormai orientati prevalentemente allo sviluppo di progetti che coinvolgono più programmi, piuttosto che a quello di un singolo programma del tipo adatto alla didattica ordinaria, in genere di scopo molto limitato, piccolo e strutturalmente semplice.

 

                Ai fini dell'apprendistato nella programmazione sembra quindi più opportuna la sequenza di sviluppo tradizionale, detta anche on-line, nelle diverse fasi:

 

(1.1.1)                     - scrittura del testo del programma;

                               - compilazione, o traduzione in linguaggio macchina;

                               - link, o collegamento alle librerie per generare la versione eseguibile;

                               - esecuzione del programma.

 

                Tale opinione è naturalmente discutibile, e potrebbero portarsi argomenti opposti a favore dello sviluppo in un ambiente integrato (Turbo, Quick, ecc.) con caratteristiche totalmente interattive, in contrapposizione all’impostazione "a tappe" o batch. Chi scrive ritiene invece assai utile alla comprensione degli strumenti la più laboriosa frammentazione on-line, sulla base della ripetuta constatazione nella pratica didattica che da essa é facile passare agli ambienti integrati, mentre non é vero il contrario, ove questo si renda necessario; ad esempio allo scopo di sperimentare in modo dinamico ed attivo il processo di analisi che conduce alla individuazione degli errori.

 

                Nelle aule attrezzate presso il Dipartimento di Matematica sono disponibili entrambi i tipi di strumenti, dei quali la organizzazione on-line è quella utilizzata normalmente per la didattica ordinaria con C e Fortran, mentre per il Pascal ed il Basic si impiegano rispettivamente gli ambienti TurboPascal e QuickBasic. Esistono comunque le versioni Visual (per Windows) di C++, Basic e Fortran.

 

 

 

 

 

 

            1.2.- Fase preliminare: l'analisi strutturale

 

                Prima di esaminare in dettaglio le quatto fasi in (1.1.1) é bene considerare brevemente cosa viene prima della fase che figura all'inizio, cioè della scrittura del testo, secondo il seguente principio:

 

Ogni programma ha per scopo la soluzione di un problema, dalla prima semplice scrittura di una riga a video, fino, ad esempio, all'ottimizzazione del flusso del traffico in una rete di comunicazione.

 

                Indipendentemente dalla complessità del problema, non si dovrebbe mai iniziare la scrittura di un programma senza averne preventivamente effettuato l'analisi, che consideriamo qui solo per sommi capi, essendone l'approfondimento uno degli obiettivi dello sviluppo degli argomenti trattati.

 

                Tale attività, di natura eminentemente logica, consiste nella specificazione di tre elementi:

 

  (1.2.1)                   (a) - l'insieme dei dati del problema,

                               (b) - l'insieme dei risultati che ci si attende dalla sua soluzione,

                               (c) - il metodo operativo, o algoritmo, o elaborazione, con cui da (a) si giunge a (b),

 

il che é una evidente variante dello schema logico consueto del ragionamento matematico:

 

  (1.2.2)                   ipotesi-tesi-dimostrazione.

 

                Ad esempio, nel problema molto semplice posto dalla richiesta di soluzione di una equazione di secondo grado, una possibile formulazione dell’analisi é la seguente.

 

                (a) - dato l'insieme ordinato di tre numeri reali, i coefficienti a, b, c

                (b) - generare una riga di scrittura che descriva il tipo di soluzione ed i valori delle radici

                (c) - utilizzando la classica formula risolvente, con diversificazione su tutti i casi possibili.

 

                E' evidente che tale schema di massima é solo il punto di partenza, di cui ogni elemento deve ancora essere sviluppato con maggiori dettagli, in questo caso soprattutto per la struttura delle operazioni dell'algoritmo, cioè per i dettagli di calcolo ed il controllo delle alternative possibili. In casi più complessi l'analisi strutturale deve essere estesa anche ai dati.             Non é facile, né forse didatticamente opportuno presentare in termini molto astratti gli elementi logici dell'analisi; può invece essere più produttivo individuarli dal crescere delle complessità degli esempi utilizzati come supporto per lo sviluppo del  linguaggio.

 

 

 

            1.3.- Prima stesura del testo e modifiche successive

 

                Appresi gli elementi base della sintassi del linguaggio ed eseguita l'analisi del problema, é sufficiente richiedere l'attivazione del programma per la scrittura dei testi, assegnando il nome del programma stesso in modo tale che esso possa venire memorizzato come file sul disco.

 

                Nei sistemi MS-DOS é previsto che i nomi dei file siano composti da due parti: nome proprio ed estensione, scritti separati da un punto; per i programmi C non é obbligatorio, ma é molto conveniente, che la estensione sia .c, al fine di esemplificare le trattazioni successive, utilizzando procedure di sistema predefinite basate sul riconoscimento della estensione; ad esempio, si scriverà allora:

 

  (1.3.1)                   edit primo.c

 

e per la scrittura effettiva il riferimento é alle semplici e naturali convenzioni del programma edit, il cui impiego é fortemente agevolato dalla tecnica dei menù a tendina. Si consulti al proposito la scheda #1, "Macchine ed ambienti operativi", oltre ovviamente ai manuali di sistema, e si utilizzi il comando:

 

  (1.3.2)                   help edit

 

che fornisce istruzioni esaurienti per l'uso del programma. Agli stessi contesti si rinvia anche per l’indispensabile familiarizzazione di base con il sistema operativo, in particolare per la trattazione dei file su disco.

 

                Ci limitiamo a ricordare che utilizzando ogni text-editor valgono alcuni principi di tipo generale:

 

      La prima cosa da apprendere, dopo il modo (o comando) di attivazione, é quella di uscita dal programma alla conclusione delle operazioni di scrittura; nel caso del programma edit ciò si ottiene attivando il primo menù a tendina, quello intestato File, cosa che può farsi con il mouse, oppure con la combinazione di tasti  Alt-F (premuti contemporaneamente).

 

      Nell'utilizzazione dell'editor si deve distinguere tra due contesti: la composizione del testo e l'emissione di comandi che controllano il comportamento del programma di scrittura; nei programmi controllati da menù di norma ogni tasto serve al primo scopo, mentre solo certe combinazioni di tasti funzionali danno accesso alla seconda modalità. In altri programma, come pe2 o e3 della IBM (Personal Editor, versione II o III) una riga evidenziata in modo particolare - detta riga di comando - serve per le richieste di azioni di controllo ed il resto dello schermo è la finestra del testo; il passaggio da un contesto all'altro si ottiene con il tasto Esc (da Escape, o fuga).

 

      All'uscita delle operazioni é possibile memorizzare su disco tutte le modifiche al testo, oppure scartarle lasciando invariato l'originale; con edit si tratta di rispondere 'Si' o 'No' ad una apposita domanda del programma, mentre nell'altro caso citato, quello del pe2, si hanno a disposizione due diversi tasti (di norma F3 ed F4) per i due diversi scopi.

 

 

                Il linguaggio C ammette la scrittura del testo in forma quasi totalmente libera, a differenza di quanto accade, ad esempio, in Fortran o in Assembler, che richiedono la scrittura delle istruzioni del programma una sola per riga, con una divisione rigida in zone per il primo e con una successione strutturale per il secondo.

 

                Il C ammette invece più istruzioni sulla stessa riga, o una istruzione divisa su più righe senza formalità particolari, salvo poche eccezioni. Prevede invece la divisione tra istruzioni e metaistruzioni, queste ultime dette anche comandi per il compilatore e sottoposte a restrizioni un po’ più severe rispetto alle prime.; vi è una ulteriore eccezione, data dalla possibilità di inserire istruzioni ‘in linea’ scritte in Assembler.


 

            1.4. - La compilazione - programma oggetto

 

                E' necessario conoscere quale particolare versione, o implementazione del linguaggio, che si sta utilizzando, perché tra diversi sistemi, o anche tra diverse versioni nello stesso sistema vi possono essere differenze notevoli; nel caso dell'aula esso è il compilatore on-line Microsoft C vers. 6.0, il cui manuale è interamente disponibile su disco ed é consultabile tramite il programma QuickHelp digitando:

 

  (1.4.1)                   qh

 

che permette, tramite menù a tendina e mouse, di percorrere l'intera documentazione con la tecnica degli ipertesti ( parole chiave di riferimento e percorsi di lettura dinamici ).

 

                E' necessario rendersi conto in modo almeno minimale del tipo di lavoro svolto dal compilatore: esso è un programma, fornito dal costruttore che ha implementato il linguaggio, che accetta come insieme di dati su cui operare un testo scritto secondo le regole formali del C.Tale testo viene elaborato in due fasi, di cui la prima divisa in almeno due sottofasi che però al momento non interessano:

 

  (1.4.2)                   - controllo dell'osservanza della sintassi;

                               - in caso positivo, generazione dell'equivalente del testo in linguaggio macchina.

 

                La prima necessità è ovvia: senza il rispetto rigoroso delle regole del linguaggio non ci si possono attendere azioni coerenti da parte della macchina; la prima fase si può quindi concludere con una lista di messaggi di errore che indicano in quali righe del testo é stata violata la sintassi, in genere accompagnando l'indicazione con un commento esplicativo.

 

                Nella maggior parte dei casi le inosservanze della sintassi sono etichettate come errori fatali, tali cioè da rendere impossibile la successiva traduzione; in questo caso non si ha altra possibilità che tornare con il  text-editor sulle righe interessate e correggere gli errori.

 

                Poiché una riga che contiene un errore non può più essere considerata in modo coerente nella verifica globale del programma, e viene quindi ignorata, accade spesso che nelle successive vengano segnalati errori 'fantasma', che cioè non sono tali, ma solo conseguenze dell’eliminazione detta; ai fini della correzione, in caso di dubbi, conviene limitarsi a correggere il primo errore segnalato e riavviare la compilazione, a meno che altri errori successivi non siano palesemente indipendenti.

 

                In altri casi si hanno errori etichettati come avvertimenti, o warning; non si tratta di violazioni formali strette della sintassi, ma di possibili incoerenze logiche, che non sempre il compilatore può determinare con certezza. Uno dei più tipici è l'impiego di una variabile in una espressione aritmetica senza che in precedenza essa abbia ricevuto un valore; un altro la definizione esplicita di una variabile che non risulta però utilizzata nelle istruzioni operative del programma.

 

                In caso di avvertimenti il programma é molto probabilmente scorretto nella logica delle operazioni, pure essendo formalmente traducibile in linguaggio macchina, perchè essi non bloccano, salvo esplicita richiesta contraria. E' però opportuno eseguire verifiche e probabilmente correzioni, perché quasi certamente i risultati della esecuzione saranno incoerenti; discuteremo più a fondo questo argomento nella parte conclusiva sugli errori logici.

 

                Per quanto riguarda il comando di compilazione, il Microsoft C fornisce un comando di uso generale  cl  (compile & link), che può attivare anche l’opzione di link, della quale trattiamo più avanti; nella forma per la sola compilazione il comando é, supposto ad esempio che il nome del programma sia primo.c:

 

  (1.4.3)                   cl /c primo.c

 

in cui cl é la richiesta di azione (compilazione), primo.c è l'oggetto, o argomento dell'azione e /c è una opzione (potrebbe non esservi) che richiede la sola compilazione, senza procedere al link.


                In alternativa si potrebbe richiedere

 

  (1.4.4)                   cl primo.c

 

con cui si ottiene anche il passaggio alla fase di link; per almeno i primi programmi questo é sconsigliabile, perché i vari messaggi automatici ed i possibili errori delle due fasi rendono difficile seguire il procedimento; inoltre, in questa forma il comando utilizza solo opzioni standard, spesso insufficienti.

 

                Un altro esempio é:

 

  (1.4.5)                   cl /c /AL primo.c

 

in cui le opzioni sono due, delle quali /AL richiede l'impiego del cosiddetto modello di memoria grande, senza il quale il programma sarebbe soggetto a forti vincoli di spazio sia per le istruzioni, che per i dati. La tecnica d'impiego di diversi modelli per la trattazione della memoria piccola, compatta, grande ed ‘enorme’ é specifica del sistema operativo MS-DOS e non trova riscontro in altri ambienti, o sistemi operativi, quali ad esempio il VMS della Digital o molte versioni del sistema Unix.

 

                La (1.4.5) é forse la forma 'più normale' del comando di compilazione; essa mostra anche bene come purtroppo l'impiego dei comandi on-line sia noiosamente ripetitivo ed atto a generare frustranti errori di battitura; mostreremo più avanti una conveniente alternativa.

 

                Se la compilazione ha successo, essa si conclude con la generazione su disco di un nuovo file, che di norma ha lo stesso nome di quello originario, ma estensione .obj, che sta per linguaggio oggetto, sinonimo di linguaggio macchina; il testo leggibile originario é cioè stato tradotto in una sequenza funzionalmente equivalente di comandi elementari di macchina, che in genere non interessano direttamente il programmatore C.

 

                Un file di tipo .obj é cioè un insieme di pure informazioni binarie, non destinato alla lettura, (come si prova facilmente se per errore si cerca di operare su di esso con il text-editor), ma solo all'uso interno da parte della macchina. Esso é in ogni caso soltanto un prodotto temporaneo, da utilizzare nella fase successiva ed in genere da cancellare dal disco alla conclusione delle operazioni, salvo specifiche esigenze contrarie.

 

 

 

 

 

 

            1.5.- Il link - programma eseguibile

 

                La versione oggetto di un programma, pure essendo composta di istruzioni direttamente eseguibili dal calcolatore e da dati che hanno la forma corretta per le esigenze di gestione della memoria, é però incompleta e non permette ancora il passaggio all'esecuzione.

 

                Ad esempio, il programma potrebbe utilizzare operazioni aritmetiche complesse, come il calcolo di una radice quadrata, di un logaritmo o di una funzione trigonometrica; nessuna di esse può essere ridotta ad una operazione elementare, eseguita ricorrendo ad un apposito 'circuito', almeno in configurazioni ragionevoli di macchine.

 

                Le operazioni che conducono al risultato debbono cioè essere organizzate in un flusso più o meno complesso, vale a dire in un apposito programma. Poiché é evidentemente assurdo che l'utente del linguaggio di programmazione debba ricostruirsi risorse di questo tipo, esse sono state messe a punto una volta per tutte e messe a disposizione nella libreria standard di programmi forniti con il compilatore, già pronti nella forma di linguaggio macchina e quindi disponibili per essere 'connessi' con le istruzioni specifiche di un programma particolare, da cui il termine 'link', o 'collegamento'.


 

                Queste considerazioni non riguardano solo programmi ‘ausiliari’ di calcolo, ma anche altri aspetti, quali ad esempio i dettagli delle operazioni di Input/Output.

 

                Infine, é in generale necessario che la versione eseguibile del programma contenga una parte, detta di testata (header), che ne permette l'interfacciamento con il sistema operativo, senza cui il caricamento in memoria centrale ed il controllo dell'esecuzione sarebbero pressoché impossibili.

 

                A questi compiti, ossia completamento con i programmi ausiliari rilevati dalla libreria, costruzione della testata del programma, ed altri ancora che non interessano questo contesto, provvede il programma  link, che accetta come dato un file di tipo .obj e ne produce uno, che di norma ha lo stesso nome, e la cui estensione è .exe. Ciò almeno come procedura predefinita, rispetto alla quale si possono introdurre varianti, quali ad esempio un diverso nome ed una diversa estensione.

 

                Nella forma più semplice, il comando è (utilizziamo sempre il medesimo nome come esempio):

 

  (1.5.1)                   link primo,,nul;

 

in cui:

     (a) la posizione vuota tra le virgole indica che per la versione eseguibile si assume lo stesso nome (primo);

 

     (b) la parola speciale nul che non é richiesta una mappa degli indirizzi di memoria (listato per stampa);

 

     (c)  il  punto e virgola  finale che é sufficiente  il riferimento normale alla libreria standard: si potrebbero

           avere esigenze diverse, ad esempio per programmi che utilizzano la grafica.

 

                Non approfondiamo oltre, rinviando ancora al programma qh di assistenza in linea, che illustra tutti i dettagli dei comandi cl e link.

 

                E' comunque evidente che anche in questo caso il loro impiego esplicito obbliga a fastidiose ripetizioni; per questo motivo sui sistemi dell'aula è stato inserito uno pseudo-comando (o procedura batch) clink , da attivare nella forma, che utilizza ancora il nome precedente come esempio:

 

  (1.5.2)                   clink primo

 

da scrivere SENZA l’estensione .c, che evita la digitazione delle sequenze ripetitive.

 

 

                Anche la fase di link può evidenziare errori, essi pure distinti tra fatali ed avvertimenti; essi non derivano però dalla sintassi (sarebbero emersi nella compilazione), ma riguardano legami di relazione evidenziabili solo con il link; essi sono cioè legati alla logica generale del programma, non alla sua forma.

 

                Il caso più comune riguarda l'assenza di una funzione specifica, come può ad esempio accadere scrivendo sqrp(x) invece di sqrt(x) per la richiesta di una radice quadrata: l'inesistenza di tale risorsa non può essere rilevata in fase di compilazione.

 

                Casi più complessi si hanno quando un sottoprogramma viene menzionato con dati scorretti, come ad esempio con sqrt(x,y), in cui la richiesta viene fatta presentando due argomenti, mentre la procedura di calcolo é definita per uno solo, oppure una scorretta definizione di dati, quando l’intero programma é diviso in due o più testi distinti.

 

                Si deve però dire che il linguaggio C è molto più severo di altri, ad esempio del Fortran e del Basic, nella definizione delle risorse fondamentali: dati e funzioni, con la conseguenza che molti errori della logica strutturale che gli ultimi due possono rivelare solo nella fase di link, per il C sono invece ‘intrappolabili’ già nella compilazione.

 

 

 

 

                Anche la comparsa di errori nel link comporta il ritorno al text-editor e la correzione del testo del programma; gli errori di questo tipo richiedono in genere un'analisi più accurata rispetto ai casi di violazione della sintassi, relativamente assai più semplici.

 

 

 

            1.6.- Fase finale: l'esecuzione

 

                Ottenuta la versione .exe tutto é pronto per il passaggio all'esecuzione, cosa che in MS-DOS  avviene semplicemente digitando il nome del programma (l'estensione non è necessaria); nel nostro esempio:

 

  (1.6.1)                   primo

 

che per il sistema operativo significa: individua un programma eseguibile di nome primo, caricalo nella memoria ed avviane le operazioni dalla prima istruzione eseguibile.

 

                Cosa accade da questo momento in avanti dipende esclusivamente dalla logica interna del programma eseguito; prima e, se tutto va bene, anche dopo, il controllo della macchina, ossia del suo microprocessore centrale, é dato al sistema operativo che é, per così dire, il ‘padrone’ di risorse come ad esempio lo schermo e la tastiera. Dopo l’avviamento tale ruolo spetta al programma messo in esecuzione, che ha a disposizione tutte (o quasi) le risorse della macchina.

 

                Avviate le operazioni, esse procedono senza interruzione, a meno che il programma preveda l’immissione dei dati da tastiera, nel qual caso il cursore torna a lampeggiare evidenziando lo stato di attesa, o si impiegano altri modi più o meno equivalenti per sincronizzarsi con l'operatore esterno.

 

                Gli errori più complessi si presentano in questa fase ed hanno natura esclusivamente logica; é abbastanza normale, e non solo per principianti, che un programma che deve stabilire se un numero intero é primo, concluda con la scrittura 17 é un numero primo, ma subito dopo anche con 25 é un numero primo, oppure che un programma che dovrebbe ordinare una lista di numeri li presenti alla fine in un modo apparentemente casuale, magari eliminandone alcuni e ripetendone altri.

 

                L'individuazione degli errori logici è spesso complessa e spesso dovuta a carenze nell'analisi della struttura logica del programma, che deve quindi essere revisionata a fondo, avendo come guida gli stessi errori riscontrati nell'esecuzione.

 

                Solo raramente la causa dell'errore é banale; se ad esempio una variabile di nome punto che il programma utilizza in una serie di calcoli é stata erroneamente scritta come punta, ne deriva una incongruenza dei valori dal momento che il nome errato gioca un ruolo nel calcolo. Questo tipo di errore, comune in Fortran ed in Basic, é però escluso dalla struttura stessa del C, che impone la dichiarazione esplicita del genere o tipo di valore associato al nome di una variabile, in modo che il nome scritto per errore comporta automaticamente un errore di sintassi.

 

                Se però l'errore è in un impiego logicamente scorretto di nomi o espressioni, come ad esempio l'inversione tra numeratore e denominatore in una divisione, non esiste criterio automatico che possa individuarlo: in entrambi i modi, sia in quello giusto che in quello errato, l'azione richiesta alla macchina é legittima e coerente, e non si può pretendere che essa sia in grado di interpretare le intenzioni del programmatore, ma solo di eseguire alla lettera le istruzioni.

 

                Si osservi che anche questo errore, come quello citato in precedenza, ha la conseguenza che nel corso delle operazioni, da un certo punto in poi, il valore di certe variabili non concorda con quanto l'algoritmo basato sull'analisi del programma richiederebbe. Se si riesce a determinare tale punto, l'errore logico è individuato.

 

                Quest’ultima osservazione é particolarmente importante, per quanto essa possa sembrare ovvia; é infatti da essa che derivano le tecniche per l’individuazione degli errori logici.


 

 

            1.7 - Errori logici di tipo strutturale.

 

 

                Gli errori logici sono molto frequenti, specialmente durante l'apprendistato della programmazione, quando i problemi derivanti dall'adattamento ad un nuovo ambiente prevalgono sulle difficoltà della struttura degli algoritmi e dei dati, che peraltro in questa fase sono in genere elementari.

 

                Le difficoltà reali iniziano quando si affrontano problemi di una certa complessità, che si traducono in algoritmi la cui analisi é lunga e laboriosa, per i quali la probabilità di errori nella logica del flusso delle operazioni si fanno più consistenti.

 

                In tali casi l'individuazione dell'errore impone un riesame della struttura logica del programma, compito tanto più arduo quanto più il testo é lungo, ma con una relazione moltiplicativa più vicina all'esplosione esponenziale che alla linearità.

 

                E' quindi assai importante abituarsi subito all'analisi strutturale, che tende a dividere ogni problema in sottoproblemi più semplici, e se necessario questi in sotto-sottoproblemi, in modo che in ogni istante il contesto che si deve analizzare in dettaglio risulti il più limitato possibile, sia per le singole operazioni, che per le relazioni tra di esse. Si potrebbe dire che la cartina di tornasole per verificare le proprie capacità nel campo della programmazione è proprio la rapidità con cui si riesce ad apprendere l'arte della strutturazione.

 

                Ed il termine arte é stato usato non per caso, a partire dallo stesso Knuth, (i cui contributi in questo campo sono tra i più essenziali), per indicare un genere di attività che non può essere sistematizzata, ed ancora meno automatizzata, se non a grandi linee.

 

                Un programma ben strutturato é in genere composto da un insieme di sottoprogrammi, che in C sono sempre detti funzioni, legate tra loro in un flusso logico definito dal programma principale; non é però vero il reciproco, ossia un programma diviso in una serie di funzioni può certamente essere ampiamente illogico. Come norma di tipo generale, e con ampie eccezioni, una unità di programma (il senso ne sarà più chiaro in seguito) che superi le 30-40 righe deve da questo punto di vista considerarsi sospetta e candidata per un'analisi più approfondita.

 

                L'impiego di sottoprogrammi e funzioni comporta problemi di scambio di informazioni (in genere dati) tra le varie unità logiche che compongono il programma, cosa che in C viene effettuata con l'impiego di parametri, oppure con la definizione di uno stato speciale per certe risorse (ad esempio variabili e funzioni), che sono dette globali  e sono accessibili a diverse unità.

 

                Questo é spesso il terreno più delicato, su cui si verificano gli errori logici più difficili da individuare, derivanti da incoerenze nell'impiego di parametri o di zone di dati ad accesso comune; anche da questo punto di vista il C si rivela come linguaggio particolarmente ‘robusto’, a differenza dei soliti Fortran a Basic, per l’esistenza di un elemento sintattico, detto prototipo di una funzione, capace di ridurre virtualmente a zero gli errori di questo tipo.

 

                Per completare questo argomento, si deve dire ancora che la grande abbondanza di operatori  e di strutture per la definizione di dati che sono indirizzi di memoria (puntatori) di cui il C dispone, per di più con notazioni estremamente sintetiche, rendo particolarmente alto il rischio di scrittura di sequenze operative formalmente corrette ma logicamente prive di senso.

 

                Questo fatto, e l’esistenza di alcune convenzioni implicite per certi contesti rendono il linguaggio C particolarmente insidioso per il principiante; i relativi pericoli e qualche modo per evitarli verranno in genere segnalati quando se ne presenti la necessità.


 

 

            1.8 - Debug o ricerca degli errori

 

 

                Nel gergo informatico gli errori logici dei programmi sono detti bug (più o meno, pidocchi) e di conseguenza si chiama debug (spidocchiamento) l'attività della loro ricerca ed individuazione.

 

                La revisione dell'analisi e la rilettura del testo del programma per verificarne la rispondenza con quanto previsto dall'analisi stessa sono gli strumenti primari, ma se i programmi hanno una certa lunghezza e complessità il tempo richiesto per le revisioni successive può essere molto alto.

 

                Vi sono metodi di controllo che in genere si imparano molto in fretta; il più semplice ed efficace é l'inserimento in punti che si ritengono critici di richieste di scrittura il cui senso sia:

 

  (1.8.1)                   é stata eseguita l'istruzione al punto tale e le tali variabili hanno questo valore

 

il cui esame é spesso in grado di rivelare rapidamente a quale punto dell'esecuzione i valori iniziano ad essere incoerenti.

 

                Scritture di questo tipo non hanno nulla a che fare con la logica dell'algoritmo in sé, ma applicare con efficienza questa tecnica, ossia individuare i punti in cui potrebbero insorgere problemi, é di per sé un ottimo esercizio di analisi logica, tanto che chi le utilizza abitualmente preferisce pianificarle già dal corso della prima scrittura del programma, per la grande efficacia al fine della comprensione della logica del flusso operativo; esse possono quindi divenire un ausilio prezioso per il processo di apprendimento.

 

                L'evoluzione dei compilatori ha condotto i costruttori a mettere a punto programmi specifici per il debug, che permettano di ottenere un grado di controllo sul programma di cui la tecnica del 'fai da te' illustrata sopra é solo il primo passo.

 

                Il suggerimento é venuto dal linguaggio Basic, che fin dall'inizio é stato progettato per poterne seguire l'esecuzione riga per riga direttamente sul testo leggibile del programma, eventualmente modificando in modo dinamico ed interattivo il valore di certe variabili durante l'esecuzione; tale linguaggio non é nemmeno nato per essere compilato, ma solo interpretato, cioè con esecuzione diretta di una riga per volta sul testo, senza considerarne l'intero contesto di cui le righe fanno parte. Il successo ottenuto e le evoluzioni successive hanno condotto allo sviluppo di compilatori anche per il Basic, in grado di velocizzarne l'esecuzione.

 

                Per i compilatori Microsoft viene fornito il programma CodeView, che é detto debugger simbolico, perché permette di eseguire programmi scritti in altri linguaggi con la medesima logica della interpretazione del Basic: eseguendoli un passo per volta, permettendo il controllo dei valori delle variabili in modo saltuario o continuo, arrestando l'esecuzione su righe predeterminate, ed addirittura modificando i valori delle variabili in esecuzione.

 

                Perché un programma C sia disponibile per l'analisi con CodeView esso deve comunque essere compilato, ma esercitando opzioni speciali sia per il compilatore, che per il link, nella forma di richieste aggiuntive nelle righe di comando.

 

                Il lettore interessato può scoprire da sé nel programma di assistenza qh quali sono le opzioni necessarie ed in che modo può essere utilizzato il comando cv di attivazione di CodeView. Per semplificare le operazioni, la procedura batch flink disponibile sui sistemi dell'aula é disponibile anche per richiedere una compilazione che predisponga per il debug. E' sufficiente aggiungere un parametro, costituito dalla sola lettera d,  alla riga di comando, scrivendo  ad esempio:

 

  (1.8.2)                   flink primo d


 

                Si ottiene un programma .exe, esattamente come con (1.5.2), che può essere messo in esecuzione direttamente come in (1.6.1), ed in tal caso non si hanno benefici particolari ai fini del debug, ma solo un programma eseguibile che occupa molto più spazio sul disco ed in memoria.

 

                Esso é però utilizzabile anche in un altro modo; il comando:

 

  (1.8.3)                   cv primo

 

richiede che venga messo in esecuzione il programma CodeView, a cui viene passato come parametro, o argomento, il nome del programma eseguibile (primo.exe), che deve però essere stato ottenuto come in (1.8.2).

 

                Sullo schermo viene presentato un ambiente diviso a finestre, con menù a tendina e sensibile al mouse, che presenta in genere nella parte centrale il testo del programma da analizzare, pronto per l'esecuzione a partire dalla prima riga.

 

                Per il dettaglio dei comandi disponibili si può consultare il programma di help in linea qh alla voce cv, oppure la stessa assistenza (help) di CodeView attivabile dal suo menu o con il tasto F1. Citiamo qui alcuni dei comandi principali, principalmente per dare un'idea delle possibilità operative:

 

                F10 :      esegui solo la riga corrente

                F8 :        entra nel dettaglio delle operazioni della riga corrente (per sottoprogrammi)

                F4 :        interruttore on/off per la finestra di output, cioè per quello che si vedrebbe

                               con l'esecuzione 'normale' del programma

                F7 :        esegui fino alla riga su cui é posizionato il cursore

                F9 :        interruttore on/off per un breakpoint, ossia una riga su cui l'esecuzione deve arrestarsi

 

                Come accade per la tecnica delle scritture 'artigianali', questo strumento, nato per l'analisi degli errori logici, ha rivelato uno straordinario potenziale dal punto di vista didattico; é quindi fortemente consigliabile utilizzarlo fino dai primi esperimenti di programmazione, non tanto per la ricerca di errori, quanto per il grado di comprensione della logica esecutiva che da esso può derivare.

 

 

 

 

 

 

            1.9 - Cenni ad altri ambienti operativi

 

 

                Quanto visto in precedenza é stato  presentato facendo riferimento per i dettagli operativi al tipo di sistema disponibile sulle macchine dell’aula, ossia per il sistema operativo MS-DOS e per la versione di compilatore Microsoft C 6.0.

 

                Anche senza soffermarci sulle varianti del tipo Quick, Turbo o in ambiente Windows, nel Centro di Calcolo del Dipartimento di Matematica sono però disponibili almeno altri tre ambienti operativi: quello dei sistemi Macintosh, il VMS della Digital ed una variante di sistema Unix (AIX implementato su RISC IBM); gli ultimi due, in particolare, sono generalmente disponibili su tutti i terminali e su tutte le altre macchine, quando operano come emulatori di terminali; questa disponibilità é però al momento molto limitata per le aule attrezzate.

 

                I compilatori in ambiente Macintosh sono generalmente assimilabili a quelli degli ambienti assistiti (Quick, Turbo e Windows), mentre negli altri due contesti si hanno invece stretti parallelismi con quanto esposto sul processo di compilazione ‘on-line’, cioè con lo sviluppo diviso nelle tre fasi distinte:

 

  (1.9.1)                   edit-compilazione-link,

 

 

la differenza maggiore essendo data dal fatto che entrambi i sistemi operativi VMS e Unix sono di tipo multiterminale e richiedono un nome di utente ed una parola-chiave (password) per l’accesso, che deve essere ottenuta dai responsabili dei sistemi.

 

                Ottenuto l’accesso, il rapporto dell’utente con la macchina non è molto diverso dal caso dei Personal Computer in ambiente MS-DOS: la logica della linea dei comandi é molto simile, pure essendo ovviamente diversa la loro sintassi di dettaglio (e diversa anche per i due sistemi); ci limitiamo agli elementi essenziali.

 

 

 

            (a) - Sistema VAX VMS

 

                La scrittura del testo avviene di norma con il programma edit, che ha lo stesso nome, ma comandi molto diversi dal suo omologo MS-DOS, pure essendone le funzioni simili. Mentre non vi sono grandi differenze per la parte di composizione del testo, cambia invece molto la logica dei comandi, che utilizza due forme diverse: la riga di comando, oppure l’impiego estensivo del tastierino numerico, che nelle prime versioni aveva condotto al nome KED, per Keypad Editor.

 

                E’ opportuno apprendere immediatamente il suo uso, facendo preferenzialmente riferimento alla assistenza in linea, o help, ottenibile premendo il tasto PF2.

 

                Per quanto riguarda la compilazione ed il link, è invece disponibile il comando cc, che può svolgere entrambe le funzioni; infine, la gestione dei nomi avviene con convenzioni molto simili a quelle viste sopra.

 

 

 

            (b) - Sistema Unix AIX / IBM Risc

 

 

                Il text editor preferenziale é in questo caso il programma e3, assai simile al pe2 disponibile in ambiente MS-DOS; per i sistemi Unix le regole di formazione dei nomi dei file sono meno restrittive ed ammettono un numero maggiore di parti rispetto alla coppia nome.estensione dello MS-DOS e del VMS; è però opportuno utilizzare le regole ormai note, ed in particolare terminare con .c il nome di un file che contenga il testo di un programma scritto in linguaggio C.

 

                Per compilazione e link il comando preferenziale é xlc, esso pure in grado di svolgere entrambe le funzioni; per le convenzioni del sistema il prodotto intermedio della compilazione, o modulo oggetto, viene automaticamente indicato con la estensione .o, mentre la versione eseguibile non ha in genere alcuna estensione.

 

 

               

                Per entrambi i sistemi sono ovviamente disponibili i programmi di debug; per questo, come per la manualistica e la assistenza in linea, gli utenti interessati debbono però richiedere esplicitamente assistenza presso il Centro di Calcolo.

 

 



[1] ASCII: da American Standard Codes for Information Interchange. E' la convenzione "quasi universale" per la rappresentazione di caratteri, uno per ogni byte, per cui ogni carattere si identifica con un valore numerico; con ASCII-standard si intende l'intervnosciuti utilizzano solo 7 bit, ossia l'intervallo di valori 0 .. 127.