Laboratorio di Informatica I: Programmazione. AA 2003/04.
Esercitazione n. 5

Scopo di questa esercitazione è di familiarizzarsi con le dichiarazioni, definizioni ed utilizzo delle function C. Viene anche illustrato il meccanismo della compilazione separata di files diversi.

Al solito, si suggerisce di creare una directory apposita per i files relativi a questa esercitazione.
Per i primi due esercizi, si consiglia di usare l' opzione -Wall nel compilare i files (in modo da ottenere tutti i messaggi di errore, a qualsiasi livello  di gravita').


     
  1. Trasformare in  function le linee di codice usate nell' esercitazione precedente per  trasformare nella corrispondente lettera maiuscola solo i caratteri che rappresentano lettere minuscole.  

    if( (c >= 'a') && (c <= 'z') )
    c = c + 'A' - 'a';

    Si tratta quindi di scrivere un file che contenga il codice della function (cioe' la definizione) e poi, di seguito, un main che legga un carattere da tastiera e lo modifichi con la function letteramaiuscola scrivendolo in output. Dapprima non inserire la dichiarazione della function (cioe' una linea che istruisce il compilatore sul tipo dati della function e dei suoi argomenti) nel corpo del main, compilare e verificare che il compito viene eseguito. Poi spostare la definizione della function dopo  il main e riprovare a compilare.  Cambia qualcosa ? A questo punto, inserire  la dichiarazione della function e riprovare a compilare. Cosa se ne puo' dedurre ?


  2. Lasciare nel file .c del precedente esercizio solo il codice relativo al main (con la dichiarazione globale della function). Cambiare nome al file chiamandolo prog.c (ricordiamo come si fa?).Quindi scrivere un secondo file (chiamandolo ad esempio fun.c) contenente solo il codice relativo alla function. E' possibile compilare separatamente i due files e creare un unico eseguibile mediante il comando
       gcc -ansi -pedantic -Wall prog.c fun.c -o prog.x
    Il cui effetto e' di leggere i due files prog.c e fun.c, compilarli e creare dai rispettivi codici un file eseguibile dal nome prog.x. Provare a compilare ed eseguire.

    E' anche possibile compilare in momenti diversi i due files. Il comando 

     
    gcc -ansi -pedantic -Wall -c fun.c
    compila il solo file fun.c senza tentare di eseguire le operazioni di linking. Il risultato di tale compilazione sara' un file di nome fun.o che contiene codice macchina non rilocato (non eseguibile). Questo potra' utilizzato per creare il file eseguibile, per esempio contestualmente alla compilazione di prog.c, con il comando
    gcc -ansi -pedantic -Wall -o prog.x prog.c fun.o

  3. Cosa scrive il seguente programma ? Fare la previsione e controllarla.
    #include <stdio.h>

    float x=3.4;

    int main(){
    int i;
    float x=2.6;
    float fun( float);
    printf(" x prima del for = %f \n",x);
    for(i=1;i<=5;i++){
    float x= -5.5;
    x = x + 1.0;
    printf(" x nel for, iterazione n. %d, = %f \n",i,x);
    }
    printf(" x dopo il for = %f \n",x);
    x = fun(x);
    printf(" x dopo la chiamata a fun = %f \n",x);
    return 0;
     }
    float fun( float y){
    float x=3.0;
    x = x + y;
    return x;
    }

  4. Realizzare un programma che calcoli le due radici della generica equazione di secondo grado, utilizzando due function, aventi come argomento i coefficienti, una per ciascuna radice. Tener conto anche dei casi degeneri dell' equazione.
  5. Implementare la soluzione delle equazioni di secondo grado di cui al punto precedente mediante un' unica function che  utilizzi variabili globali per  ritornare l' eventuale coppia di radici reali.

  6. Implementare mediante function ricorsiva il calcolo della successione di Fibonacci: 
    an = an-1 + an-2 

    con a0 = 0 e a1 = 1

    La function deve essere tale che in corrispondenza dell' argomento n ritorni l' n-esimo termine della successione.

  7. Il tempo di esecuzione di un comando (incluso tra questi un file eseguibile generato dal compilatore puo' essere determinato mediante l' utilizzo del comando UNIX
    time
    . Per esempio,
    time a.out
    fara' comparire, dopo l' esecuzione del comando a.out una linea con varie informazioni tra cui il tempo di esecuzione per in codice "user" cioe' per eseguire la parte di codice corrispondente al programma scritto dall' utente. Se la linea scritta dal comando time e' come la seguente
    real         1m41.560s 
    user 0m1.110s
    sys 0m0.180s
    il risultato va letto come:

    1m41.560 = 1 min, 41.56 s tempo "di orologio" necessario per completare l' esecuzione
    1.110 = 1.110 s tempo di CPU per eseguire codice utente relativo all' applicazione
    0.180 = 0.180 s tempo di CPU per eseguire codice di sistema rel. all' applicazione

    Per ulteriori informazioni, cfr. man time.

    Implementare una versione iterativa del calcolo della successione di Fibonacci e confrontare, al crescere di n, i tempi di esecuzione del programma con quelli relativi alla versione ricorsiva di cui al punto precedente.

  8. Utilizzare l' operatore di shift bit a bit per verificare il valore dei 32 bit di un int scrivendoli   in output. Verificare la rappresentazione degli interi  3,   9,  -1,   231-1, -231

    Modificare il programma per lavorare con i float. Cosa succede ? Perché ?

  9.  Trovare, mediante un programma C, tre float,  a,b,c, tali che non valga la proprieta' associativa (  (a+b)+c != a+ (b+c) ) e tre (d,e,f) per cui non valga la propriata' distributiva  ( d*(e+f) != d*e + d*f ). Cosa si puo' dire delle proprieta' commutative di somma e prodotto ?

  10. Riprendere il programma per il calcolo della radice quadrata e fare in modo che main ritorni 0 se il discriminante e' nullo o positivo, mentre ritorni 1 se e' negativo. Verificare quindi il valore ritornato all' interprete di comandi ( che, nel caso dell' interprete  bash e' il valore della variabile ? visualizzabile col comando   echo $? ).