Università degli Studi di Roma "La Sapienza"

Facoltà di Ingegneria

Guida all'uso di LCC-Win32 per la programmazione in C

Corso di Fondamenti di Programmazione - A.A. 2001/2002

Diego Calvanese, Domenico Lembo

Il programma LCC-Win32 costituisce un ambiente integrato per la messa a punto di programmi in C (ed eventualmente in altri linguaggi di programmazione quali Pascal, Fortran ed Eiffel). In questo ambiente è possibile creare, correggere, compilare, mandare in esecuzione e memorizzare su disco programmi C.

1. Ambiente di sviluppo LCC-Win32

Lanciando LCC-Win32 viene visualizzata la schermata seguente:

Tramite i menù è possibile selezionare i comandi desiderati. Questo può avvenire sia utilizzando il mouse, sia posizionandosi con le frecce sul comando desiderato e premendo quindi il tasto Invio. Ad un comando può inoltre essere associato un tasto Fi (F1,...,F10), oppure le combinazioni Alt+Fi, Ctrl+Fi, Alt o Ctrl seguiti (lasciando premuto Alt o Ctrl) da un carattere o tasto (ad esempio BkSp (spazio) o Up-arrow (freccia verso l'alto)). È anche possibile selezionare un menù digitando il tasto Alt seguito dalla lettera iniziale del menù stesso (senza rilasciare il tasto Alt). Le combinazioni di tasti che corrispondono a particolari comandi sono indicate nei menù di appartenenza.

Se si vuole uscire definitivamente da LCC-Win32 basta eseguire il comando Quit del menù File (o da tastiera Alt+F4).

2. Realizzazione di un programma

In LCC-Win32 ogni programma C deve essere associato ad un progetto che guida le operazioni di scrittura, compilazione, esecuzione e memorizzazione su disco del programma stesso. Ogni progetto può essere associato ad un solo programma. Esistono due modalità per creare i progetti:
  1. la prima richiede che il progetto sia creato manualmente tramite il comando New Project del menù File, e che i file che ostituiscono il programma siano esplicitamente selezionati dal programmatore ed associati al progetto stesso;
  2. nella seconda modalità, il progetto viene creato in modo automatico alla prima compilazione del programma C che si intende realizzare. Il file complilato viene direttamente associato al progetto, mentre altri file possono essere aggiunti in un secondo momento tramite il comando Add/Delete files del menù Project.
Mentre nel primo caso la procedura prevede che il programmatore stabilisca i valori dei parametri di configurazione del progetto, nel secondo questi assumono dei valori di default che possono essere eventualmente cambiati successivamente tramite il comando Configuration del menù Project.

Nel corso di Fondamenti di Informatica realizzeremo programmi in C relativamente semplici, per i quali possiamo fare uso di progetti che contengono un unico file e che sono sufficientemente semplici da non richiedere una configurazione manuale. Nel seguito, quindi, descriviamo in dettaglio come realizzare un programma C in LCC-Win32 sfruttando la possibilità di creare automaticamente il progetto associato (modalità 2).

Prima di proseguire è bene notare che, sebbene in questa dispensa ci limitiamo a situazioni in cui un unico file viene associato ad un progetto, ciò non implica che il programma associato debba necessariamente essere scritto in un solo file. Nell'ambito del corso verranno studiati anche casi in cui un programma viene suddiviso in più file tramite l'uso della direttiva di compilazione #include. In questi casi, al progetto dovrà essere associato unicamente il file contenente la funzione main, mentre gli altri file non dovranno essere aggiunti al progetto.

Per semplicità di esposizione, nel seguito supporremo che il programma sia scritto in un unico file. Nel caso in cui si debba suddividere un programma in più file, ognuno di questi potrà essere creato e modificato con le stesse modalità descritte nei paragrafi 2.1, 2.2 e 2.5, mentre solo il file contenente la funzione main dovrà essere compilato ed eseguito.

2.1 Creazione di un nuovo programma

Per creare un nuovo programma si esegue il comando New > File del menù File e si inserisce il nome che si intende assegnare al file nella casella Name?, ad esempio mio_programma.c (si noti che è necessario specificare esplicitamente l'estensione .c nel nome del file). In questo modo viene visualizzata la finestra mio_programma.c in cui verrà scritto il testo del programma. È bene notare che a questo punto non è ancora stato memorizzato su disco il file mio_programma.c, ma che questo verrà effettivamente creato solo dopo aver apportato delle modifiche al testo ed averle successivamente salvate (vedi comando Save più avanti).

2.2 Scrittura del programma

Per i comandi dell'editor si rimanda Si ricorda che le frecce consentono di posizionare il cursore nel punto sul quale si intende lavorare. Quando si ritiene completato il testo del programma questo può essere salvato nel file associato alla finestra tramite il comando Save del menù File (Ctrl+S). Il file così salvato viene memorizzato nella directory corrente.

Se si vuole conoscere il nome ed il percorso della directory corrente ed eventualmente modificarlo, è possibile utilizzare il comando Configuration del menù Project e selezionare la scheda General (il percorso della directory corrente è indicato nella casella Current directory).

Se si vuole salvare su un nuovo file, il comando da utilizzare è Save as, che ne richiede il nome; in questo caso il file di partenza rimane inalterato. Ovviamente questo comando può essere usato anche per salvare in una directory diversa da quella corrente.

2.3 Compilazione del programma

La compilazione del programma avviene tramite il comando Compile mio_programma.c del menù Compiler. La prima volta che viene eseguita la compilazione viene automaticamente creato un progetto che assume lo stesso nome del file (ad esempio mio_programma), e che contiene il file stesso. Notiamo che, per poter compilare (e successivamente eseguire) un programma, LCC-Win32 ha bisogno che il progetto associato al programma sia "aperto".

Quando avviene la prima compilazione, il progetto creato viene automaticamente aperto, e diviene il "progetto corrente". Il nome del progetto corrente è indicato sulla barra della finestra LCC-Win32. La prima volta che viene compilato un nuovo programma è bene che gli altri progetti siano stati preventivamente chiusi. In caso contrario LCC-Win32 chiederà all'utente se intende aggiungere il file da compilare al progetto corrente o se si vuole realizzare un nuovo progetto per questo file. Dal momento che nel caso che stiamo trattando il nuovo file è relativo ad un nuovo programma, il programmatore dovrà selezionare l'opzione nuovo progetto (tasto New Project).

Non appena il file è compilato, nella directory corrente viene creata una nuova cartella, chiamata lcc, in cui vengono memorizzati i prodotti della compilazione.

Sottolineamo il fatto che, per compilare il programma modificato, è necessario salvare le correzioni effettuate. In caso contrario si compila una versione non aggiornata del file che può risultare differente da quella visualizzata.

2.4 Esecuzione del programma

Il comando Execute mio_programma.exe del menù Compiler compila il programma e, se non vengono individuati errori, lo esegue. L'input e l'output dei dati avvengono in una finestra di esecuzione (ti tipo testuale) che viene automaticamente aperta all'inizio dell'esecuzione e chiusa al termine. Per interrompere il programma (ad esempio, nei casi di non terminazione) si usano i tasti Ctrl+Pausa, o semplicemente si chiude la finestra di esecuzione.

2.5 Modifica di un programma

Se si vuole modificare un programma realizzato in precedenza e già memorizzato su disco occorre aprire il progetto associato al programma. Per aprire un progetto si può utilizzare il comando Open del menù Project, che consente di effettuare una selezione da una lista di progetti esistenti. L'apertura di un nuovo progetto provoca la chiusura del progetto corrente (se questo esiste). Se si desidera avere più progetti aperti contemporaneamente occorre selezionare la casella in basso della finestra Open a project. Ad ogni modo, è buona norma mantenere aperto un solo progetto alla volta.

L'apertura di un progetto non porta necessariamente all'apertura dei file che ne fanno parte, ma questi devono essere aperti a loro volta tramite il comando Open File dal menù File. Nel caso in cui sia necessario consultare la lista dei file appartenenti ad un progetto si può utilizzare il comando Add/Delete files del menù Project. È comunque possibile aprire file che non appartengono al progetto. Notiamo che all'apertura di un progetto può accadere che siano visualizzati file che non fanno parte del progetto stesso. Questo è dovuto al fatto che, quando si apre un progetto, vengono visualizzati esattamente gli stessi file che erano aperti al momento della sua chiusura.

Una volta effettuate le modifiche e terminato il lavoro su un progetto, questo può esser chiuso tramite il comando Close del menù Project (vivamente consigliato).

2.6 Note conclusive sui progetti

Come abbiamo già detto nel paragrafo 2.3, quando un programma viene compilato per la prima volta, LCC-Win32 crea automaticamente il progeto associato. Viene inoltre creata nella directory corrente una cartella lcc che contiene i prodotti della compilazione, ed in particolare il file eseguibile, ad esempio mio_programma.exe. Quando il programma sviluppato deve eseguire operazioni di lettura da e/o scrittura su file, è necessario tenere conto che l'eseguibile del programma si trova nella cartella lcc (e non nella stessa cartella del file sorgente C). Quindi, se il programma deve accedere ad un file di input/output specificandone solo il nome (senza percorso), perchè questo file possa essere trovato dal programma, deve trovarsi nella cartella lcc. Se invece un file di input/output si trova nella stessa cartella del file sorgente C, allora si dovrà utilizzare come percorso ../ (ovvero risalire da lcc di una cartella).

Si noti inoltre che, se la directory corrente contine già una sottocartella lcc, allora, al momento della compilazione di un nuovo progetto, verrà creata ed utilizzata una cartella lcc1. Analogamente, se esiste già anche la cartella lcc1, allora viene creata lcc2, e così via. Questo comporta che, se si tengono molti file sorgente relativi a programmi (e quindi progetti) diversi in una stessa directory, si avranno molte sottocartelle lcci, e diventa complicato stabilire quale cartella lcci è associata a quale progetto. Si consiglia pertanto di utilizzare una directory separata per ogni programma che si sviluppa (ad esempio con lo stesso nome del programma senza estensione .c)

3. Individuazione degli errori

Uno degli aspetti più importanti di un ambiente di programmazione riguarda gli strumenti per l'individuazione degli errori logici contenuti nei programmi. A questo proposito risulta molto utile fare uso degli strumenti offerti dall'ambiente di programmazione.

In LCC-Win32, tali strumenti sono utilizzabili nella modalità di "debugging", accessibilie tramite il comando Debugger (F5) del menù Compiler. In questa modalità il menù Compiler non compare fra i menù disponibili, ed è sostituito dal menù Debug, mentre la finestra di compilazione viene usata per i messaggi di debugging (per questo nel seguito viene chiamata finestra di debugging).

3.1. Esecuzione del programma istruzione per istruzione

Per ottenere una esecuzione istruzione per istruzione del programma occorre eseguire il comando Step in (F8) del menù Debug. Il programma viene compilato, la barra di esecuzione viene posta all'inizio della funzione main() e viene aperta la finestra di esecuzione. Notiamo che durante il debugging la finestra di esecuzione non è visualizzata in primo piano, ma è ridotta ad icona sulla barra degli strumenti. Clickando sull'icona della finestra di esecuzione è possibile portare la finestra in primo piano ed inserire i dati in input, quando richiesto da una istruzione di lettura (scanf), o verificare l'effetto delle istruzioni di output. Ogni volta che si preme F8 viene eseguita l'istruzione evidenziata.

Consideriamo il programma seguente che somma n interi specificati dall'utente.

#include <stdio.h>

int main(void)
{
  int i, m, n;
  int somma = 0;
  printf("Numero dati da sommare = ");
  scanf("%d", &n);
  for (i = 1; i <= n; i++) {
    printf("Prossimo dato = ");
    scanf("%d", &m);
    somma = somma + m;
  }
  printf("Somma = %d\n", somma);
  return 0;
}
Premendo più volte F8 si esegue il programma passo passo. Quando si arriva all'istruzione sottolineata, vengono chiesti dati di input, e, per proseguire, occorre inserirli.

Il comando Same level (F4) del menù Debug, consente di eseguire in un unico passo l'istruzione corrente, durante l'esecuzione istruzione per istruzione. In particolare quindi, quando si esegue la chiamata ad una funzione in questa modalità, non se ne segue il flusso completo, ma tutta l'attivazione viene eseguita in un singolo passo. Ad esempio:

#include <stdio.h>
int fattoriale(int x)
{
  int i;
  int somma = 1;
  for (i = x; i >= 1; i--)
    somma = somma * i;
  return somma;
}

int main(void)
{
  int numero, fatt;
  printf("Numero = ");
  scanf("%d", &numero);
  fatt = fattoriale(numero);
  printf("Il fattoriale di %d e' %d", numero, fatt);
  return 0;
}
Eseguendo il programma passo passo, arrivati all'istruzione sottolineata dando il comando Same level il sistema restituisce il valore del fattoriale senza eseguire la funzione istruzione per istruzione come invece avrebbe fatto con Step in.

Infine, il comando Step out (F9) del menù Debug, consente di eseguire in un unico passo tutte le istruzioni che vanno dalla instruzione selezionata alla fine della funzione che si sta eseguendo.

Finestre durante l'esecuzione istruzione per istruzione

In questa fase è utile posizionare la finestra di esecuzione e la finestra dell'applicazione LCC-Win32 in modo da avere sempre sotto gli occhi il codice del programma che si sta analizzando e contemporaneamente verificare l'effetto delle istruzioni (questo può essere utile soprattutto nel caso in cui si esegua una scanf che causa l'arresto del processo di debugging fino a che non siano stati inseriti i dati richiesti in input).

3.2. Ispezione delle variabili

Durante l'esecuzione passo passo è molto utile verificare il valore delle variabili del programma. A questo scopo occorre specificare le variabili di cui si vuole controllare il valore. Tramite il comando Watchs del menù Debug si attiva la finestra Watch nella quale si può specificare il nome di una variabile di cui si vuole controllare il flusso editando sulla scritta new item. L'operazione va ripetuta per ogni variabile che si intende ispezionare. La lista delle variabili selezionate viene mantenuta nella finestra Watch fino al termine della procedura di debugging.

Facendo riferimento al programma precedente che somma n interi, può essere utile controllare il valore che assumeranno istruzione dopo istruzione le variabili somma ed n: queste sono i new item che vanno inseriti.

Se il programma ancora non è stato mandato in esecuzione, nella finestra Watch accanto al nome della generica variabile ci sarà un valore non significativo fino a quando il programma non avrà raggiunto la definizione della variabile. Proseguendo con l'esecuzione passo passo del programma le variabili assumono i valori determinati dall'esecuzione del programma. Si noti che quando ci sono due variabili con lo stesso nome in due blocchi diversi, viene mostrato il valore della variabile visibile nella parte di programma in esecuzione.

ATTENZIONE: A causa di alcuni "bachi" di LCC-Win32, il debugging non funziona correttamente con le variabili di tipo puntatore, né con gli operatori & e *. Si consideri ad esempio la situazione seguente:

int main (void)
{
  int i = 3;
  int* punt = &i;
  ...
  return 0;
}
in cui si decida di ispezionare il valore delle variabili punt ed i. Se aggiungiamo sia i che punt fra le variabili ispezionate, vediamo che mentre per la variabile i è visualizzato il valore (ad esempio 3 dopo l'inizializzazione), per la variabile punt viene visualizzato il suo indirizzo e non il suo valore. Quindi, mentre per la variabile i di tipo intero il comportamento risulta corretto, per la variabile punt di tipo puntatore ad intero abbiamo un comportamento errato.

La situazione è analoga anche nei casi in cui si vogliano ispezionare *punt o &i: nel primo caso viene ancora mostrato l'indirizzo di punt (mentre il valore corretto sarebbe 3), mentre nel secondo caso viene visualizzato 3, cioè il valore di i e non il suo indirizzo come richiesto.

Inoltre, sebbene sia consentito l'uso di operatori per visualizzare il valore di espressioni nella finestra Watch, un tale utilizzo non sembra funzionare correttamente.

3.3. Punti di arresto

L'esecuzione istruzione per istruzione risulta impraticabile non appena le dimensioni dei programmi e dei dati diventano significative. Si può per questo eseguire il comando Run to cursor (F7) del menù Debug, che provoca l'esecuzione di tutte le istruzioni da quella corrente fino al punto in cui è posizionato il cursore. Nella finestra Watch compaiono i valori delle variabili aggiornate fino all'ultima istruzione eseguita.

Si può infine richiedere l'arresto dell'esecuzione del programma solo in determinati punti usando i comandi Breakpoint (F2) e Edit Breakpoint del menù Debug.

Notiamo che i comandi Breakpoint ed Edit Breakpoint del menù Debug hanno lo stesso funzionamento rispettivamente dei comandi Set breakpoint e Breakpoint del menù Compiler che è accessibile al di fuori dell'ambiente di debugging.

Il comando Execute (F5) del menù Debug consente di eseguire in un solo passo tutte le istruzioni che sono separate da due punti di arresto.

3.4. La pila delle attivazioni

La pila delle attivazioni (chiamate) di funzione può essere molto utile per esaminare l'esecuzione del programma. Per vedere la pila delle attivazioni di funzione si deve eseguire il comando Stack nel menù Debug, durante l'esecuzione passo passo. Questo comando visualizza nella finestra di debugging la pila delle attivazioni nello stato corrente del programma. Le informazioni contenute nella pila sono aggiornate ogni volta che viene eseguita una nuova istruzione nell'esecuzione passo passo.

Si consideri il seguente programma che contiene una procedura ricorsiva per il calcolo del fattoriale.

#include<stdio.h>

void fattoriale(int* risultato, int numero)
{
  if (numero > 1) {
    *risultato = numero * (*risultato);
    fattoriale(risultato, numero-1);
  }
}

int main(void)
{
  int fatt = 1;
  int n;
  printf("Inserisci un numero:  ");
  scanf("%d", &n);
  fattoriale(&fatt, n);
  printf("Il fattoriale e' %d\n", fatt);
  return 0;
}
Supponendo che al programma venga fornito il valore 4, eseguendo il comando Stack subito dopo la quarta chiamata alla funzione fattoriale() si ottiene:
_fattoriale: fattoriale.c: 5
         int * risultato int * 0x12ff08 =  24
           int numero 1
_fattoriale: fattoriale.c: 8
         int * risultato int * 0x12ff24 =  24
           int numero 2
_fattoriale: fattoriale.c: 8
         int * risultato int * 0x12ff40 =  24
           int numero 3
_fattoriale: fattoriale.c: 8
         int * risultato int * 0x12ff5c =  24
           int numero 4
_main: fattoriale.c: 17
        (void)
A questo punto dell'esecuzione, lo stack contiene cinque record di attivazione, ognuno relativo ad una attivazione di funzione del programma. Procedendo dal basso verso l'alto, notiamo che il primo record è relativo all'attivazione del main, mentre i successivi si riferiscono alle quattro attivazioni della funzione fattoriale. In ogni record è indicato: Notiamo, infine, che con un doppio click del mouse sulla prima riga di un record della pila delle attivazioni è possibile spostare il cursore sul codice della funzione relativa, nel punto in cui la sua attivazione era stata sospesa.

3.5. Chiusura dell'ambiente di debugging

Al termine dell'esecuzione passo passo di un programma, LCC-Win32 chiede di selezionare la cartella in cui salvare i file prodotti durante la compilazione. Per proseguire si può selezionare indifferentemente OK o Annulla, dato che questo non influenza in alcun modo l'intera procedura di individuazione degli errori. L'esecuzione del passo successivo causa la chiusura dell'ambiente di debugging.

Se si desidera interrompere l'esecuzione passo passo del programma è possibile selezionare il comando Stop debugging del menù Debug.
Notiamo che se il programma viene modificato, questo deve essere nuovamente compilato affinchè le modifiche abbiano effetto durante l'esecuzione passo passo. In questo caso è necessario chiudere l'ambiente di debugging, salvare, compilare il programma e attivare nuovamente la modalità di debugging.

4. I comandi dei menù

Riportiamo di seguito i principali comandi dei menù din LCC-Win32. I comandi non elencati riguardano opzioni i modalità di lavoro non rilevanti ai fini del Corso di Fondamenti di Informatica

4.1. Menù File

4.2.Menù Edit

4.3. Menù Search

4.4. Menù Project

4.5. Menù Compiler

4.6. Menù Debug (sostituisce il menù Compiler in modalità di debugging)

4.7. Menù Window

4.8. Menù Help