1
Unità Didattica 1
Linguaggio C
Fondamenti. Struttura di un programma.
2
La storia del Linguaggio C
• UNIX (1969) - DEC PDP-7 Assembly Language • BCPL - un OS facilmente accessibile che fornisce
potenti strumenti di sviluppo prodotti a partire da BCPL. Si tratta di un assemblatore noioso, lungo ed incline agli errori
• Un nuovo linguaggio "B" come secondo tentativo (1970)
• Un linguaggio "C" totalmente nuovo come successore di "B" (1971)
• Dal 1973 UNIX OS, quasi totalmente scritto in C.
3
La storia del Linguaggio C
• E’ stato utilizzato per sviluppare il sistema operativo UNIX, viene comunemente usato per scrivere sistemi operativi
• Indipendente dall’hardware (portabile)• Standardizzazione:
– inizialmente sono state definite molte piccole varianti del C, incompatibili tra loro;
– ANSI (American National Standards Institute) si è occupato della sua standardizzazione, nel 1989, standard aggiornato nel 1999.
4
Caratteristiche del C
• Linguaggio a medio/alto livello. Basso livello di controllo degli errori nella fase di compilazione.
• Variabili tipizzate, con notevoli possibilità di conversione mediante il type casting, che permette di forzare una variabile a cambiare tipo;
• Abbina ad un medio-alto livello di astrazione, un buon controllo delle operazioni a basso livello.
5
Fasi di traduzione ed esecuzione di unprogramma C
• Traduzione:– Preprocessing: il preprocessore si occupa di includere altri
file (es: librerie standard) e sostituisce simboli speciali che seguono particolari direttive (es: costanti simboliche);
– Compilazione: traduzione del programma in codice oggetto (linguaggio macchina (*.obj));
– Linking: risoluzione dei riferimenti a funzioni e variabili definite altrove (es. in librerie standard o definite dall’utente), producendo una immagine eseguibile (progr.exe);
• Esecuzione:– Loading: caricamento in memoria il codice eseguibile;– Esecuzione delle istruzioni;
6
Primo programma in C
#include <stdio.h> /*# = Preprocessore C precede le direttive che il compilatore
valuta prima di iniziare la traduzione effettiva del programma (in questo caso
l’inclusione della libreria delle funzioni standard di I/O (stdio.h))
*/
main() /* Funzione principale, l’esecuzione di un programma C consiste nella esecuzione del main
*/{
printf(“Hello world\n”); /* Visualizza sullo standard output (monitor) la frase fra “ ”
\n rappresenta un a capo*/
}
7
Esecuzione
• Ogni programma comincia la sua esecuzione con la funzione main.
• I commenti sono inseriti tra i caratteri /* e */ (oppure // per commentare un’intera riga) e vengono ignorati in fase di compilazione.
• In int main (void), la sintassi indica che il programma non ha argomenti di ingresso e che restituisce un valore intero (che indica la terminazione con successo).
8
Istruzioni e Librerie
• Il set di istruzioni del C è molto limitato: le primitive più comunemente utilizzate (es. I/O, matematiche) sono contenute nelle librerie standard del C sotto forma di funzioni.
• Se si utilizzano delle funzioni contenute in una certa libreria, questa deve essere inclusa mediante la direttiva #include del preprocessore.
• Es: – la funzione di output printf() è contenuta della libreria
standard stdio.h;– Per usare la funzione deve essere presente la direttiva di
inclusione:• #include <stdio.h>
9
Preprocessore
• # è il simbolo con cui iniziano i comandi del preprocessore.
• Tali comandi non sono terminati da “;”• Attraverso il preprocessore si esprimono direttive al
compilatore;• Principali direttive:
– Definizione delle costanti simboliche e delle macro;
– Compilazione condizionale del codice.
10
Preprocessore: #include
• La direttiva #include: consente di includere in la copia di un file specificato.
• Esistono due forme:– #include <nome_file>
viene utilizzata per includere file di intestazione (header file) della libreria standard, che sono memorizzati in directory standard (dipendenti dall’implementazione del compilatore);
– #include “nome_file”cerca il file nella directory corrente, altrimenti in directory standard (perciò è utilizzata per includere header file definiti dal programmatore).
11
Preprocessore: #define
• La direttiva #define è utilizzata per definire costanti simboliche e macro.
• Il formato è:
– #define [identificatore] [testo_di_sostituzione]
• All’interno del file in cui è presente tutte le successive occorrenze dell’identificatore saranno automaticamente sostituite dal testo_di_sostituzione, prima della compilazione.
12
Preprocessore: #define(per definizione di costanti simboliche)
• #define PI 3.14159
sostituirà tutte le occorrenze della costante simbolica PI con quella numerica 3.14159
13
Preprocessore: #define(per definizione di macro)
• Le macro possono essere definite con o senza argomenti.• Una macro senza argomenti viene elaborata come una
costante simbolica.• In una macro con argomenti, essi saranno rimpiazzati
all’interno del testo di sostituzione e solo in seguito sarà espansa la macro: ovvero, il testo di sostituzione rimpiazzerà la lista degli identificatori e degli argomenti all’interno del programma.
• Esempio:– #define AREA_CERCHIO(x) ( PI * (x) * (x) )
ogni volta che nel programma appare AREA_CERCHIO(r)il valore di r sarà usato al posto di x, la costante PI sarà rimpiazzata dal suo valore (definito precedentemente) e la macro verrà espansa all’interno del programma.
14
Preprocessore: #define(per definizione di macro)
• Esempio 1:– area = AREA_CERCHIO(4)
sarà espanso in– area = ( 3.14159 * (4) * (4) );
• Esempio 2:– area = AREA_CERCHIO(c+2)
sarà espanso in
– area = ( 3.14159 * (c+2) * (c+2) );
• Vantaggi nell’uso delle macro:– risparmio nella definizione di una funzione;– miglioramento nella leggibilità del programma.
15
Preprocessore:Compilazione condizionale
• Consente al programmatore di controllare la compilazione del codice del programma.
• Le direttive condizionali del preprocessore vengono valutate come espressioni costanti intere.
• Esempio:#define DEBUG#ifdef DEBUG
printf(“Variabile x = %d\n”, x );#endif
l’istruzione printf viene compilata (ed eseguita) solo nel caso in cui la variabile DEBUG sia definita.
16
Variabili
• In C ogni variabile è caratterizzata dai seguenti aspetti:– Tipo;– Classe di memoria.
• Assegnare un tipo ad una variabile significa assegnarle il dominio dal quale assume i valori;
• La classe di memoria determina la durata della vita (ciclo di vita) e l’ambito di visibilità (scope) delle variabili.
17
Tipi di dati• Tipi di base
– char: carattere– int: intero– float: virgola mobile, singola precisione– double: virgola mobile, doppia precisione– void: indica che il dominio della variabile è
l’insieme vuoto
• Qualificatori:– unsigned: (es: unsigned int, unsigned char)– short: (es: short int)– long: (es: long int, long double)
18
Dichiarazione di Variabili
• [tipo_var] var0, var1, …;
• E' possibile inizializzare una variabile al momento della dichiarazione:
– Esempi:
int i, j, k=1;
float x=2.6, y;
char a;
19
Dichiarazione di Costanti
• La definizione di identificatori per le costanti, oltre che con il comando di preprocessore #define, può avvenire usando il modificatore const:
const tipo nome_costante = valore ;
• Esempi:const double e = 2.71828182845905;
20
Funzioni
• Generalizzazione del concetto di funzione algebrica:– legge che associa a valori delle variabili in ingresso valori della
variabili in uscita o, più in generale, azioni.
• Raggruppano operazioni che possono essere riutilizzate usando il nome della funzione e i suoi parametri (chiamata della funzione), senza preoccuparsi dell’aspetto implementativo;
• I programmi in C combinano funzioni definite dal programmatore con funzioni di libreria standard;
• Una funzione deve essere definita (definizione) e dichiarata (prototipo), prima della definizione, relativamente al nome e ai tipi dei parametri in ingresso e in uscita.
21
Parametri alle funzioni
• Una funzione può avere dei parametri in ingresso e un parametro di uscita (tale fatto non rappresenta una limitazione perché si può restituire anche una struttura dati complessa);
• Se non si vuol restituire alcun valore da una funzione è sufficiente dichiararla di tipo void ed omettere o meno il return;
• E’ obbligatorio mettere le parentesi () (oppure (void)) dopo il nome della funzione anche se non ci sono parametri in ingresso.
• void funz(void) oppure: void funz()
22
Parametri alle funzioni
• In C i parametri alle funzioni sono sempre passati per valore;
• Al momento della chiamata, le funzioni allocano (riservano) memoria per le variabili d’ingresso;
• In ciascuna variabile viene copiato il valore che le viene passato al momento della chiamata.
23
Definizione e prototipo e chiamata di una funzione
#include <stdio.h>[tipoUscita] nomeFunzione([tipo0],[tipo1]);
//Prototipovoid main(){
[tipoUscita] a;[tipo0] x;[tipo1] y;…a = nomeFunzione(x, y); //Chiamata
}
[tipoUscita] nomeFunzione([tipo0] var0, [tipo1] var1){ //Definizione
…}
24
Esempio di passaggio di parametri:Calcolo della potenza di un numero
#include <stdio.h>int potenza(int, int); //Prototipo
void main(){
int x, y, z;x = 2; y = 3;z = potenza(x, y); //Chiamata
}
//Definizioneint potenza(int base, int n) { int i, p = 1;
for(i = 0; i < n; i++) p = p * base;return (p);
}
2x 3y
2base 3n
Passaggio al momento della chiamata
25
Esempio di passaggio di parametri in funzioni ricorsive
#include <stdio.h>long int fattoriale(int); //Prototipo
void main(){
long int fatt;int n;scanf(“%d”, &n);fatt = fattoriale(n);
}
long int fattoriale(int n){
if (n == 0)return 1;
return (n * fattoriale(n-1));}
26
Struttura di un programma C/* commenti: nome programma, descrizione, etc. */#istruzioni per il preprocessoredichiarazione di tipi, variabili, costanti;prototipi delle funzioni;
tipo_di_ritorno main (lista_argomenti){dichiarazione variabili localisequenza di istruzioni}
tipo_di_ritorno funzione_1 (lista_argomenti){dichiarazione variabili localisequenza di istruzioni}
tipo_di_ritorno funzione_n (lista_argomenti){ … }
27
Scrittura di un programma C su più moduli (file)
• Quando si scrivono programmi di grosse dimensioni è consigliabile suddividere i programmi in moduli separati. La funzione main() sarà contenuta in un solo file (es: “mioprogr.c”);
• E’ buona norma concentrare inclusioni di librerie standard, dichiarazioni e prototipi necessari ad un modulo, in un unico file (header file) da includere in tale modulo (es: #include “mioprogr.h”);
• Esiste un compromesso fra il desiderio che ogni modulo acceda solo alle informazioni di cui ha bisogno (N moduli => N header file) e la realtà pratica secondo la quale è difficile gestire molti header file;
• Per programmi di dimensioni ridotte è meglio avere un unico header file contenente le informazioni da condividere fra due punti qualunque del programma scritto su più moduli e includere in essi l’unico header file creato;
28
Scrittura di un programma C su più moduli e un unico header file
#include <mioprogr.h>
void main(){ …}
#include <mioprogr.h>
[tipo0]funz0([tipo0] var0){ …}[tipo3]funz1([tipo0] var7){ …}
#include <mioprogr.h>
[tipo1] funz3([tipo0] var0){ …}[tipo5] funz2([tipo3] var1){ …}
mioprogr.c
mod1.c mod2.c#include <stdio.h>#define …
Variabili globali;Prototipi funzioni;
mioprogr.h
Header file: file, da includere in ogni modulo,contenente inclusione di librerie,dichiarazioni e prototipi.
29
Classi di Memorizzazione delle Variabili
• Variabili locali (automatiche);
• Variabili globali (esterne);
30
Variabili Locali (o automatiche)
• Parola chiave auto o nessuna dichiarazione;
• Sono interne ad un blocco individuato da … (es: interne ad una funzione);
• Scope (ambito di visibilità):visibili (accessibili) solo all’interno del blocco di definizione;
• Ciclo di vita:create al momento della dichiarazione, cessano di esistere quando si esce dal blocco;
• Sono inizializzate con valori casuali.
31
Esempi di dichiarazione di variabili automatiche
#include <stdio.h>
void main(void)
int i, j;…
funz();
void funz()
int i, j; //Sono variabili automatiche,ovvero //locali alla funzione. Fuori dalla //funz non sono più accessibili, cessano //di esistere (per esempio le due //variabili “i, j” definite nel “main” //sono del tutto scorrelate da queste).
32
Modifica del ciclo di vita di variabili locali
• E’ possibile modificare il ciclo di vita di una variabile locale in modo che conservi il suo valore fra l’uscita e il successivo rientro nella funzione o nel blocco nel quale è stata dichiarata;
• Tale modifica si ottiene anteponendo alle variabili locali la parola chiave static:void funz()static int i, j; //Sono variabili automatiche, ovvero //locali alla funzione. Fuori dalla //funz non sono più accessibili ma al//rientro nella funzione riacquistano//il valore che avevano al momento //dell’uscita dalla funzione stessa.
33
Variabili Globali (o esterne)• Sono dichiarate una sola volta esternamente a qualsiasi blocco;• Devono essere rese note alle funzioni o ad altri moduli tramite la
parola chiave extern (eccetto il caso in cui la dichiarazione preceda la funzione o il blocco in uno stesso modulo (es: all’inizio di un modulo, come avviene nella pratica)).
• Scope (ambito di visibilità):– Se rese note ad un blocco con l’uso di extern: ovunque, anche in
moduli diversi;– Se rese note ad un blocco senza l’uso di extern: ovunque, nello
stesso modulo, a partire dalla loro dichiarazione in avanti;
• Ciclo di vita:Esistono e conservano il loro valore ovunque siano visibili all’interno del programma (es: uscita e successivo rientro da funzioni o blocchi);
34
Variabili globali in un unico modulo
#include <stdio.h>int i, j; //variabili globali inizializzate a 0
void main(void)
extern int i, j; //in questo caso la //dichiarazione viene //generalmente omessa
int x;i = 2;j = 3;x = i + j;
35
Variabile globale condivisa in più moduli
#include <stdio.h>
int i;
void main(void)
funz();
#include <stdio.h>
extern int i;
void funz(void);
void funz(void)
int j;
j = i+1;
…
mioprogr.c
mod1.c
i è globale per entrambi i moduli
36
Variabile globale condivisa in più moduli con uso di header file
#include “mioprog.h”
void main(void)
funz();
#include “mioprog.h”
void funz()
int j;
j = i+1;
…
mioprogr.c mod1.c
#include <stdio.h>
int i;
void funz(void);
…
mioprogr.h
i è globale perentrambi i moduli(mioprogr.c, mod1.c)