Sei sulla pagina 1di 138

Il linguaggio C

Corso di Programmazione di Sistema a.a. 2006/07

Versione 0.9 preparata dalling. E. Mancini sulla base dei lucidi del prof. Ghini, Univ. Di Bologna
Il linguaggio C 1/243

Riferimenti
Brian W. Kernighan, Dennis M. Ritchie, "Linguaggio C", ed. Pearson Education Italia Webpage del corso: web.ing.unisannio.it/villano/ps0607

Il linguaggio C

2/243

Caratteristiche del linguaggio C


Utilizzo frequente di chiamate a funzioni. Debole controllo sui tipi di dato: a differenza del Pascal, il C permette di operare con assegnamenti e confronti su dati di tipo diverso, in qualche caso solo mediante un type cast (conversione di tipo) esplicito. Linguaggio strutturato. Il C prevede costrutti per il controllo di flusso, quali raggruppamenti di istruzioni, blocchi decisionali (if-else), selezione di alternative (switch), cicli con condizione di terminazione posta all'inizio (while, for) o posta alla fine (do) e uscita anticipata dal ciclo (break).
Il linguaggio C 3/243

Caratteristiche del linguaggio C


programmazione a basso livello facilmente disponibile implementazione dei puntatori: luso di puntatori per memoria, vettori, strutture e funzioni rende facile commettere errori. portabilit sulla maggior parte delle architetture. disponibilit di librerie standard.

Il linguaggio C

4/243

Il primo programma in linguaggio C


Un programma minimo in C e': main() { } che corrisponde a un programma in Java: class PrimoProg { public static void main(String[] args) { } }
Il linguaggio C 5/243

Caratteristiche del linguaggio C


Lo standard per i programmi C in origine era basato sulla prima edizione del testo di Kernighan e Ritchie (K&R). Al fine di rendere il linguaggio pi accettabile a livello internazionale, venne messo a punto uno standard internazionale chiamato ANSI C (American National Standards Institute). Questo standard quello descritto nella seconda edizione del libro (ANSI/ISO/IEC 9899:1990) Standard internazionale corrente (non Americano!) per il C: ISO/IEC 9899:1999 (noto come C99)

Il linguaggio C

6/243

Caratteristiche di ogni programma C


Ogni programma C deve contenere una e una sola funzione main(), che rappresenta il programma principale ed il punto di inizio dell'esecuzione del programma. La parentesi graffa aperta { indica linizio di un blocco di istruzioni mentre la parentesi graffa chiusa } ne indica la fine esattamente come in Java, e corrispondono al begin end del Pascal. Per ogni parentesi graffa aperta { deve essercene una chiusa }.

Il linguaggio C

7/243

Caratteristiche di ogni programma C


I commenti possono essere posti ovunque. L'inizio del commento dato dalla coppia /* La fine del commento indicata da */ Non si pu inserire un commento in un altro (vietato lannidamento dei commenti). Molti compilatori ammettono anche luso dei commenti in stile C++ con la doppia barra //, presenti anche in Java.

Il linguaggio C

8/243

Esempi
Ad esempio: /* Esempio di programma con problemi nei commenti */ main() { /* Un commento */ ESATTO /* Commento /* Ancora un commento */ QUI ERRORE */ }

Il linguaggio C

9/243

Esempi
Il seguente esempio e' un programma che produce l'output sullo schermo della frase "Hello World": #include <stdio.h> int main() { printf("Hello World \n"); exit(0); }

Il linguaggio C

10/243

Caratteristiche di ogni programma C


In C, occorre un punto e virgola ; dopo ogni istruzione.

L'istruzione printf(...) chiama la funzione printf che visualizza ci che gli viene passato come argomento (\n indica l'andata a capo), e ricorda la System.out.println() di Java e la write del Pascal. L'istruzione exit(0) chiama la funzione exit che termina il programma e restituisce al sistema operativo il codice 0.

Il linguaggio C

11/243

Caratteristiche di ogni programma C


In C non esiste la distinzione che esiste in Pascal tra funzioni e procedure, e neppure esistono metodi come in Java. In C sono tutte funzioni, che possono restituire un qualche risultato oppure no. Le chiamate ad ogni funzione in C si effettuano chiamando il nome della funzione seguita dalle parentesi tonde, aperta e chiusa, all'interno delle quali vengono passati i parametri necessari. Anche se la funzione non richiede nessun parametro, il suo nome deve essere seguito dalle parentesi tonde aperta e chiusa.

Il linguaggio C

12/243

Sviluppo di un Programma in linguaggio C

Il linguaggio C

13/243

Creare il programma
Create un file contenete il programma completo. Per creare il file potete usare ogni editor ordinario con il quale abbiate familiarit (vi, kedit, nedit). Il nome di file deve per convenzione terminare con .c, (es. myprog.c o progtest.c). I contenuti devono essere conformi alla sintassi C.

Il linguaggio C

14/243

Compilazione
Ci sono molti compilatori C disponibili. cc il compilatore di default nei sistemi Unix. Il compilatore GNU C gcc popolare e disponibile per molte piattaforme. Gli utenti di PC potrebbero avere familiarit col compilatore Borland bcc. Per i pi audaci: Intel C compiler per Linux icc (http://developer.intel.com) Ci sono anche compilatori equivalenti C++ che di solito si chiamano CC (notate il maiuscolo). Per esempio, CC e GNU GCC. Il compilatore GNU si chiama anche g++ . Esistono altri compilatori C/C++ meno comuni. Tutti i compilatori citati sopra operano essenzialmente nello stesso modo e hanno molte opzioni command line in comune.

Il linguaggio C

15/243

Compilazione
La migliore fonte di informazioni su ogni compilatore il suo manuale in linea: e.g. man cc. Per compilare in vostro programma semplicemente invocate il comando cc seguito dal nome del programma (C) che volete compilare. E possibile specificare anche alcune opzioni di compilazione. Quindi il comando di compilazione di base : cc program.c dove program.c il nome del file.
Il linguaggio C 16/243

Compilazione
Se ci sono errori ovvi nel programma (di battitura, scrittura sbagliata di una delle keywords o omissione di un punto e virgola), il compilatore li trover e li riporter. Ci possono essere ovviamente errori logici che il compilatore non pu scoprire. Potreste stare chiedendo al computer di fare operazioni sbagliate. Quando il compilatore ha digerito con successo il vostro programma, la versione compilata (eseguibile) lasciata in un file chiamato a.out o, se viene usata lopzione -o, nel file citato dopo il -o.

Il linguaggio C

17/243

Compilazione
E pi conveniente usare un -o e il nome del file nella compilazione come in cc -o program program.c che mette il programma compilato nel file program (o in un qualsiasi file citato nel nome che segue largomento -o) invece di metterlo nel file a.out .

Il linguaggio C

18/243

Esecuzione
Il passo successivo di eseguire il programma compilato. Per lanciare un eseguibile in UNIX, basta semplicemente digitare il nome del file che lo contiene, in questo caso program (o a.out), preceduto dalla working directory corrente (./program, ./a.out). Questo manda in esecuzione il programma, stampando i risultati sullo schermo. A questo punto ci possono essere errori run-time, come divisioni per zero, o pu divenire evidente che il programma ha prodotto un output non corretto. In tal caso, occorre editare il sorgente del programma, ricompilarlo e eseguirlo di nuovo.

Il linguaggio C

19/243

Il modello di Compilazione per il C


Gli step essenziali della compilazione per un programma sviluppato in C sono rappresentati nel seguente schema:

Il linguaggio C

20/243

10

Il Preprocessore
Il Preprocessore accetta codice sorgente in ingresso e ha la responsabilit di rimuovere i commenti, interpretare preprocessor directives, indicate dal simbolo #.

Il linguaggio C

21/243

Il Preprocessore
Per esempio #include include i contenuti del file nominato. I file inclusi sono tipicamente header files. #include <math.h> -- standard library maths file. #include <stdio.h> -- standard library I/O file. #define definisce un nome simbolico o costante. Macro substitution. #define MAX_ARRAY_SIZE 100

Il linguaggio C

22/243

11

Il Preprocessore
2) elimina i commenti contenuti nel sorgente. 3) genera cos un nuovo file senza commenti e senza pi necessit di effettuare sostituzioni, pronto per essere processato dal compilatore.
/* file header h*/ extern mt, ni; extern double f; /* file prova.E */ extern int i;

Preprocessore
/* file prova.c */ #include "header.h" /* size vettore */ #define SIZE 10 int vettore[SIZE];

extern double f; int vettore[10];

Il linguaggio C

23/243

Il compilatore e lassemblatore
Il compilatore C traduce codice sorgente in codice assembly. Il codice sorgente quello prodotto dal preprocessore. Lassembler crea codice oggetto. In un sistema UNIX si possono vedere file con un suffisso .o (.OBJ in MSDOS/WIN) che indica file di codice oggetto.

Il linguaggio C

24/243

12

Il linkage editor (linker)


Se un file sorgente fa riferimento a funzioni di libreria o a funzioni definite in altri file sorgente il linkage editor combina queste funzioni (col main()) per creare un file eseguibile. Anche i riferimenti a variabili esterne sono risolti in questa fase.

Il linguaggio C

25/243

Alcune opzioni del compilatore

-c Sopprime il processo di linking e produce un file .o per ogni file sorgente listato. Questi possono essere successivamente linkati sempre col comando cc: cc file1.o file2.o ...... -o executable

Il linguaggio C

26/243

13

Alcune opzioni del compilatore


-llibrary Linkare con librerie oggetto. Questa opzione deve seguire gli argomenti di tipo file sorgente. Le librerie oggetto sono archiviate e possono essere standard, di terze parti o create dallutente. Probabilmente la libreria pi usata la math library (libm.a). Occorre linkare questa libreria esplicitamente se si vogliono usare le funzioni matematiche (non dimenticare anche di #include lheader file <math.h> ), ad esempio: cc calc.c -o calc -lm

Il linguaggio C

27/243

Alcune opzioni del compilatore


-Ldirectory Aggiunge directory alla lista di directory contenente object-library routines. Il linker cerca sempre le librerie standard e di sistema in /lib and /usr/lib. Se si vogliono linkare librerie che sono state create o installate dallutente (a meno che questi non abbia i privilegi necessari ad installare le librerie in /usr/lib) necessario specificare la directory dove i file sono memorizzati, ad esempio: cc prog.c -L/home/myname/mylibs mylib.a
Il linguaggio C 28/243

14

Alcune opzioni del compilatore


-Ipathname Aggiunge pathname alla lista di directory in cui cercare #include files con filename relativi (che non cominciano con slash /). Per default, il preprocessore prima cerca #include files nella directory contenente il file sorgente, poi nelle in directories indicate con le opzioni -I options (se presenti), e finalmente, in /usr/include. Quindi per includere header files memorizzati in /home/myname/myheaders occorre: cc prog.c -I/home/myname/myheaders Nota: I system library header files sono memorizzati in una posizione speciale (/usr/include) e non sono influenzati dalluso dellopzione -I. System header files e user header files vengono trattati in maniera lievemente differente.
Il linguaggio C 29/243

Alcune opzioni del compilatore


-g invoca lopzione di debugging. Questa dice al compilatore di produrre informazioni addizionali sulla tabella dei simboli che vengono usate da una variet di tool di debugging. -D definisce simboli o come identificatori (-Didentifer) o come valori (-Dsymbol=value) in una maniera del tutto simile al comando del preprocessore #define .

Il linguaggio C

30/243

15

Le librerie
Il C un linguaggio estremamente piccolo: molte delle funzioni disponibili in altri linguaggi non sono presenti in C (e.g. niente I/O, funzioni per gestione stringhe o matematiche). A che serve il C allora? Il C fornisce funzionalit mediante un ricco insieme di librerie di funzioni. Come risultato la maggior parte delle implementazioni C includono librerie standard di funzioni per molte funzionalit ( I/O etc.). Dal punto di vista pratico queste possono essere considerate come facenti parte del C. Ma possono variare da macchina a macchina (Borland C vs. UNIX C).
Il linguaggio C 31/243

Le librerie
Il programmatore pu anche sviluppare le sue librerie di funzioni o anche usare librerie di terze parti special purpose (e.g. NAG, PHIGS). Tutte le librerie (eccetto quelle di standard I/O) devono essere esplicitamente linkate con le opzioni del compilatore -l e, possibilmente, -L, descritte prima. Il sistema UNIX dispone di un ampio numero di funzioni di libreria C. Alcune di queste implementano operazioni usate di frequente, mentre altre sono di utilizzo specialistico.

Il linguaggio C

32/243

16

Le librerie
Non Reinventare la Ruota: E saggio per il programmatore vedere se esiste una funzione di libreria che esegua un certo compito prima di scriverne una propria versione. Questo riduce il tempo di sviluppo del programma. Le funzioni di libreria sono state testate, quindi pi probabile che siano corrette rispetto a qualsiasi funzione che possa essere scritta per loccasione. Questo riduce anche i tempi di debugging del programma.
Il linguaggio C 33/243

Le librerie
Il manuale UNIX ha un entry per ciascuna funzione disponibile. La documentazione delle funzioni memorizzata nella sezione 3 del manuale, e molte utili system calls sono contenute nella sezione 2. Se conoscete gi il nome della funzione cercata, si pu leggere la pagina digitando (ad es., per sqrt): man 3 sqrt Se non conoscete il nome della funzione, una lista completa e inclusa nella pagina introduttiva della sezione 3 del manuale. Per leggerla, digitate man 3 intro Ci sono circa 700 funzioni descritte qui. Questo numero tende a crescere ad ogni upgrade del sistema.
Il linguaggio C 34/243

17

Le librerie
Su ogni pagina del manuale, la sezione SYNOPSIS include informazioni sulluso della funzione. E.g.: #include <time.h> char *ctime(time_t *clock) Questo significa che occorre includere #include <time.h> nel file sorgente prima di chiamare ctime. E che la funzione ctime ha un puntatore al tipo time_t come argomento, e ritorna una stringa (char *). time_t probabilmente viene definita nella stessa pagina del manuale. La sezione DESCRIPTION d poi una breve descrizione di quello che fa la funzione. Per esempio: ctime() converte un long integer, puntato da clock, in una stringa di 26 caratteri del tipo prodotto da asctime().
Il linguaggio C 35/243

Struttura di un programma C
Un programma C ha in linea di principio la seguente forma: Direttive per il preprocessore Definizione di tipi Prototipi di funzioni, con dichiarazione dei tipi delle funzioni e delle variabili passate alle funzioni) Dichiarazione delle Variabili Globali Dichiarazione Funzioni, dove ogni dichiarazione di una funzione ha la forma: tipo NomeFunzione(Parametri) { } Dichiarazione variabili locali Istruzioni C
Il linguaggio C 36/243

#include <stdio.h> typedef struct point { int x; int y; }; int f1(void); void f2(int i, double g); int sum; int main(void) { int j; double g=0.0; for(j=0;j<2;j++) f2(j,g); return(2); } void f2(int i, double g) { sum = sum + g*i; }

18

Il preprocessore C
Il preprocessore C modifica un codice sorgente prima di passarlo al compilatore. Viene prevalentemente adoperato per includere files direttamente dentro altri files (#include), o per definire costanti (#define). Pu anche essere usato per creare inlined code usando macro espanse al compile time e per prevenire che del codice sia compilato pi volte.

Il linguaggio C

37/243

Direttive del preprocessore


Ci sono essenzialmente tre utilizzi del preprocessore: direttive, constanti e macro. Le direttive sono comandi che dicono al preprocessore di tralasciare parte di un file, di includere un altro file, o di definire una constante o una macro. Le direttive cominciano sempre con uno sharp sign (#) e, per leggibilit, dovrebbero essere collocate alla sinistra della pagina. Tipicamente, constanti e macro sono scritte in ALL CAPS per indicare chiaramente cosa sono.

Il linguaggio C

38/243

19

Direttive del preprocessore


Header Files La direttiva #include dice al preprocessore di prendere il testo di un file e di piazzarlo allinterno del file corrente. Tipicamente, questi statements sono collocati in testa al programma - da cui il nome di header file per i file inclusi in tal modo.

Il linguaggio C

39/243

Costanti
#define [identifier name] [value] Ogni volta che [identifier name] compare nel file, sar rimpiazzato da [value]. Si noti che tutto quello che segue [identifier name] sar parte del rimpiazzo. Questo pu portare a strani risultati: per es., una cattiva idea usare commenti in C++ style in linee #define: #define PI 3.14 // This is pi x = PI + 1; // oops! Nella linea precedente, PI sar rimpiazzata da 3.14 // This is pi, che commenter il + 1;, causando un syntax error difficile da trovare.
Il linguaggio C 40/243

20

Costanti
Se dovete definire una costante in termini di una espressione matematica, saggio includere lintero valore in parentesi: #define PI_PLUS_ONE (3.14 + 1) Cos facendo, si evita che lordine di valutazione delle espressioni distrugga il significato della costante: x = PI_PLUS_ONE * 5; Senza parentesi, la precedente sarebbe convertita in x = 3.14 + 1 * 5; che avrebbe portato al valutare 1 * 5 prima delladdizione, e non dopo!!
Il linguaggio C 41/243

Definizioni vuote
E anche possibile scrivere semplicemente #define [indentifier name] che definisce [identifier name] senza assegnargli un valore. Questo pu servire insieme con un altro set di direttive che consentono la compilazione condizionale.

Il linguaggio C

42/243

21

Compilazione condizionale
C un insieme di opzioni che pu esser usato per determinare se il preprocessore rimuover linee di codice prima di passare il file al compilatore: #if, #elif, #ifdef, e #ifndef. Un blocco #if o #if/#elif/#else o un blocco #ifdef o #ifndef deve essere terminato da un #endif. La direttiva #if prende un argomento numerico che viene valutato true se non-zero. Se il suo argomento false, allora il codice fino all #else, #elif, o #endif di chiusura sar escluso.
Il linguaggio C 43/243

Compilazione condizionale
Commenting out Code La compilazione condizione particolarmente utile per comment out un blocco di codice che contiene commenti multi-line (che non possono essere innestati). #if 0 /* comment ... */ code /* comment */ #endif

Il linguaggio C

44/243

22

Compilazione condizionale
Evitare di includere file pi volte (idempotenza) Un altro problema comune che un header file richiesto in pi altri header files che poi sono inclusi in un source code file, con il risultato che variables, structs, classes e functions appaiono definite pi volte (una per ogni volta che lheader file viene incluso). Usando la direttiva #ifndef, si pu includere un blocco di testo solo se una particolare espressione non definita; poi, nellheader file, si pu definire lespressione #ifndef _FILE_NAME_H_ #define _FILE_NAME_H_ /* code */ #endif // #ifndef _FILE_NAME_H_

Il linguaggio C

45/243

Macro
Laltro utilizzo prevalente del preprocessore per definire macro. Il vantaggio di una macro che pu essere type-neutral (ma questo a volte uno svantaggio!), e che viene inlined direttamente nel codice, cosicch non c nessun overhead per la chiamata di funzioni. (In C++, possibile fare entrambe le cose usando templated functions e la keyword inline.) Una definizione di macro ha di solito la forma: #define MACRO_NAME(arg1, arg2, ...) [code to expand to] Per es.: #define INCREMENT(x) x++

Il linguaggio C

46/243

23

Macro e funzioni
Le macro somigliano a chiamate di funzioni, ma ci sono almeno un paio di trucchi da tenere presente: Il testo esatto di una macro viene inserito nella macro #define MULT(x, y) x * y usando int z = MULT(3 + 2, 4 + 2) z non assumer il valore 30!!! Espandendo la macro MULT: int z = 3 + 2 * 4 + 2; 2 * 4 viene valutato prima, e z assume il valore 13! Per evitare tutto questo, bisogna forzare la valutazione degli argomenti prima del resto del corpo della macro. Questo pu essere ottenuto usando le parentesi nella definizione di macro: #define MULT(x, y) (x) * (y) ora MULT(3+2, 4+2) viene espanso in (3+2) * (4+2)
Il linguaggio C 47/243

Macro e funzioni
E anche di solito una buona idea includere il codice della macro in parentesi se ci si aspetti che ritorni un valore. Altrimenti, ci sono problemi simili a quelli per la def. di costanti. Per es., la seguente macro, che aggiunge 5 allargomento, ha problemi quando viene inserita in uno statement pi grande: #define ADD_FIVE(a) (a) + 5 int x = ADD_FIVE(3) * 3; // this expands to (3) + 5 * 3, so 5 * 3 is evaluated first // Ora x 18, non 24! La soluzione racchiudere lintera macro in parentesi #define ADD_FIVE(a) ((a) + 5) int x = ADD_FIVE(3) * 3;
Il linguaggio C 48/243

24

Macro e funzioni
Daltra parte, una macro multilinea usata per i suoi side effects e non per calcolare un valore, va racchiusa tra parentesi graffe in maniera tale da poterla utilizzare anche dopo uno statement if #define SWAP(a, b) a ^= b; b ^= a; a ^= b; int x = 10; int y = 5; SWAP(x, y); // works OK // What happens now? if(x < 0) SWAP(x, y); Nel secondo esempio, solo il primo statement, a ^= b, gestito dal condizionale: gli altri due statements vengono eseguiti sempre! Usando le parentesi graffe, la cosa funziona: #define SWAP(a, b) {a ^= b; b ^= a; a ^= b;} Per inciso, gli argomenti non sono in parentesi perch non saranno mai espressioni!!!
Il linguaggio C 49/243

Macro e funzioni
Altri problemi Il problema pi irritante con le macro evitare di passare alle macro argomenti con "side effects". Side effect: ogni espressione che fa qualcosa oltre a valutare un valore. (es.: ++x valuta x+1, ma incrementa anche x). Le macro non valutano gli argomenti, ma si limitano ad inserirli nel testo. #define MAX(a, b) ((a) < (b) ? (b) : (a)) int x = 5, y = 10; int z = MAX(x++, y++); diventa: int x = (x++ < y++ ? y++ : x++) E y++ viene valutato due volte (y assumer il valore 12 anzich 11)
Il linguaggio C 50/243

25

Macro multilinea
Usando "\" Per indicare la continuazione di una linea, possibile scrivere le macro su pi righi per renderle pi leggibili. #define SWAP(a, b) { \ a ^= b; \ b ^= a; \ a ^= b; \ } Non serve uno slash alla fine dellultima riga (lo slash dice al preprocessore che la macro continua alla linea successiva, non che la linea la continuazione della linea precedente).

Il linguaggio C

51/243

Identificatori
Gli identificatori per il C possono essere usati per indicare variabili, funzioni, etichette e dati di tipo definito dall'utente. Un identificatore deve essere costituito da uno o pi caratteri. Il primo carattere di un identificatore (quello pi a sinistra) deve essere una lettera o una sottolineatura (underscore _ ). I caratteri successivi al primo possono essere numeri o lettere o sottolineature. Non sono ammessi caratteri di punteggiatura o altro, che hanno significati speciali.
Identificatori ammessi: Identificatori NON ammessi:
Num num$! _Num num n_um 1Num

num;pippo

Il linguaggio C

52/243

26

Identificatori
Il C, come in Java ma a differenza del Pascal, case-sensitive, ovvero considera caratteri minuscoli e caratteri maiuscoli come differenti. Quindi l'identificatore "NUMERO" diverso dall'identificatore "numero" e da "Numero". Non sono ammessi caratteri di punteggiatura o altro, che hanno significati speciali. Ogni identificatore, usato sia per identificare variabili sia per indicare funzioni, deve essere diverso dalle parole riservate utilizzate per il linguaggio C, e deve essere diverso anche dai nomi di variabili o funzioni delle librerie utilizzate durante la fase di linking.

Il linguaggio C

53/243

Tipi di dati semplici


Il C mette a disposizione i seguenti dati di tipo semplice, la dimensioni di alcuni di essi dipendente dallarchitettura: Tipo
char unsigned char short int unsigned short int long int unsigned long int float double void

Dim.
1 1 2 2 4 4 4 8 0

Min.
-127 0 -32768 0 -231 0 -3.2 * 10 38 -1.7 * 10 308

Max.
128 255 32768 216 1 231-1 232 1 3.2 * 1038 1.7 * 10308 -

Pascal
char integer longint real extended -

Java
byte short int float double void
Il linguaggio C 54/243

27

Tipi di dati semplici

Sui sistemi UNIX tutte le variabili dichiarate "int" sono considerate long int", mentre "short int" deve essere dichiarato esplicitamente. In alcune architetture, inoltre, il dato di tipo int costituito da un numero maggiore di byte. E' importante notare che in C non esiste un tipo di variabile booleano. Come variabili di tipo booleano si possono utilizzare variabili "char", "int", "long int" sia signed che unsigned.

Il linguaggio C

55/243

Tipi di dati semplici


Ciascuno di questi dati, quando viene valutato dal punto di vista booleano, valutato FALSO quando assume valore 0 (ZERO), se invece assume un valore diverso da zero valuato come VERO. Per esempio, la seguente istruzione condizionale if(-1) i=1; esegue l'assegnamento perch il valore -1 considerato VERO. Il tipo void rappresenta un tipo di dato indefinito, e ha due funzioni: serve ad indicare che una funzione non restituisce nessun valore, e serve per definire un puntatore che punta ad un dato generico.
Il linguaggio C 56/243

28

Variabili
Tutte le variabili devono essere dichiarate prima di essere usate. La dichiarazione delle variabili cos fatta: tipo ElencoVariabili; dove tipo uno dei tipi di dati ammessi dal C, e ElencoVariabili composto da uno o pi identificatori validi separati da una virgola. In questo modo ogni identificatore che compare in ElencoVariabili diventa una variabile di tipo tipo.

Il linguaggio C

57/243

Variabili
/* i una variabile di tipo int. */ int i;

/* l1 ed l2 sono long int */ long int l1, l2;

/* f, g, x, y sono variabili in virgola mobile */ float f, g, x, y;


Il linguaggio C 58/243

29

Variabili
Le variabili assumono caratteristiche diverse, in particolare caratteristiche di visibilit (scope) da parte delle funzioni, in dipendenza della posizione in cui avviene la dichiarazione. A seconda della posizione in cui avviene la dichiarazione, si distinguono tre tipi di variabili: Variabili Locali. Parametri Formali. Variabili Globali.

Il linguaggio C

59/243

Variabili locali
Definiamo Blocco di Istruzioni una sequenza di istruzioni C racchiusa tra una parentesi graffa aperta ed una parentesi graffa chiusa. Il corpo di una funzione (il codice C che implementa una funzione) un caso particolare di Blocco. Esempi di Blocchi:
Corpo di Funzione int funcA (double f) { int j; /* corretto */ printf("corpo funz.") int K; /* ERRORE */ }

Interno di un ciclo for for (i=0; i<10; i++) { int j; printf("ciclo") } func(j); /* ERRORE qui j NON e' visibile */

Ovunque, usando il trucco aperta-chiusa { ... } printf("codice C"); { int j; printf(ciao") }

Il linguaggio C

60/243

30

Variabili locali
Una variabile locale pu essere dichiarata dentro un qualunque blocco, ma in questo caso sempre e solo all'inizio del blocco, cio mai dopo che nel blocco sia stata scritta un'istruzione diversa da una dichiarazione), ed in tal caso: la variabile verr detta locale al blocco, potr essere acceduta solo dall'interno del blocco stesso, cio non visibile fuori dal blocco, avr un ciclo di vita che inizier nel momento in cui il controllo entra nel blocco, e terminer nel momento in cui il controllo esce dal blocco.

Il linguaggio C

61/243

Variabili locali

Le variabili locali sono caricate sullo stack quando il controllo entra nel blocco considerato, e vengono eliminate quando il controllo esce dal blocco in cui sono state dichiarate. Quando una variabile dichiarata nel corpo di una funzione, locale alla funzione, e assomiglia alle variabili locali del Pascal o di Java.

Il linguaggio C

62/243

31

Variabili come parametri formali


Sono le variabili che definiscono, nell'implementazione di una funzione, i parametri passati come argomenti alla funzione. Sono esattamente equivalenti ai parametri formali delle funzioni o procedure del Pascal o dei metodi Java. Per default i dati di tipo semplice sono passati per valore, come in Pascal. Invece i dati di tipo matrice sono passati per puntatore (la modalit var del pascal).
int func( float f , int i ) { printf ("param: f=%f i=%d\n,f,i); }

Il linguaggio C

63/243

Variabili come parametri formali


Come per le variabili locali, anche i parametri formali potranno essere acceduti solo dall'interno della funzione in cui sono stati dichiarati, avranno un ciclo di vita che inizier nel momento in cui il controllo entra nella funzione stessa, e terminer nel momento in cui il controllo esce dal blocco. I parametri formali vengono caricati sullo stack quando il controllo entra nel blocco considerato, e vengono eliminati quando il controllo esce dal blocco in cui sono state dichiarate. Se il Parametro passato per puntatore, il puntatore ad essere caricato sullo stack.

Il linguaggio C

64/243

32

Variabili globali e specificatore extern


Le variabili Globali sono quelle variabili che sono dichiarate fuori da tutte le funzioni, in una posizione qualsiasi del file. Una tale variabile allora verr detta globale, perch potr essere acceduta da tutte le funzioni che stanno nello stesso file ma sempre dopo la dichiarazione della variabile stessa, potr essere acceduta da tutte le funzioni che stanno in altri file in cui esiste una dichiarazione extern per la stessa variabile, ma sempre dopo la dichiarazione extern della variabile stessa, avr durata pari alla durata in esecuzione del programma.
Il linguaggio C 65/243

Variabili globali e specificatore extern

Per default, una variabile globale NomeVariabile visibile da tutti i moduli in cui esiste una dichiarazione di variabile extern di NomeVariabile, ovvero una dichiarazione siffatta: extern tipo NomeVariabile; che la solita dichiarazione di variabile preceduta per dalla parola extern.

Il linguaggio C

66/243

33

Variabili globali e specificatore extern


extern tipo NomeVariabile; Una tale dichiarazione dice al compilatore che: 1. nel modulo in cui la dichiarazione extern presente, la variabile NomeVariabile non esiste, ma esiste in qualche altro modulo, 2. il modulo con la dichiarazione extern autorizzato ad usare la variabile, e quindi il compilatore non si deve preoccupare se non la trova in questo file, perch la variabile esiste da qualche altra parte. 3. sar il Linker a cercare in tutti i moduli fino a trovare il modulo in cui esiste la dichiarazione senza extern per la variabile NomeVariabile.
Il linguaggio C 67/243

Variabili globali e specificatore extern


La variabile NomeVariabile viene fisicamente collocata solo nel modulo in cui compare la dichiarazione senza extern, (che deve essere uno solo altrimenti il Linker non sa quale scegliere) e precisamente nel punto in cui compare la dichiarazione. Nei moduli con la dichiarazione extern invece rimane solo un riferimento per il linker.

Il linguaggio C

68/243

34

Variabili globali e specificatore static


Se vogliamo che una certa variabile globale NomeVariabile, collocata in un certo file, non sia accedibile da nessun altro modulo, dobbiamo modificare la sua dichiarazione in quel modulo, facendola precedere dalla keyword static ottenendo una dichiarazione di questo tipo. static tipo NomeVariabile; In tal modo, quella variabile potr ancora essere acceduta dalle funzioni nel suo modulo, ma da nessun altro modulo.

Il linguaggio C

69/243

Problemi con variabili globali


Esempio, Problemi con variabili globali, all'interno dello stesso file in cui le variabili globali sono definite
#include <stdio.h> int K=2; /* variabile int main(void) { int i=34; printf("i = %d \n", int J=0; printf("K = %d \n", printf("g = %f \n", funzione1(); exit(0); }

globale visibile da tutte le funzioni */

i ); /* stampa i cio 34, corretto */ /* ERRORE, dichiarazione dopo istr. */ K ); g );

double g=13; void funzione1(void) { printf("g = %f \n", g ); printf("i = %d \n", i ); }

/* stampa g, cio 13, corretto */ /* NON VEDE i, ERRORE */

Il linguaggio C

70/243

35

Problemi tipici in programmi con pi moduli


Il nostro programma costituito da due moduli, var.c e main.c. main.c contiene il main del programma, ed alcune funzioni, tra cui la funzione f , che accetta come parametro formale un intero e lo stampa. var.c contiene alcune variabili intere, alcune (A) globali, altre (C) globali ma statiche e quindi visibili solo dentro il modulo var.c.
#include <stdio.h> extern int A; /* file var.c */ int A=1; static int C; void main(void) { f(A); /*corretto */ void f(int c){ printf("c=%d\n",c); } /* file main.c */ extern int C;

f(B); /*error C2065: 'B' : undeclared identifier*/ f(C); /*error LNK2001:unresolved external symbol _C*/ }
Il linguaggio C 71/243

Problemi tipici in programmi con pi moduli


Il modulo main.c contiene due errori, perch: 1) con l'istruzione f(C) main tenta di accedere alla variabile C che non pu vedere perch protetta dallo specificatore static che la rende visibile solo dentro var.c. Il compilatore non si accorge dell'errore perch main.c ha una dichiarazione extern per C, e il compilatore si fida e fa finta che C esista e sia accedibile in un qualche altro modulo. Il linker invece, che deve far tornare i conti, non riesce a rintracciare una variabile C accedibile, e segnala l'errore indicando un "error LNK2001: unresolved external symbol" perch non trova C.
Il linguaggio C 72/243

36

Problemi tipici in programmi con pi moduli


2) con l'istruzione f(B) main tenta di accedere alla variabile B che non definita nel modulo main.c, nemmeno da una dichiarazione extern. il compilatore si accorge dell'errore e lo segnala con il messaggio "error C2065: 'B' : undeclared identifier".

Il linguaggio C

73/243

Lo specificatore static
Lo specificatore static, applicato ad una variabile locale ordina al compilatore di collocare la variabile non pi nello stack all'atto della chiamata alla funzione, ma in una locazione di memoria permanente (per tutta la durata del programma), come se fosse una variabile globale. A differenza delle variabile globali, la variabile locale static sar visibile solo all'interno del blocco in cui stata dichiarata. L'effetto che la variabile locale static: viene inizializzata una sola volta, la prima volta che la funzione viene chiamata. mantiene il valore assunto anche dopo che il controllo uscito dalla funzione, e fino a che non viene di nuovo chiamata la stessa funzione.

Il linguaggio C

74/243

37

Lo specificatore static
Vediamo un esempio di utilizzo, per contare il numero delle volte che una data funzione viene eseguita.
#include <stdio.h> void f(void) { /* file contaf.c */

/* viene inizializzato solo una volta */


static int contatore=0; contatore = contatore + 1; printf("contatore =%d\n", contatore); } void main() { /* per vedere cosa succede in f*/ int i; for( i=0; i<100; i++ ) f(); }
Il linguaggio C 75/243

Istruzioni di assegnamento
L'operatore di assegnamento in C, come in Java, =, mentre in Pascal := . Quindi l'istruzione di assegnamento diventa: NomeVariabile = espressione;

dove espressione pu essere semplice come una singola costante, o essere una combinazione di variabili, operatori, funzioni e costanti. NomeVariabile deve essere una variabile, e mai una funzione.

Il linguaggio C

76/243

38

Istruzioni di assegnamento
Esempi: int func( float f); /* prototipo */ int i , j;

i = 0; i = func(3.8); j = i ; func( 3.8 ) = 100; in compilazione

corretta corretta corretta NON AMMESSA, errore

Il linguaggio C

77/243

Assegnamento
L'assegnamento in C produce un risultato, che il valore assegnato alla variabile a sinistra dell'uguale, ed del tipo della variabile. Tale risultato pu essere utilizzato: per un ulteriore assegnamento, tenendolo alla destra di un uguale, valgono perci istruzioni composite del tipo: int i, j , k ; k = j = i = 10; che assegnano alla variabile a sinistra il valore assegnato alla variabile subito a destra, cio si assegna prima il valore 10 ad i, poi dato che i vale 10 si assegna il valore 10 a j, poi visto che j vale 10 si assegna 10 a k. Il vantaggio un'esecuzione pi veloce delle istruzioni di assegnamento separate, perch il dato da assegnare gi caricato sui registri.
Il linguaggio C 78/243

39

Assegnamento
come espressione ad esempio come condizione in un if, eventualmente tenendolo dentro parentesi per evitare confusioni if ( i=10 ) func(9); i=10 vale 10 che diverso da zero e vale VERO

Il linguaggio C

79/243

Conversioni di tipo negli assegnamenti


void main(void) { int i,j; double g,x; x = i = g = 10.3; printf("x=%f i=%d g=%f\n",x, i, g ); } Tale programma stampa la stringa: x=10.0 i=10 g=10.3

Il linguaggio C

80/243

40

Conversioni di tipo negli assegnamenti

void main(void) { int i,j; double g,x; i = x = g = 10.3; printf("x=%f i=%d g=%f\n",x, i, g); } Tale programma stampa la stringa: x=10.3 i=10 g=10.3

Il linguaggio C

81/243

Conversioni di tipo negli assegnamenti


Valutiamo cosa succede nell'assegnamento multiplo del primo programma. L'istruzione di assegnamento g=10.3; assegna un valore floating point 10.3 alla variabile floating point g, e 10.3 il risultato dell'assegnamento pi a destra. Tale risultato (10.3) viene assegnato ad i che per una variabile intera, e quindi viene persa la parte decimale e ad i viene assegnato il valore 10, che il risultato dell'assegnamento ad i.

Il linguaggio C

82/243

41

Conversioni di tipo negli assegnamenti


Questo risultato (10) viene quindi assegnato a x che lo memorizza come floating point 10.0 , che il risultato dell'assegnamento ad x, ed anche il risultato finale di tutto l'assegnamento multiplo. Nell'assegnamento multiplo del programma a destra, invece, l'assegnamento ad x viene fatto dandogli il valore ottenuto dall'assegnamento di 10.3 a g che floating point, e quindi memorizza 10.3. N.B. In entrambi i casi il compilatore si accorge che c'e' una perdita di dati nel passaggio da double a int e avverte il programmatore con un warning di questo tipo: warning: '=' : conversion from 'double ' to 'int ', possible loss of data
Il linguaggio C 83/243

Conversioni di tipo nelle istruzioni di assegnamento


Il problema delle conversioni di tipo esiste quando l'assegnamento coinvolge variabili (o una variabile ed una costante) di tipo diverso. La regola di conversione che vale in C la seguente: Il C converte il valore alla destra del segno di uguale (il risultato della valutazione dell'espressione) nel tipo del lato sinistro (la variabile destinazione dell'assegnamento).

Il linguaggio C

84/243

42

Conversioni di tipo nelle istruzioni di assegnamento


Tipo Destinazione
unsigned char char char short int

Tipo Espressione
char short int long int (4 byte) long int

Possibile Perdita di Informazione


se valore > 127 byte + significativo 3 byte + significativi 2 byte + significativi

Informazione Conservata
7 bit meno significativi byte - significativo byte - significativo byte - significativo la parte intera se minore del valore massimo per short int, un valore senza senso altrimenti risultato arrotondato alla precisione del float

short int

float, double

almeno la parte frazionaria

float

double

arrotondamento del risultato

Il linguaggio C

85/243

Conversioni
Vediamo un esempio, conversione di uno short int in un char: short int i;
byte pi significativo

char ch;
byte meno significativo

ch = i ;

short int i

char ch

Il linguaggio C

86/243

43

Inizializzazione delle variabili


All'atto della dichiarazione di una variabile, possibile assegnare un valore alla variabile stessa, mediante un'estensione della dichiarazione cos fatta: Tipo NomeVariabile = costante; Questa inizializzazione si traduce in due fatti diversi a seconda della collocazione della variabile che stiamo dichiarando: Se la variabile una variabile globale, il valore viene assegnato alla variabile prima di cominciare l'esecuzione del programma,

Il linguaggio C

87/243

Inizializzazione delle variabili


Se la variabile una variabile locale l'inizializzazione viene effettuata nella posizione dello stack in cui viene posizionata la variabile locale quando si entra nel blocco a cui appartiene, e questa inizializzazione viene ripetuta ogni volta che si entra in quel blocco. Fa eccezione il caso in cui la variabile locale sia preceduta dallo specificatore static, nel qual caso la variabile non viene messa sullo stack ma in una zona dati, e l'inizializzazione viene effettuata una ed una sola volta, nel momento in cui il controllo entra per la prima volta nella funzione in cui la variabile stata dichiarata. Se la variabile un parametro formale di una funzione, non pu essere inizializzata.

Il linguaggio C

88/243

44

Inizializzazione delle variabili


Esempi: int i = 0; i inizializzato a zero f inizializzato a 13.7

double f = 13.7;

int j=2, k, m=3;

j inizializzato a zero, k non inizializzato, m inizializzato a 3

#define NUM 10 int n=NUM; int p=0xFF; int q=011; n inizializzato a 10 p inizializzato a 255 (costante esadecimale) q inizializzato a 9 (costante ottale)

Il linguaggio C

89/243

Costanti
Le costanti sono entit che non possono essere modificate durante l'esecuzione del programma, ma mantengono invariato il loro valore per tutta la durata del programma. Le costanti possono essere: 1. scritte direttamente nel programma scrivendo il valore che interessa. Ad es. i = 1; f = 137.12; k = 0xFF;

2. indicate mediante un simbolo tramite una direttiva al preprocessore #define nel qual caso il preprocessore, sostituendo al simbolo il valore, ci riporta al caso precedente, Ad es. #define SIZE 10 int i = SIZE; viene tradotto dal preprocessore in int i = 10; 3. indicate mediante un simbolo (tramite la keyword const) che viene inizializzato al valore che interessa e mantenuto in una locazione di memoria come una variabile, ma che non pu essere modificato.
Il linguaggio C 90/243

45

Costanti
La dichiarazione di un dato di tipo costante deve essere cos: const oppure Tipo Ad es. const NomeVariabile = costante; Tipo NomeVariabile = costante;

const int i = 10;

Dichiarare una costante mediante la const impone al compilatore di associare il simbolo ad una certa locazione di memoria separata dal resto dei dati, e di mantenerne in questa locazione il valore assegnato in via di inizializzazione. Comunque, in tutti i 3 casi, resta sempre il problema di come scrivere un certo valore costante (numerico intero, numerico in virgola mobile, stringa) all'interno in una certa istruzione. Il formato varia al variare del tipo di dato. Esiste inoltre un caso particolare di uso delle costanti che sar descritto a proposito delle funzioni e dei loro parametri formali, e che serve a dichiarare che un parametro formale non modificabile.
Il linguaggio C 91/243

Costanti numeriche intere


Le costanti numeriche intere: sono espresse da numeri senza componente frazionaria, possono essere preceduti eventualmente da un segno (+ o -), e possono essere espresse in notazione decimale (base 10), esadecimale (base 16) o ottale (base 8). In notazione decimale il numero espresso nel modo consueto, in notazione esadecimale il numero deve essere preceduta dal segno 0x, in notazione ottale il numero deve essere preceduta da uno 0. Per indicare il tipo di dato con cui rappresentare la costante, si utilizzano dei caratteri da porre immediatamente dopo i caratteri numerici.
Il linguaggio C 92/243

46

Costanti numeriche intere


Per default le costanti intere sono considerate int. Se la costante intera troppo grande per essere contenuto in un int, viene considerato long. Una costante unsigned int deve essere seguita dal carattere u o U. es: 41234U Una costante long deve essere seguita dal carattere L o l. es: 49761234L Una costante unsigned long deve essere seguita dai caratteri UL o ul.

Il linguaggio C

93/243

Esempi:
Valore 0 1 8 -8 17 -30 100000 -100000 Tipo int int int int int int long int long int DECIMALE 0 1 8 -8 17 -30 100000L 100000l -100000L -100000l ESADEC. 0x0 0x1 0x8 -0x8 0x11 -0xFD 186A0L 186A0l -186A0L -186A0l OTTALE 00 01 010 -010 021 036 -303240L -3032040l -303240L -3032040l
Il linguaggio C 94/243

47

Costanti numeriche in virgola mobile


Le costanti numeriche in virgola mobile sono scritte in rappresentazione decimale nel modo consueto, ovvero: un eventuale segno, la componente intera, il punto decimale ed eventualmente la componente decimale. es: -10.4 37.235f 7. .001

oppure in formato esponenziale, cio nella forma XeM con il significato di X*10M, dove X una componente floating point rappresentata come prima indicato, M un intero es: -1.04e+1 0.37235e+2L 0.7e+1 1.e-2

Inoltre, il tipo di dato usato per rappresentare la costante sar: il tipo double se non viene specificato un suffisso alla costante. il tipo float se invece viene aggiunto un suffisso f o F , il tipo long double a 16 byte se viene aggiunto un suffisso l o L.

Il linguaggio C

95/243

Costanti di tipo char


I caratteri, cio i tipi char (signed e unsigned) servono a rappresentare un insieme di simboli quali i simboli della tastiera a A b B 1 2 ed alcuni caratteri cosiddetti "di controllo" quali Enter, Tab, BackSpace, NewLine. Lo standard ASCII associa un carattere ai valori da 0 a 127, mentre per i caratteri da 128 a 255 l'associazione varia a seconda del computer. I caratteri di controllo sono i primi 32 della tabella ASCII, cui seguono i caratteri stampabili, cio visualizzabili da un comune editor. Qui di seguito un estratto dalla tabella ASCII. Il tipo char del C pi simile al tipo byte di Java che non al tipo char. Carattere: Codice ASCII: 0 48 1 49 A 65 B 66 a 97 b 98

Il linguaggio C

96/243

48

Variabili di tipo carattere


Una variabile di tipo carattere quindi memorizza un certo valore, da 0 a 255, che corrisponde ad un certo carattere. In un assegnamento ad una variabile di tipo carattere, potremo assegnare o il codice numerico o il carattere rappresentato da quel codice numerico. Una costante di tipo carattere quindi potr essere rappresentata o come valore numerico da 0 a 255, oppure come simbolo corrispondente a quel codice, ad es. ad un certo char potremo assegnare o il carattere '0' o il valore numerico 48.

Il linguaggio C

97/243

Variabili di tipo carattere


La rappresentazione numerica delle costanti di tipo carattere fatta mediante la rappresentazione decimale del codice numerico. Ad es., assegnando ad un char il valore 97 gli assegniamo il carattere 'a'. La rappresentazione simbolica delle costanti di tipo carattere non soffre dei problemi dovuti alla differenze tra le codifiche dei caratteri, perch scrive nel codice direttamente il simbolo che si vuole ottenere, e non una sua rappresentazione numerica.

Il linguaggio C

98/243

49

Variabili di tipo carattere


D'altro canto necessario delimitare la rappresentazione simbolica di un certo carattere separandola dagli altri elementi del linguaggio C. A questo scopo la rappresentazione delle costanti di tipo carattere mediante i simboli viene fatta scrivendo il simbolo all'interno di due apici singoli. ad es. 'L' indica il carattere che rappresenta il simbolo L. Per, poich non tutti i caratteri sono visualizzabili da un editor (ad es. i caratteri di controllo) deve essere previsto un modo simbolico per indicare un carattere che non pu essere visualizzato. A tal scopo sono definite le cosiddette "sequenze di escape", ovvero dei simboli stampabili che quando sono precedute dal carattere \ vengono interpretate dal C come un carattere diverso. Ad es. la sequenza di escape '\n' indica il carattere new line cio l'andata a capo di una riga di testo. Le sequenze di escape vanno racchiuse tra apici singoli come i simboli dei caratteri.
Il linguaggio C 99/243

Sequenze di escape
Le sequenze di escape vengono introdotte anche per poter indicare simbolicamente alcuni caratteri particolari ( ' " ) che hanno un significato speciale nel linguaggio C Le principali sequenze di escape disponibili in C sono le seguenti: \a suono \b BackSpace \n New Line, andata a capo \r carriage return \t il Tab orizzontale \\ il BackSlash, che il delimitatore delle sequenze di escape \' il carattere apice singolo ' che in C delimitatore di carattere \ il carattere doppio apice " che in C delimitatore di stringa Notare l'analogia tra l'uso delle sequenze di escape \\ \' \" in C e l'uso della coppia di apici singoli '' dentro la write del Pascal o la println di Java per stampare un solo apice singolo '.
Il linguaggio C 100/243

50

Sequenze di escape
Infine possibile rappresentare i caratteri ancora in forma numerica mediante rappresentazione ottale o esadecimale, scrivendo all'interno della coppia di apici singoli: - un BackSlash seguito dalla rappresentazione ottale del numero, oppure - un BackSlash seguito da una x seguita dalla rappresentazione esadecimale. rappr. ottale rappr. esadecimale
char ch; ch='A'; ch=65; ch='\x41'; ch='\101';

'\OOO '\xhh

fino a tre caratteri ottali (0:7), (max \377) fino a due (hh) caratteri esadec. (0:9,A:F)

es, sono equivalenti i seguenti assegnamenti, nella stessa colonna:


ch=' " '; ch=34; ch='\x22'; ch='\42'; ch='\042'; ch='\"'; ch='\0042'; errore,4 ottali ch=' \' '; ch=39; ch='\x27'; ch='\47'; ch='\047'; ch='\047'; ch=' ' '; /*errore*/ ch='\\'; ch=92; ch='\x5C'; ch='\134'; ch='\'; /*errore*/

non ammesso ch='\400

Il linguaggio C

101/243

Costanti di tipo stringa


In C la stringa vera e propria non esiste, si usa come stringa un vettore di n+1 char , in cui al carattere in ultima posizione assegnato il valore 0, ovvero il carattere nullo '\0'. Questo carattere particolare rappresenta il delimitatore finale della stringa, ovvero l'indicazione che la stringa finita.

\0

Quindi, una stringa in C internamente rappresentata da un vettore di caratteri terminati da un carattere nullo. La lunghezza nota solo dopo avere trovato il carattere 0 in fondo. Una costante di tipo stringa, ovvero una costante di tipo vettore di caratteri viene scritta come una sequenza di caratteri racchiusa tra 2 doppi apici, che non fanno parte della stringa. Ciascun carattere nella stringa pu essere rappresentato simbolicamente o per mezzo di una sequenza di escape o in forma esadecimale o ottale.
Il linguaggio C 102/243

51

Costanti di tipo stringa


Esempi di costanti stringa sono:

"questA stringa giusta" "quest\x41 stringa e' giusta"

'' e' un caratt. non ASCII ' ' ' delimitatore di carattere, non di stringa, quindi non c'e' confusione

"quest\x41 stringa e\47 giusta" "quest\101 stringa e\047 giusta" "questA stringa " sbagliata" "questA stringa \" stata corretta

' ' ' scritto in ottale con 2 cifre(occhio) ' ' ' scritto in ottale a 3 cifre ERRORE, '"' delimitatore di stringa

"aa12bb" uguale a "aa\0612bb" ma diverso da "aa\612bb" NB: la const a destra d errore in compilazione, "numeric constant too large"
Il linguaggio C 103/243

Operatori in C
Il linguaggio C mette a disposizione degli operatori, ovvero dei simboli speciali che indicano al compilatore di generare codice per far eseguire delle manipolazioni logiche, matematiche o di indirizzamento sulle variabili che costituiscono gli operandi dell'operatore. Le funzionalit degli operatori in C sono in linea di massima le stesse di tutti i linguaggi di programmazione evoluti, ad es. il Pascal. Il C si distingue perch mette a disposizione alcuni operatori particolari che servono ad effettuare velocemente alcune operazioni semplici. Le operazioni realizzate da questi operatori sono molto veloci perch la semplicit delle funzionalit rende possibile implementare l'operazione in una maniera che sfrutta appieno la potenzialit dei registri del calcolatore, limitando il numero degli accessi alla memoria, e lavorando il pi possibile coi registri. Prima di vedere alcuni operatori, bene introdurre una caratteristica molto particolare del C, il Type Casting, ovvero la modifica del tipo di dato.

Il linguaggio C

104/243

52

Type Casting
Molto spesso il risultato di un'operazione dipende dal tipo di dato che coinvolto nell'operazione. Ad es. la divisione tra interi ha un risultato diverso dalla divisione tra floating point, anche partendo dagli stessi dati iniziali. 5.0/2.0=2.5 5/2=2 Questo perch a seconda del tipo di dati coinvolti nelle operazioni, le operazioni sono svolte in modo diverso, o utilizzando registri o porzioni di registri diversi. Una caratteristica peculiare del C che permette durante l'esecuzione di un'operazione, o durante il passaggio di parametri ad una funzione all'atto della chiamata, di modificare il tipo del o dei dati coinvolti nell'operazione.
Il linguaggio C 105/243

Type Casting
Ci non significa affatto che la variabile (o la costante, o il risultato di un'espressione) interessata cambia le sue caratteristiche (dimensioni, propriet, ecc..) di tipo. Significa invece che se la variabile deve essere utilizzata in dei calcoli, viene copiata in un registro sufficentemente grande per contenere il nuovo tipo di dato e per svolgere le operazioni di conversione, ed il valore del registro caricato viene convertito secondo le caratteristiche del nuovo tipo di dato. Solo dopo questa conversione, l'operazione viene effettuata, secondo le regole proprie del nuovo tipo di dato, ottenendo quindi come risultato un valore coerente con il nuovo tipo.
Il linguaggio C 106/243

53

Type Casting
La conversione di tipo type-casting in qualche caso viene effettuata implicitamente dal compilatore C, ma pu anche essere forzata dal programmatore mediante un operatore unario detto cast, che opera in questa maniera. ( nome_nuovo_tipo ) espressione dove "nome_nuovo_tipo" un tipo di dato primitivo o definito dall'utente, ed "espressione" pu essere una variabile, una costante o un'espressione complessa. es. (double) i; "Espressione" viene risolta fino ad arrivare ad un risultato (di un suo certo tipo), poi il risultato viene convertito nel tipo "nome_nuovo_tipo" mediante opportune operazioni pi o meno complesse a seconda della conversione. A questo punto abbiamo come risultato della conversione un dato di tipo "nome_nuovo_tipo" con un certo valore, che pu essere utilizzato secondo le regole definite per "nome_nuovo_tipo".
Il linguaggio C 107/243

Type casting: esempio


Vediamo subito un esempio di cosa succede: int i=5, j=2; double f; f=i/j; int i=5, j=2; double f; f=(double)i/(double)j;

A questo punto f vale 2

A questo punto f vale 2.5

Un cast quindi equivale ad assegnare l"espressione" ad una variabile del nuovo tipo specificato, che viene poi utilizzata al posto dell'intera espressione.
Il linguaggio C 108/243

54

Type casting
L'esempio riportato mostra un caso in cui il type cast modifica fortemente il risultato di un'operazione, che comunque avrebbe potuto essere effettuata, pur con risultati diversi, senza type cast. Il casting viene per utilizzato spesso anche nel passaggio di parametri a funzioni, qualora il programmatore abbia necessit di passare alla funzione chiamata un parametro formalmente diverso da quello richiesto dalla funzione. Ad esempio qualora si debba passare alla funzione un puntatore ad una struttura, di tipo diverso da quella che la funzione si aspetta. Il compilatore dovrebbe segnalare l'errore. Mediante un type cast del parametro passato all'atto della chiamata (di cui il programmatore si deve assumere la responsabilit relativamente alla correttezza) si pu convincere il compilatore della correttezza formale dell'operazione.

Il linguaggio C

109/243

Type casting
Abbiamo finora parlato di type-casting forzato, ovvero imposto dall'utente. In realt il C effettua automaticamente delle conversioni implicite di tipo, in particolare quando effettua operazioni matematiche tra operandi di tipo diverso.

Il casting viene effettuato automaticamente dal compilatore C quando due operandi di un'operazione binaria sono tra loro diversi. In tal caso l'operando di tipo pi piccolo viene convertito nel tipo pi grande, senza perdita di informazioni.

Il linguaggio C

110/243

55

Type casting
Quindi, dati due operandi di tipo diverso, il cast automatico si ha secondo queste regole:
Tipo 1 operando long double double float double int long int Tipo 2 operando double float (long) int (long) int char , short (short) int Tipo risultato long double double float double int long int

Il linguaggio C

111/243

Type casting
Attenzione, il casting automatico pu dare delle false sicurezze. Nell'esempio seguente, prima viene eseguita la divisione (tra due interi, quindi nessuna conversione) e c' perdita di informazione, e solo in un secondo tempo c l'assegnamento al float. int i=5, j=2; double f; f = (double) (i /j); /* f diventa 2.0 */ N.B. come abbiamo gi visto, anche nell'assegnamento il C effettua conversioni di tipo automatiche, convertendo il valore del lato destro nel tipo del lato sinistro, eventualmente perdendo informazioni quando si passa da un tipo ad un tipo pi piccolo

Il linguaggio C

112/243

56

Operatori aritmetici
Gli operatori aritmetici + - * / % sono operatori presenti in tutti i linguaggi di programmazione. Sono operatori binari (servono due operandi):

Somma Differenza Cambio segno Prodotto Divisione Resto

+ * / %

x+y x-y -x x*y x/y n%m

valore del tipo pi grande valore del tipo pi grande stesso tipo valore del tipo pi grande valore float tra float o valore int tra int resto modulo m solo tra interi

Il linguaggio C

113/243

Operatori di incremento e decremento


Gli operatori incremento ++ e decremento -- sono operatori unari (serve un unico operando): aggiungono (tolgono) l'unit (1) all'operando. Equivalgono ad assegnare alla stessa variabile il proprio valore incrementato (decrementato) di uno, ma lavorano velocemente perch usano (bene) registri. x++ x-x=x+1 x=x-1

Attenzione, sono previste due modalit di esecuzione, qualora l'incremento sia parte di un'istruzione pi complessa. x++; ++x; prima usa x nell'istruzione, poi lo incrementa prima incrementa x, poi lo usa nell'istruzione

Il linguaggio C

114/243

57

Operatori di incremento e decremento


Le due istruzioni di incremento (decremento) isolate hanno lo stesso effetto. Il risultato cambia quando vengono usate in istruzioni meno semplici. es. int n, m=0; n = m++; n vale 0, m vale 1 int n, m=0; n = ++m; n vale 1, m vale 1

Priorit:

++ -*/% +-

massima (cambio segno)

minima
Il linguaggio C 115/243

A pari priorit si valuta a partire da sinistra, a meno delluso di parentesi.

Operatori relazionali e logici


Ricordiamo che in C il valore booleano Falso equivale a 0, e che Vero equivale a diverso da 0. Gli operatori logici (equivalenti ad AND OR NOT del Pascal) e gli operatori relazionali (minore maggiore uguale diverso ecc..) restituiscono 0 quando il risultato falso e 1 quando il risultato vero.
Operatori Logici AND OR NOT && || ! Operatori Relazionali Maggiore Maggiore o uguale Minore Minore o uguale Uguale Diverso > >= < <= == !=
Il linguaggio C 116/243

(doppio simbolo =)

58

Operatori relazionali e logici


La priorit degli operatori relazionali e logici minore della priorit degli operatori aritmetici, quindi in un'espressione complessa prima vengono valutate le operazioni aritmetiche, e solo in un secondo momento gli operatori relazionali. Ad es., l'espressione x <= 5+y viene valutata prima effettuando la somma tra 5 ed y, ed il risultato viene confrontato con x. Ci equivale alla valutazione di questa espressione (x <= (5+y))

Il linguaggio C

117/243

Operatori relazionali e logici


E' buona norma abituarsi ad usare parentesi tonde e spaziature in abbondanza per delimitare visivamente almeno le espressioni pi semplici che entreranno poi in relazione tramite operatori logici. Si commettono di solito meno errori di programmazione guidando la valutazione delle espressioni tramite alcune parentesi tonde poste ad hoc, piuttosto che non affidandosi esclusivamente alla precedenza degli operatori. - Le espressioni sono valutate da sinistra a destra. - Le priorit all'interno del gruppo degli operatori logici e relazionali sono le seguenti:
! > == && ||
Il linguaggio C 118/243

priorit massima (valutato per primo) >= != < <=

59

Operatori relazionali e logici


Ad es. valutiamo l'espressione qui di seguito: 10>5 &&!(10<9) || 3<=4 10>5 vale 1 !(10<9) vale 1 otteniamo: otteniamo: otteniamo: 1 && 1 || 3<=4 1 || 3<=4 1 || 1 1 && 1 vale 1 3<=4 vale 1 1 || 1 vale 1

Il risultato finale dell'espressione 1. Attenzione ad evitare di usare espressioni scritte cos. Utilizzate le parentesi tonde. La stessa espressione, scritta in forma pi leggibile, riportata qui sotto: ( (10>5) && (!(10<9)) ) || (3<=4) La sequenza delle parentesi impone l'ordine di valutazione degli operatori, ed evidenzia il criterio logico con cui si deve essere formata l'espressione.

Il linguaggio C

119/243

Valutazione abbreviata di espressioni connesse da operatori logici


Consideriamo una espressione formata da espressioni connesse da operatori logici && e || come ad esempio ESPR1 && ESPR2 || ESPR3 Tali espressioni sono valutate da sinistra verso destra, quindi, ricordando che l'operatore AND logico && ha priorit maggiore dell'operatore OR logico, l'espressione nel suo complesso pu essere interpretato come segue: ( ESPR1 && ESPR2 ) || ESPR3 nel senso che dovrebbe essere valutata prima l'espressione ( ESPR1 && ESPR2 ) ed in un secondo tempo l'espressione ESPR3, per poi mettere in OR logico i due risultati. Ci abbastanza vero, ma in realt il compilatore C sufficientemente furbo da generare il codice corrispondente alla valutazione di questa espressione, in modo tale da interrompere la valutazione dell'espressione non appena determina la VERITA' o la FALSITA' dell'INTERA espressione. Lo stesso concetto vale per porzioni di espressioni, la cui valutazione viene interrotta quando viene determinata la Verit o Falsit della porzione di espressione.
Il linguaggio C 120/243

60

Valutazione abbreviata di espressioni connesse da operatori logici


Vediamo come il compilatore C organizza la valutazione di: ( ESPR1 && ESPR2 ) || ESPR3

viene valutata ESPR1 se ESPR1 falsa (0) NON VIENE VALUTATA ESPR2 perch in ogni caso l'espressione ESPR1 && ESPR2 sar falsa viene valutata ESPR3 se ESPR3 falsa (0) l'espressione totale vale falso (0) se invece ESPR3 vera (!=0) l'espressione totale vale vero. in ogni caso non abbiamo valutato ESPR2 perch era inutile

Il linguaggio C

121/243

Valutazione abbreviata di espressioni connesse da operatori logici


se ESPR1 vera VIENE VALUTATA ESPR2 se ESPR2 vera allora l'espressione ESPR1 && ESPR2 sar vera, ed anche l'espressione intera ESPR1 && ESPR2 || ESPR3 sar vera, indipendentemente dal valore di ESPR3, quindi ESPR3 non viene valutata, ed il risultato finale vero. se ESPR2 falsa allora l'espressione ESPR1 && ESPR2 sar falsa, e deve essere valutata ESPR3 per conoscere il risultato finale. viene valutata ESPR3 se ESPR3 vera il risultato finale vero se ESPR3 falsa il risultato finale falso Questo modo di valutare le espressioni, velocizza le operazioni, perch non esegue operazioni inutili, ma deve essere attentamente valutato, se all'interno delle espressioni sono presenti istruzioni o funzioni che generano qualche effetto secondario, perch tali funzioni potrebbero essere eseguite oppure no a seconda del risultato booleano delle varie porzioni dell'espressione.
Il linguaggio C 122/243

61

Valutazione abbreviata di espressioni connesse da operatori logici


Supponiamo di avere un vettore di interi, con SIZE elementi, vogliamo stampare tutti gli elementi del vettore o fino all'ultimo o fino a che non incontriamo un elemento che vale 17, nel qual caso non vogliamo stampare 17 e vogliamo interrompere la stampa. Possiamo sfruttare la valutazione abbreviata per scrivere un codice cosi fatto: #define SIZE 10; int Vet[SIZE]; int i; .... scrittura degli n elementi del vettore Vet i=0; while ( (i<SIZE) && (Vet[i]!=17) ) printf( "Vet[%d]=%d\n", i , Vet[i++] ); l'espressione Vet[i]!=17 viene valutata solo espressione (i<SIZE). Ci consente di evitare di accedere alla se stata valutata vera la prima variabile Vet in una posizione maggiore o uguale della posizione numero SIZE, perch si cadrebbe fuori dal vettore e si potrebbe causare un eccezione di sistema del tipo segmentation fault che causa l'interruzione traumatica del programma.
Il linguaggio C 123/243

Valutazione di espressioni
Uno degli errori pi comuni per chi comincia a programmare in C, consiste nell'ostinarsi a voler scrivere codice "stretto" e poco commentato. E' sempre un errore, perch: 1. 2. il codice va modificato nel tempo, ed il codice scritto in forma compatta pi difficile da capire, anche per chi l'ha scritto. l'aggiunta di parentesi tonde e spazi per rendere pi visibile e comprensibile il codice non diminuisce le prestazioni.

Il linguaggio C

124/243

62

Operatori di assegnamento ed espressioni aritmetiche


Il C mette a disposizione alcuni operatori particolari, che coniugano un'operazione aritmetica all'operazione di assegnamento. Somma e Assegnamento Differenza e Assegnamento Prodotto e Assegnamento Divisione e Assegnamento Resto e Assegnamento x += y x -= y x *= y x /= y x %= y equivale a x = x+y equivale a x = x-y equivale a x = x*y equivale a x = x/y equivale a x = x%y

Istruzioni C del seguente tipo (dove op un'operazione aritmetica) espressione_1 equivalgono all'istruzione espressione_1 = espressione_1 op espressione_2 op= espressione_2

in cui l'espressione espressione_1 viene valutata due volte.


Il linguaggio C 125/243

Operatori di assegnamento ed espressioni aritmetiche


Questi operatori composti servono a velocizzare il codice, perch evitano di valutare ripetutamente una stessa espressione, e in qualche caso riescono a mantenere i valori valutati nei registri, quindi minimizzano l'accesso alla memoria.

Ad es. se noi avessimo un vettore di interi Vet, e volessimo aggiungere un 3 all'elemento in posizione i+7*k, dovremmo scrivere (l'accesso alle posizioni di un vettore si effettua mediante l'uso di parentesi quadre) un'istruzione del tipo: Vet[i+7*k] = Vet[i+7*k] +2; dovendo cos valutare due volte l'espressione i+7*k Invece con l'istruzione Vet[i+7*k] += 2; l'espressione i+7*k viene valutata una volta sola.

Il linguaggio C

126/243

63

Operatore sizeof()
Il C mette a disposizione un operatore unario, che restituisce la dimensione della variabile o dello specificatore di tipo passato in input. Serve a conoscere le dimensioni di alcuni tipi di dati, che potrebbero cambiare al variare dell'architettura su cui il programma deve girare, come ad esempio cambiano le dimensioni degli interi int. L'operatore sizeof prende in input o una variabile o un identificatore di tipo, e restituisce la dimensione in byte del dato. Il dato pu anche essere un dato definito dall'utente, non solo un tipo di dato primitivo.

Il linguaggio C

127/243

Operatore sizeof()
Es:
int I; printf("dimensione di I: %d \n", sizeof(I) ); /* stampa 2 in Windows */ /* stampa 4 in Linux */ printf("dimensione del float: %d \n", sizeof(float) ); /* stampa 4 */

L'operatore sizeof molto importante per la portabilit del codice, da un'architettura ad un'altra. Particolarit dell'operatore unario sizeof che viene valutato non durante l'esecuzione del programma, ma al momento della compilazione
Il linguaggio C 128/243

64

Strutture di controllo del flusso


In un linguaggio, le strutture di controllo del flusso specificano l'ordine secondo il quale le operazioni devono essere effettuate. Abbiamo gi visto informalmente alcune strutture, ora completeremo e preciseremo la descrizione.

Istruzioni e Blocchi di Istruzioni


Ogni istruzione in C deve essere terminata da un punto e virgola ; Un'espressione qualsiasi (come x=0, i++, 1&&23<13==7>>5) oppure una chiamata ad una funzione (come ad es. printf(...)) diventa un'istruzione quando seguita da un punto e virgola. Il valore restituito da queste espressioni il risultato della valutazione dell'espressione considerata. Un'istruzione costituita dal solo punto e virgola un'istruzione nulla, che non ha effetto. Un Blocco di Istruzioni una sequenza di istruzioni C racchiusa tra una parentesi graffa aperta ed una parentesi graffa chiusa. Un blocco dal punto di vista dei costrutti linguistici (else while ecc..) va considerato come un'unica istruzione. Il corpo stesso di una funzione un blocco di istruzioni. All'interno di un blocco possibile dichiarare delle variabili locali. L'unica altra differenza tra un'istruzione singola ed un blocco che dopo un blocco non deve essere inserito il punto e virgola. Se viene messo viene considerato un'altra istruzione, nulla.

Il linguaggio C

129/243

Istruzione if-else
L'istruzione if puo' avere due forme di base: if (espressione) istruzione else istruzione2 Viene valutata l'espressione "espressione", e se risulta vera (cio diversa da zero) viene eseguita l'istruzione "istruzione". In caso contrario, se esiste la keyword else viene eseguita l'istruzione "istruzione2". N.B. 1: con "istruzione" deve intendersi o singola istruzione o blocco di istruzioni. if (espressione) istruzione1

N.B. 2: quando viene valutata l'espressione dentro parentesi tonde nel costrutto if, viene effettuato il controllo se il risultato della valutazione uguale o diverso da zero, cio se falso o vero. Quindi scrivere if(espressione) oppure if(espressione!=0) la stessa cosa, ma il primo tipo di scrittura viene codificato dal compilatore in modo pi veloce, perch non richiesto di caricare un registro con la costante inserita nel programma (lo zero) per il confronto, ma si ricorre ad una istruzione assembler di jump condizionato dall'essere il risultato della valutazione (gi contenuto in un registro) diverso o uguale a zero. (Alcuni compilatori ottimizzano situazioni di questo tipo).
Il linguaggio C 130/243

65

Istruzione if-else
N.B. 3: Nel caso di costrutti if-else annidati, una keyword else relativa al pi vicino degli if che lo precedono che manchi dell'else, ovviamente sempre che sia rispettata la sintassi dell'if-else.

questo codice: if (x) if(y) istr1 else istr2

significa: if (x){ if(y) istr1 else istr2 }

che diverso da: if (x){ if(y) istr1 } else istr2

Il linguaggio C

131/243

Istruzione if-else
Il costrutto if-else-if fatto nel modo seguente if (espressione1) istruzione1 else if (espressione2) istruzione2 else if (espressione3) istruzione3 else istruzione e consente di effettuare una scelta multipla. Le espressioni 1, 2, ..ecc. vengono valutate nell'ordine in cui si presentano. La prima espressione che si rivela vera fa eseguire la corrispondente istruzione, e fa uscire il controllo dalla struttura di controllo if-else-if. L'ultimo else (che pu anche non esserci) serve nel caso in cui nessuna delle precedenti condizioni si realizza.
Il linguaggio C 132/243

66

Istruzione if-else
Ecco un esempio, in cui si discrimina secondo il valore di una variabile x, considerando 4 intervalli (-infinito,0) , [0,10) , [10,100) , [100 , +infinito) double x; if (x<0.) istruzione1 else if (x<10.) istruzione2 else if (x<100.) istruzione3 else istruzione La struttura annidata if-else-if, per quanto molto potente, ha lo svantaggio di non essere particolarmente veloce, perch se la prima espressione che risulta vera molto annidata, prima di poterla verificare necessario valutare tutte le espressioni precedenti.
Il linguaggio C 133/243

Istruzione switch
L'istruzione switch un'altra struttura di scelta plurima, presente anche in Java ed analoga al case del Pascal, che controlla se un'espressione assume un valore intero in un insieme di COSTANTI intere, e fa eseguire una serie di istruzioni in corrispondenza del valore intero verificato. Una limitazione dello switch che i valori ammessi come possibili scelte (possibili casi previsti) devono essere delle costanti, e non possono essere delle espressioni. Questo perch il costrutto switch vuole implementare una scelta multipla molto pi veloce del costrutto if-else-if, ma per fare questo deve limitare le possibili scelte, definendole non in modo run-time mediante un'espressione, ma mediante delle costanti, che il compilatore utilizzer per scrivere in codice macchina una sequenza di jump condizionati.
Il linguaggio C 134/243

67

Istruzione switch
Un esempio di switch semplice La struttura dello switch : switch (espressione) { case espressione_costante1: sequenza di istruzioni1; break; case espressione_costante2: sequenza di istruzioni2; break; .... altri case ... default: sequenza di istruzioni; break; } #define NUM 3 int j; switch( j ) { case 1: case 2+NUM: sequenza_di_istruzioniA break; case -34: case -34<<2: sequenza_di_istruzioniB break; default: sequenza_di_istruzioniC }

Il linguaggio C

135/243

Istruzione switch
Ogni possibile caso deve essere etichettato mediante uno (o pi) costrutti case "Espressione_costante": dove Espressione_costante un'espressione formata solo da costanti e operatori, ma non da variabili o funzioni. Possono esserci pi etichette per una stessa sequenza di istruzioni, come nel precedente esempio. Le diverse case rappresentano delle entry point nel costrutto switch. Se l'espressione valutata nello switch assume uno dei valori indicati nelle espressioni costanti, l'esecuzione passa alla sequenza di istruzioni che seguono la keyword case per quell'espressione costante.

Il linguaggio C

136/243

68

Istruzione switch
L'esecuzione procede fino a che non si incontra la keyword break, o si oltrepassa la parentesi graffa aperta che termina la struttura switch, ed allora il controllo esce dalla struttura di controllo di flusso switch. Il caso etichettato come default pu essere presente o no. Se presente assume il significato di "in tutti gli altri casi" cio viene eseguito se l'espressione valutata nello switch non ha assunto nessuno dei valori indicati da una delle espressioni costanti di un qualche case. N.B. la keyword break non strettamente indispensabile. Se non presente viene eseguita sequenzialmente ogni istruzione a partire dal case che stato raggiunto.

Il linguaggio C

137/243

Ciclo while
Il loop di tipo while analogo al while del pascal, e consente di eseguire un loop condizionale. La struttura del while la seguente: while (espressione) istruzione dove "espressione" una espressione che deve essere valutata ogni volta prima di eseguire "istruzione". Se la valutazione di "espressione" da' come risultato vero allora l'istruzione viene eseguita, altrimenti no, e si esce dal ciclo.

Il linguaggio C

138/243

69

Ciclo while
Poich la valutazione della condizione effettuata prima delle istruzioni che costituiscono il loop, il loop stesso pu essere eseguito zero volte oppure un numero finito di volte oppure ancora pu realizzare un loop infinito. Esempio, stampa dei caratteri di una stringa char str[]="stringa"; int j=0; while( str[j] ) printf("%c", str[j++]);

Esempio, loop infinito:

while(1) { .... }
Il linguaggio C 139/243

Ciclo do-while
Il loop di tipo while analogo al repeat-until del pascal, e consente di eseguire un loop condizionale da 1 ad infinite volte. La struttura del do-while la seguente: do { istruzioni } while (espressione); dove "espressione" una espressione che deve essere valutata ogni volta dopo avere eseguito "istruzioni". Se la valutazione di "espressione" da' come risultato vero il loop viene ripetuto, cio si riesegue "istruzioni", altrimenti si esce dal ciclo. Poich la valutazione della condizione effettuata dopo le istruzioni che costituiscono il loop, il loop stesso pu essere eseguito da una volta a infinite volte. es: int num; do { scanf("%d", &num); } while(num>100);

Il linguaggio C

140/243

70

Ciclo for
Il loop di tipo for analogo al for del pascal, ma pi potente. La forma generale del ciclo for fatta cos: for ( inizializzazione ; condizione ; incremento) istruzione le tre componenti di un ciclo for sono delle espressioni, ovvero possono essere degli assegnamenti o delle chiamate a funzione. L'espressione "inizializzazione" viene eseguita una sola volta, nel momento in cui il controllo viene affidato al costrutto for. Serve per impostare le variabili e quanto necessario per cominciare il loop. Questa "espressione" pu anche non essere presente, ed allora dopo la parentesi tonda aperta viene subito il punto e virgola. L'espressione "condizione" viene valutata (eseguita) ogni volta prima di eseguire le istruzioni del loop, ed quella che stabilisce se il ciclo deve continuare ad essere eseguito ancora una volta, oppure si deve uscire dal costrutto for. Se "condizione" vera si esegue ancora "istruzione", se invece falsa (0) si esce dal ciclo for passando alla istruzione successiva del programma.
Il linguaggio C 141/243

Ciclo for
Anche l'espressione "condizione" pu non essere presente, nel qual caso lo spazio tra i due punti e virgola dentro il costrutto for rimane vuoto, ed il compilatore assume vero come risultato della valutazione della (assente) condizione di proseguimento del ciclo for, e quindi continua ad eseguire le istruzioni dentro al loop. L'espressione "incremento" viene eseguita alla fine di ogni ciclo, per modificare ad es. le variabili che stabiliscono l'incremento o decremento delle variabili contatore che stabiliscono il numero di cicli del loop. Questa "espressione" pu anche non essere presente, ed allora dopo il secondo punto e virgola del for viene subito la parentesi tonda chiusa. Quindi un ciclo fatto cos: for( ; ; ) { .. } realizza un loop infinito. N.B. poich la condizione di continuazione del loop viene valutata (eseguita) prima di ogni ciclo, il loop for permette anche di non eseguire nemmeno una volta il ciclo.

Il linguaggio C

142/243

71

Ciclo for
Ciascuna delle tre espressioni del for, cio inizializzazione, condizione e incremento pu contenere pi istruzioni, che dovranno essere separate da virgole. Vediamone un esempio nel caso in cui si vogliano utilizzare due variabili di controllo.
/* con due variabili di controllo */ void main(void) { int x ,y; for ( x=0, y=0; x+y<100; x++ , y+=3 ) printf ("%d ", x+y); }

Il ciclo for pu anche non contenere un corpo di istruzioni vero e proprio, che pu essere limitato ad un'istruzione vuota, il punto e virgola.
/* lunghezza di una stringa null-terminata*/ int len; char stringa[] = "mia stringa"; for( len=0; stringa[len]; len++) ;

Il linguaggio C

143/243

Ciclo for: esempi


/* con incremento */ void main(void) { int x; for ( x=1; x<100; x++ ) printf ("%d ", x); } /* loop infinito */ int x; for ( ;; ) printf ("?");

/* con decremento */ void main(void) { int x; for ( x=100; x>0; x-- ) printf ("%d ", x); }

/* con espressioni costituite da funzioni */ for ( funzA();funzB();funzC())

Il linguaggio C

144/243

72

Istruzione break: uscita da un ciclo


L'istruzione break (che stata vista gi a proposito della struttura switch) serve ad imporre l'uscita da un loop di tipo for, while, do-while, oppure l'uscita dallo switch, e a far riprendere il controllo di flusso immediatamente dopo la fine del loop (o switch) da cui si esce. void main(void) { int x; for ( x=1; ; x++ ) { if ( x==5) break; /* esce dal ciclo for */ } printf ("dopo break ricomincia qui "); }

Il linguaggio C

145/243

Istruzione break: uscita da un ciclo


Nel caso di cicli annidati, con l'istruzione break si esce solo dal ciclo piu interno, entro il quale si trova la chiamata alla istruzione break. es.: void main(void) { int x; for ( y=1; ; y++ ) { for ( x=1; ; x++ ) { if ( x==5) break; /* esce dal ciclo for */ } printf ("dopo break ricomincia qui%d ", x); } } Ovviamente, in caso di switch dentro un ciclo, un break dentro lo switch fa uscire solo dallo switch.
Il linguaggio C 146/243

73

Istruzione continue: ricominciare il ciclo


L'istruzione continue, a differenza della break si applica ai cicli ma non allo switch. Come la break interrompe l'esecuzione di un ciclo for while o dowhile, ma anzich uscire dal ciclo definitivamente fa eseguire la successiva iterazione. Nel caso di ciclo while o do-while una continue posta nel corpo del ciclo fa saltare alla espressione di controllo, che viene valutata, e se risulta vera viene eseguito ancora il loop, altrimenti si esce dal loop. Nel caso del ciclo for invece, un'istruzione continue fa eseguire l'espressione che effettua l'incremento, poi viene effettuata la valutazione della condizione di continuazione del ciclo for, e se risulta vera viene eseguito il ciclo, altrimenti si esce dal for.

N.B. L'istruzione continue pochissimo usata.

Il linguaggio C

147/243

Istruzione continue: ricominciare il ciclo


Anche per la continue come per la break, in caso di cicli annidati, si interrompe solo il ciclo pi interno in cui avvenuta la chiamata alla continue. void main(void) { int x; for ( y=1; ; y++ ) for ( x=1; ; x++ ) { if ( x==5) continue; /* si effettua l'incremento di x, x diventa 6, si verifica la condizione, risulta vera perch assente si riprende dal for interno (quello della x) con x=6 */ } }
Il linguaggio C 148/243

74

Istruzione goto: salto


L'istruzione goto, consente di passare il controllo dal punto in cui si effettua la goto ad una locazione del programma individuata da una label (un'etichetta) seguita dai due punti, collocata in una qualche posizione del codice. Pu essere utile per uscire da pi livelli di annidamento di cicli, cosa che la break e la continue non riescono a fare. Va usato con attenzione.
void main(void) { int x; for ( y=1; ; y++ ) for ( x=1; ; x++ ) { for ( z=10; z<1000; z++ ) { ... if ( x==5) goto uscita; ... } } uscita:; ... }
Il linguaggio C 149/243

Istruzione goto: salto


Altro esempio di stranezza consentita dal goto:

void main(void) { int x=13; goto dentro; for ( x=1; ; y++ ) { dentro: printf("atterraggio: x=%d\n", x ); } }

Il linguaggio C

150/243

75

Istruzione goto
Il goto non permette di passare da una funzione ad un'altra. Ecco un' esempio di cosa non permesso. int funzione1(void){ arrivo: ; ... } void funzione2(int i){ goto arrivo; /* errore in compilazione */ ... }
Il linguaggio C 151/243

Array a una dimensione


Un array un insieme di variabili dello stesso tipo cui possibile accedere mediante un nome comune e referenziare uno specifico elemento tramite un indice. Il C alloca gli elementi di un array in posizioni adiacenti, associando l'indirizzo pi basso al primo elemento, e il pi alto all'ultimo. Gli array ad una dimensione vengono dichiarati come segue: Tipo NomeVariabile[ dimensione_costante ] ;

dove Tipo il tipo degli elementi del vettore, dimensione una costante che indica quanti elementi deve contenere l'array. La dichiarazione serve al compilatore per riservare in memoria spazio sufficente al vettore. Lo spazio occupato dal vettore sar: numero_byte = sizeof(Tipo) * dimensione_costante ; esempi: int vettore[10]; char stringa[100]; un vettore di 10 interi un vettore di 100 caratteri, cio potenzialmente una stringa per 99 caratteri pi il carattere terminatore '\0
Il linguaggio C 152/243

76

Array a una dimensione


Il C indicizza gli elementi a partire da 0, quindi possiamo accedere agli elementi con gli indici da 0 a dimensione_costante-1. L'accesso si effettua mediante il nome dell'array seguito dalle parentesi quadre in cui viene racchiuso l'indice dell'elemento cercato. vettore[3] stringa[0] accede al quarto elemento del vettore "vettore" accede al primo carattere del vettore "stringa"

Non si puo' assegnare qualcosa al vettore intero, ma solo alle diverse posizioni del vettore (non ha senso scrivere vettore=19; o vettore=stringa). Inizializzazione di un array: int vet [3] = { 1,2,3 } ;

Un esempio di programma con un semplice vettore. void main(void) { char str[100]; int i; for ( i=0; i<100; i++) str[i] = (char) i; }
Il linguaggio C 153/243

Il problema dello sconfinamento


Il linguaggio C non effettua controlli per verificare che non si acceda a posizioni fuori dal vettore. Cio possibile (ma un errore gravissimo anche se comune) accedere ad una locazione di memoria fuori dal vettore, con vari effetti possibili: Se si accede in lettura al vettore fuori da esso pu accadere, a seconda del sistema operativo: nessun problema apparante, solo errore logico segmentation fault. se invece si accede in scrittura al vettore ci saranno danni maggiori, perch oltre ai due problemi gi citati si sovrascriver una locazione di memoria che potrebbe contenere dati essenziali al programma o peggio ancora al sistema operativo. Oppure si modificher il valore delle variabili dichiarate immediatamente sopra o sotto al vettore.

Il linguaggio C

154/243

77

Esempio di errore di sconfinamento


void main(void) { int prima=0; int vet[10]; int dopo=0; printf("INIZIO: prima=%d dopo=%d\n", prima, dopo ); for ( i=-1; i<=10; i++) { vet[i] = 99; printf("i=%d prima=%d dopo=%d\n", i, prima, dopo ); } }

Il linguaggio C

155/243

Istruzione typedef
Il C permette di definire esplicitamente nomi nuovi per i tipi di dati, tramite la parola chiave typedef. L'uso di typedef consente di rendere il codice pi leggibile. Il formato dell'istruzione typedef il seguente: typedef tipo nuovo_nome_tipo; in questo modo assegnamo al tipo tipo il nuovo nome nuovo_nome_tipo. Da questo momento in avanti potremo riferirci al tipo di dato tipo sia con il nome tipo sia con il nome nuovo_nome_tipo. Es typedef int intero; /* ora posso usare intero come tipo invece di int */

intero i; /* definisce una variabile i di tipo int. */


Il linguaggio C 156/243

78

Tipi di dati complessi


Il C mette a disposizione i seguenti dati di tipo complesso: array monodimensionali, in parte gi visti, strutture, dette struct, unioni, dette union, puntatori, array multidimensionali,

Ci sarebbe da discutere sul fatto che i puntatori siano un dato di tipo complesso, in quanto in realt gli indirizzi rappresentano uno dei dati di base per la programmazione a basso livello (assembler) per tutte le moderne architetture dei calcolatori. Col termine "dato complesso" intendiamo perci indicare dati il cui uso richiede attenzione.
Il linguaggio C 157/243

Strutture (struct)
Vediamo come definita un tipo di dato struct chiamato luogo, che vuole rappresentare un punto geografico individuato da tre coordinate intere x, y, z ed un nome rappresentato da una stringa di 30 caratteri: Vediamo come si definisce il tipo di dato "luogo": struct luogo { char nome[30]; int x; int y; int z; }; Una volta definito il tipo di dato, possiamo dichiarare una variabile di quel tipo, premettendo per la keyword struct: struct luogo monte_bianco;
Il linguaggio C 158/243

79

Strutture (struct)
Una sintassi diversa permette di dichiarare in una sola istruzione il tipo di dato struct luogo ed alcune variabili di quel tipo.
struct luogo { char nome[30]; int x; int y; int z; } monte_bianco , monte_rosa;

In questo caso, qualora solo queste variabili debbano essere dichiarate, ovvero qualora non ci sia altrove necessit di conoscere il tipo di dato "luogo", l'identificatore "luogo " pu essere omesso, e si avr:
struct { char nome[30]; int x; int y; int z; } monte_bianco , monte_rosa;

Il linguaggio C

159/243

Strutture (struct)
Anche con i dati di tipo struct possiamo ricorrere alla typedef per definire nuovi nomi per i tipi di dato. Ad es: typedef struct { char nome[30]; int x; int y; int z; } LUOGO; In questo modo abbiamo definito un nome che pu essere utilizzato per dichiarare delle variabili di tipo LUOGO in tutto e per tutto uguale al precedente tipo di dato luogo, che per in questa dichiarazione non compare. Ora potremo dichiarare variabili omettendo la keyword struct. LUOGO milano.

Il linguaggio C

160/243

80

Strutture (struct)
E' possibile comunque mantenere i due identificatori: typedef struct luogo { char nome[30]; int x; int y; int z; } LUOGO; e definire due variabili di tipo uguale pure se con nomi diversi, ed il compilatore li tratta come dati di un solo tipo, permettendo ad es. gli assegnamenti. struct luogo Cesena; LUOGO Cesenatico.

Cesena = Cesenatico; Per accedere ai membri (o campi) di una struttura struct, il C fornisce l'operatore punto "." . Ad esempio: Cesena.nome accede alla stringa nome del dato Cesena di tipo luogo.

Il linguaggio C

161/243

Inizializzazione delle struct


Una struttura struct puo' essere pre-inizializzata al momento della dichiarazione, mettendo le costanti che inizializzano uno per uno tutti i campi della struct tra parentesi graffe.
struct luogo rimini = { "RIMINI" , 100, 139 , 10};

E possibile anche annidare nell'esempio qui di seguito le struct ed inizializzarle assieme, come:
#include <stdio.h> void main() { struct luogo { char nome[30]; int x, y, z; }; struct coppia_di_luoghi { struct luogo luogo1; struct luogo luogo2; }; struct coppia_di_luoghi Rimini_Milano = { { "RIMINI" , 100, 139 , 10} , { "MILANO" , 80, 170 , 50 } }; printf("luogo1=%s\n" , Rimini_Milano.luogo1.nome); /* stampa "RIMINI */ }
Il linguaggio C 162/243

81

Unioni: union
Un'union e' una variabile che pu tenere (in momenti diversi) oggetti di diversa dimensione e tipo, che sono quindi posizionati sulla stessa area di memoria, cio sovrapposti. Spesso i diversi oggetti sono delle struct, ed hanno tutte un campo, eventualmente con un nome diverso da struct a struct, ma collocato nella stessa posizione in memoria, in cui viene mantenuta una costante che identifica quale struttura contenuta nell'union in quel momento. In questo modo, ad es, si pu passare dati diversi ad una stessa funzione, e la funzione leggendo questo campo di identificazione della struttura, capisce che genere di struttura contenuta in quel momento nella union, e pu accedere correttamente ai dati della union.
Il linguaggio C 163/243

Unioni: esempio
struct A {int idA; char field1[10]; char field2[20]; char field3[10] }; char campo2[10]; }; char val2[25]; char val3[20] }; struct B { int idB; char campo1[8]; struct C { int idC; char val1[12];

union ABC { struct A a; struct B b; struct C c; } abc = {IS_A, field1, field2, field3}

Il compilatore alloca area sufficente a contenere la pi grande delle strutture contenute nella union. I campi sono acceduti col nome della struttura contenuta, seguita dal punto come nelle struct e dal nome del campo. es: if( abc.a.idA == IS_B ) printf("%s", abc.b.campo2);

Il linguaggio C

164/243

82

Puntatori
I puntatori sono il segreto della potenza e la flessibilit del C, perch: - sono l'unico modo per effettuare alcune operazioni; - servono a produrre codici sorgenti compatti ed efficienti, anche se a volte difficili da leggere. In compenso, la maggior parte degli errori che i programmatori commettono in linguaggio C sono legati ai puntatori. Il C utilizza molto i puntatori in maniera esplicita con: - vettori; - strutture; - funzioni. In C ogni variabile caratterizzata da due valori: - un indirizzo della locazione di memoria in cui sta la variabile, - ed il valore contenuto in quella locazione di memoria, che il valore della variabile.
Il linguaggio C 165/243

Puntatori
Un puntatore e' un tipo di dato, una variabile che contiene l'indirizzo in memoria di un'altra variabile, cio un numero che indica in quale cella di memoria comincia la variabile puntata. Si possono avere puntatori a qualsiasi tipo di variabile. La dichiarazione di un puntatore include il tipo dell'oggetto a cui il puntatore punta; questo serve al compilatore che deve accedere alla locazione di memoria puntata dal puntatore per sapere cosa trover, in particolare per sapere le dimensioni della variabile puntata dal puntatore. Per dichiarare un puntatore p ad una variabile di tipo tipo, l'istruzione e': tipo *p ; L' operatore & fornisce l'indirizzo di una variabile, perci l'istruzione p = & c scrive nella variabile p l'indirizzo della variabile c, ovvero: tipo c, *p; p = &c ; dichiaro una var c di tipo tipo ed un puntatore p a tipo assegno a p l'indirizzo di c

Il linguaggio C

166/243

83

Puntatori
L'operatore * viene detto operatore di indirezione o deriferimento. Quando una variabile di tipo puntatore preceduta dall'operatore *, indica che stiamo accedendo all'oggetto puntato dal puntatore. Quindi con *p indichiamo la variabile puntata dal puntatore.

int c, *p; p = &c; c= 5;

dichiaro una variabile c di tipo int ed un puntatore p a int. assegno a p lindirizzo di c assegno a c il valore 5

printf("%d\n", *p);

stampo il valore puntato dal puntatore p viene stampato 5

Il linguaggio C

167/243

Puntatori
Consideriamo gli effetti delle seguenti istruzioni:
int *pointer; int x=1,y=2; (1) pointer = &x; (2) y = *pointer; (3) x = pointer; /* assegna a pointer l'indirizzo di x */ /* assegna a y il contenuto dell'int puntato da pointer, x */ /* assegna ad x l'indirizzo contenuto in pointer, serve cast perch pointer a int diverso da int */ (4) *pointer=3; /* assegna alla variabile puntata da pointer il valore 3 */ /* dichiara pointer come un puntatore a int */

Vediamo cosa succede in memoria. Supponiamo che la variabile x si trovi nella locazione di memoria 100, y nella 200 e pointer nella 1000. L'istruzione (1) fa s che pointer punti alla locazione di memoria 100 (quella di x), cio che pointer contenga il valore 100. La (2) fa s che y assuma valore 1 (il valore di x). La (3) fa s che x assuma valore 100 (cioe' il valore di pointer). La (4) fa s che il valore del contenuto di pointer sia 3 (quindi x=3).
Il linguaggio C 168/243

84

Puntatori
Quindi con i puntatori possiamo considerare tre possibili valori: pointer: contenuto o valore della variabile pointer (indirizzo della locazione di memoria a cui punta) &pointer: indirizzo fisico della locazione di memoria del puntatore *pointer: contenuto della locazione di memoria a cui punta NB. Quando un puntatore viene dichiarato non punta a nulla, o meglio poich il contenuto di una cella di memoria casuale fino a che non viene inizializzata ad un valore noto, il puntatore punta ad una locazione di memoria casuale, che potrebbe non essere accessibile dal processo. Cosi': int *ip; *ip=100; scrive il valore 100 in una locazione qualsiasi: grave errore.
Il linguaggio C 169/243

Puntatori
L'utilizzo corretto e' il seguente; prima di scrivere un valore nella locazione di memoria puntata dal puntatore ci assicuriamo che tale locazione di memoria appartenga al nostro programma. Ci e possibile in due modi: 1) il primo modo consiste nell'assegnare al puntatore l'indirizzo di una variabile del nostro programma, quindi scriveremo il valore nella variabile puntata. int *ip; int x; ip=&x; *ip=100; scrivo 100 in x

2) il secondo modo consiste nel farci riservare dal sistema operativo una porzione di memoria, salvare l'indirizzo di questa porzione di memoria nel nostro puntatore (ovvero far puntare il puntatore a quell'area di memoria) in modo che i successivi riferimenti alla locazione di memoria puntata dal puntatore lavorino su questa area di memoria che ci stata riservata. Esiste una funzione di libreria standard malloc(), equivalente allistruzione new di Java e C++, che permette l'allocazione dinamica della memoria; e' definita come: void *malloc ( int number_of_bytes). Ad es.: int *p; p= (int *) malloc(sizeof(int)); assegna a p spazio per un int.
Il linguaggio C 170/243

85

Aritmetica degli indirizzi


Si possono fare operazioni aritmetiche intere con i puntatori, ottenendo come risultato di far avanzare o riportare indietro il puntatore nella memoria, cio di farlo puntare ad una locazione di memoria diversa. Ovvero con i puntatori possibile utilizzare due operatori aritmetici + e - , ed ovviamente anche ++ e --. Il risultato numerico di un'operazione aritmetica su un puntatore diverso a seconda del tipo di puntatore, o meglio a seconda delle dimensioni del tipo di dato a cui il puntatore punta. Questo perch il compilatore interpreta diversamente la stessa istruzione p++ a seconda del tipo di dato, in modo da ottenere il comportamento seguente:
Il linguaggio C 171/243

Aritmetica degli indirizzi


Sommare un'unit ad un puntatore significa spostare in avanti in memoria il puntatore di un numero di byte corrispondenti alle dimensioni del dato puntato dal puntatore. Ovvero se p un puntatore di tipo puntatore a char, char *p; poich il char ha dimensione 1, l'istruzione p++ aumenta effettivamente di un'unita il valore del puntatore p, che punter al successivo byte. Invece se p un puntatore di tipo puntatore a short int, short int *p; poich lo short int ha dimensione 2 byte, l'istruzione p++ aumenter effettivamente di 2 il valore del puntatore p, che punter allo short int successivo a quello attuale.

Il linguaggio C

172/243

86

Aritmetica degli indirizzi


In definitiva, ogni volta che un puntatore viene incrementato passa a puntare alla variabile successiva che appartiene al suo tipo base, mentre un decremento lo fa puntare alla variabile precedente. Quindi incrementi e decrementi di puntatori a char fanno avanzare o indietreggiare i puntatori a passi di un byte, mentre incrementi e decrementi di puntatori a dati di dimensione K fanno avanzare o indietreggiare i puntatori a passi di K bytes. Il caso dei puntatori a void void *p viene trattato come il caso dei puntatori a char, cio incrementato o decrementato a passi di un byte.
Il linguaggio C 173/243

Aritmetica degli indirizzi


Sono possibili non solo operazioni di incremento e decremento (++ e --) ma anche somma e sottrazione di dati interi (char, int, long int) che comunque vengono effettuati sempre secondo le modalit di incremento decremento a passi di dimensioni pari alla dimensione del dato puntato dal puntatore. es: long int *p; p = p + 9;

oppure p +=9;

queste istruzioni fanno avanzare il puntatore p di 9*sizeof(long int)=9*4=36 byte.

Il linguaggio C

174/243

87

Aritmetica degli indirizzi


long int *p; char i; i = 9; p = p +i; oppure p +=i;

Anche queste istruzioni fanno avanzare il puntatore p di i*sizeof(long int)=9*4=36 byte. Non sono consentite altre operazioni oltre all'addizione e sottrazione di interi. Non possibile sommare sottrarre moltiplicare o dividere tra loro dei puntatori. Ad es, int *ptr, *ptr1; ptr = ptr + ptr1; da' errore in compilazione di tipo "invalid operands to binary".
Il linguaggio C 175/243

Confronto tra puntatori


Si possono effettuare confronti tra puntatori, per verificare ad es. quale tra due puntatori punta ad una locazione di memoria precedente, oppure se due puntatori puntano ad una stessa locazione di memoria. Ad es. sono valide istruzioni del tipo: int x, y; int *p, *q; p = &x; q = &y; if(p<q) printf("minore");

Il linguaggio C

176/243

88

Allocazione dinamica della memoria


I programmi C suddividono la memoria in 4 aree distinte: codice, dati globali, stack e heap. Lo heap un'area di memoria libera che viene gestita da funzioni di allocazione dinamica del C quali la malloc() e la free(). malloc() alloca la memoria (chiede al s.o. di riservare una area di memoria) e restituisce un puntatore a void che punta all'inizio di quest'area di memoria allocata. Se non disponibile sufficiente memoria restituisce NULL, (l'equivalente a null di Java) ad indicare che l'allocazione non stata effettuata. NULL equivale al valore zero, quindi possibile testare il risultato della malloc() in questo modo:

Il linguaggio C

177/243

Allocazione dinamica della memoria


int *p; p = (int*) malloc( 100*sizeof(int) ); /* alloca 100 interi */ if( !p ) { printf("errore: allocazione impossibile"); exit(1); } else { .... uso la memoria free(p); /* rilascia la memoria allocata v*/ }

Il linguaggio C

178/243

89

Puntatori e array monodimensionali


Riassumendo, i puntatori sono delle variabili che contengono un indirizzo in memoria, e si dice che il puntatore "punta a quell'indirizzo". Il puntatore pu essere di tipo "puntatore ad un tipo" oppure di tipo generico "puntatore a void". Il puntatore consente di accedere alla memoria a cui punta mediante l'operatore []. Se p un puntatore ad un certo tipo (tipo *p;) e contiene un certo valore addr, ovvero punta ad un certo indirizzo addr, l'espressione p[k] accede all'area di memoria che parte dal byte addr+k*sizeof(tipo) ed ha dimensione sizeof(tipo), trattandola come se fosse una variabile di tipo tipo.

Il linguaggio C

179/243

Puntatori e array monodimensionali


Nel caso di un puntatore a void, viene considerata 1 la dimensione del dato puntato, cio il puntatore punta ad un byte. Anche per i vettori (gli array monodimensionali di dati di tipo tipo) l'accesso ai dati avviene secondo queste modalit vet[k], perch in C, il nome di un array TRATTATO dal compilatore come un puntatore COSTANTE alla prima locazione di memoria dell'array stesso . (costante significa che non posso assegnare qualcosa al nome del vettore ma solo alle sue posizioni)
Il linguaggio C 180/243

90

Puntatori e array monodimensionali


A differenza dei vettori per, l'area di memoria a cui il puntatore punta non viene allocata staticamente come per i vettori a partire dalla loro definizione (che contiene le dimensioni del vettore stesso), ma pu essere allocata dinamicamente mediante alcune funzioni che richiedono al sistema operativo di riservare memoria e restituire un indirizzo a quell'area di memoria allocata, oppure pu non essere allocata affatto. Comunque la differenza principale tra puntatori e vettori e' che i puntatori sono variabili che contengono un indirizzo, e questo contenuto (indirizzo) pu essere cambiato per puntare ad un'area di memoria diversa. Invece per i vettori, anche se il loro nome trattato come un puntatore (ma costante) all'inizio del vettore stesso, non esiste una variabile (una cella di memoria) in cui mantenuto l'indirizzo della prima locazione del vettore, e quindi questo indirizzo non pu essere modificato, costante. Ad es: il compilatore da' errore qui: int vet[100]; vet++; vet =10;
Il linguaggio C 181/243

Puntatori e array monodimensionali


A partire da queste considerazioni sottolineamo che definite due variabili, un vettore int vet[100] ed un puntatore int *p mentre possibile fare riferimento all'indirizzo di un puntatore &p ottenendo un indirizzo che punta alla locazione di p, cio che indica a che indirizzo sta la variabile p, nel caso dei vettori l'indirizzo del vettore &vet l'indirizzo che punta all'inizio del vettore stesso, ovvero l'indirizzo del primo elemento del vettore stesso. Ovvero le seguenti espressioni indicano tutte la stessa cosa, l'inizio del vettore: vet, &(vet[0]), &vet char str[100]; char *p; p = str;
Il linguaggio C 182/243

91

Puntatori e array monodimensionali


con questo assegnamento viene assegnato al puntatore p l'indirizzo della prima locazione di memoria del vettore. Da questo momento in avanti potremo accedere ai dati del vettore sia tramite str, sia tramite p esattamente negli stessi modi, o tramite l'indicizzazione tra parentesi quadre, o tramite l'aritmetica dei puntatori. str[10], *(str+10), p[10], *(p+10) sono tutti modi uguali per accedere alla 11-esima posizione del vettore str puntato anche da p. es: str[10] = 'A';
printf( "str[10]=%c\n", str[10] ); printf( "str[10]=%c\n", p[10] ); printf( " str[10]=%c\n", *(p+10) );

Il linguaggio C

183/243

Un esempio di uso di puntatori: copia di una stringa in un'altra.


Vediamo un esempio di applicazione dei puntatori, la copia di una stringa, confrontandola con un'implementazione che non usa puntatori.
char sorgente[]="STRINGA"; char destinazione[100]; int i=0; for (i=0; ; i++){ destinazione[i]=sorgente[i]; if( ! destinazione[i] ) break; } char sorgente[]="STRINGA"; char destinazione[100]; char *src, *dest; src=sorgente; dest=destinazione; while( *(dest++) = *(src++) );

Il linguaggio C

184/243

92

Puntatori a strutture
E' possibile utilizzare puntatori che puntano a dati di tipo strutturato come le struct. La definizione di questo tipo di puntatore ricalca esattamente la definizione generale, cio prevede di definire il tipo di dato, e poi di definire il puntatore a quel tipo di dato.
Es. struct PUNTO {char nome[50]; int x; int y; int z; }; /* def. tipo */ struct PUNTO *ptrpunto; strutturata */ struct PUNTO punto; strutturato */ /* dichiaraz. puntatore a var. /* dichiaraz. variabile di tipo

ptrpunto = &punto; /* assegno l'indirizzo della var. punto */


Il linguaggio C 185/243

Puntatori a strutture
E' necessario per semplicita' definire un operatore che permetta di accedere ai campi della struttura direttamente dal puntatore. In C esiste l'operatore -> che permette l'accesso ad un campo della struttura puntata. Nell'esempio, ptrpunto->x = 102; accede in scrittura al campo x della struttura di tipo PUNTO puntata da ptrpunto. Quindi l'operatore -> equivale ad usare in maniera combinata l'operatore * facendolo precedere al nome del puntatore per accedere al dato strutturato, seguito dall'operatore . per accedere al campo di interesse. Nell'esempio, l'istruzione ptrpunto->x = 102; equivale all'istruzione: (*ptrpunto).x = 102;

Il linguaggio C

186/243

93

Puntatori in strutture
Ora che abbiamo introdotto i puntatori, vediamo perch stato reso possibile utilizzare per le struct quella sintassi particolare, con il nome della struct prima della sua descrizione.

Vogliamo definire una struttura dati che serva come nodo di una lista semplice, una struttura che contenga cioe' un intero (il dato della lista) ed un puntatore all'elemento successivo della lista. Il problema come definire un puntatore ad un tipo di dato mentre ancora il tipo di dato non stato definito. Vediamo due esempi alternativi di come procedere, in un caso utilizzando la typedef, nell'altro no.
struct nodolista { int id; struct nodolista *next; }; struct nodolista nodo; typedef struct nodolista { int id; struct nodolista *next; } NODOLISTA; NODOLISTA nodo;
Il linguaggio C 187/243

Array multidimensionali
Un array multidimensionale un array, i cui elementi sono a loro volta degli array, i cui elementi ecc.. fino ad esaurire le dimensioni volute. Facciamo riferimento per semplicit al caso degli array bidimensionali, le matrici. Le matrici in C sono locazioni di memoria contigue che contengono i dati memorizzati per righe (come in pascal, mentre il fortran memorizza per colonne). Quindi la matrice, anche se viene pensata come un oggetto a due dimensioni del tipo ad es. (3 righe per 4 colonne), cio come un vettore di righe, ciascuna delle quali un vettore,

in realt un oggetto disposto linearmente in memoria, riga dopo riga, come un vettore

Il linguaggio C

188/243

94

Array multidimensionali
Indicizzando righe e colonne a partire da 0, se NR il numero di righe, ed NC il numero di colonne, allora l'elemento in posizione (n,c) della matrice sta fisicamente nella posizione r*NC+c del vettore. La matrice (es. di int) viene quindi dichiarata come un vettore di NR elementi di tipo vettore di NC interi, ovvero come segue: #define NR 3 #define NC 4 int matrice[NR][NC]; Nella dichiarazione la dimensione delle matrici deve essere una costante, perch il compilatore per effettuare l'accesso all'elemento (r,c) della matrice ovvero all'elemento r*NC+c (a partire da dove inizia la matrice) deve conoscere la dimensione delle righe cio il numero NC di colonne. L'accesso al dato in posizione r,c della matrice viene effettuato mediante l'operatore [ ] gi usato per i vettori. Ad es. matrice[1][3]=7; scrive il valore 7 nell'elemento che sta nella seconda riga in quarta colonna.
Il linguaggio C 189/243

Array multidimensionali
Analogamente ai vettori, il nome della matrice rappresenta il puntatore al primo elemento della matrice, cio alla prima riga.

All'atto della dichiarazione possibile inizializzare le matrici come di seguito indicato: int matrice [2][3] = { { 1,2,3 } , { 4,5,6 } }; L'inizializzazione viene fatta scrivendo tra parentesi graffe i valore di ciascun elemento, separati da virgole. Come si vede, essendo la matrice un vettore di righe, ciascuna riga viene inizializzata in modo analogo, scrivendo tra parentesi graffe i valori di ciascun elemento separati da virgole.

Il linguaggio C

190/243

95

Vettori di puntatori
Vogliamo costruire dinamicamente un dato M che sia un array di n puntatori ad int, per poi passare ad allocare, per ciascuno dei puntatori ad int dell'array, spazio sufficiente ad m int, ottenendo in definitiva un array di vettori di interi.

Il linguaggio C

191/243

Vettori di puntatori
Se gli elementi dell'array dinamico M sono puntatori ad interi, il nostro puntatore M sar un puntatore di puntatore ad int, cio sar un int* *M; L'elemento intero che sta nella posizione c-esima dell'r-esimo vettore di interi verr acceduta mediante un'espressione: M[r][c] i = M[r][c] In effetti questa struttura dati spesso usata come matrice in C, quando non sono note a priori le dimensioni della matrice da realizzare, e si preferisce non sovradimensionarla con un'allocazione statica.

Il linguaggio C

192/243

96

Vettori di puntatori
/* allocazione della struttura dati */ int n,m,r,c; int* *M; /* valori ad n ed m assegnati run-time */ n = ...; m = ...; if ( ! (M = malloc( n * sizeof(int*) )) ) exit(1); for ( r=0; r<n; r++) if ( ! (M[r] = malloc( m * sizeof(int) )) ) exit(1); else for ( c=0; c<m; c++) M[r][c] = valore

Il vettore di puntatori qui visto viene utilizzato in un caso particolare, in cui tutti i vettori allocati hanno stessa dimensione. In generale invece possibile per ogni puntatore allocare un vettore di dimensioni diverse.

Il linguaggio C

193/243

Inizializzazione statica di un vettore di puntatori


E' inoltre possibile costruire un dato M che sia un array di n puntatori, non solo dinamicamente, ma anche staticamente al momento della dichiarazione. La seguente istruzione crea ed inizializza un array di puntatori a char, cio un array di stringhe di lunghezza diversa (nomi un vettore di puntatori a char). char *nomi [] = { "paola", "marco", "giovanna" };

Il linguaggio C

194/243

97

Funzioni
In C non esiste la distinzione che esiste in Pascal tra funzioni e procedure, non esistono le classi e i metodi di Java, in C sono tutte funzioni, che possono restituire un qualche risultato oppure no, nel qual caso restituiscono void. Le chiamate ad ogni funzione in C si effettuano chiamando il nome della funzione seguita dalle parentesi tonde, aperta e chiusa, all'interno delle quali vengono passati i parametri necessari. Anche se la funzione non richiede nessun argomento, nella chiamata il suo nome deve essere seguito dalle parentesi tonde aperta e chiusa. Il main stesso una funzione. La forma generale della definizione di una funzione e':

tipo_restituito nome_funzione (paramdef1, paramdef2, ...) { dichiarazione variabili locali ii istruzioni }


Dove paramdef1, paramdef2, ecc. sono la definizione degli argomenti da passare alla funzione all'atto della chiamata (ad es. int i). tipo_restituito il tipo del dato che viene restituito come risultato dalla funzione.
Il linguaggio C 195/243

Funzioni
Se manca la definizione del tipo di dato restituito dalla funzione ("tipo_restituito"), il C assume che il risultato della funzione e' di tipo int. La funzione restituisce il risultato mediante un'istruzione detta return(), che, analogamente a quanto avviene in Java ha la forma: return espressione; o return (espressione);. La return termina la funzione e restituisce il controllo al chiamante. Se la funzione non deve restituire nessun valore, si dichiara il tipo_restituito come void, e non si esegue la return o la si esegue senza passare alcun argomento ( es.: return;).

Il linguaggio C

196/243

98

Funzioni: esempio
Es. funzione che somma due valori di tipo int e restituisce un int: int somma(int a, int b) { int sum; sum = a+b; return(sum); }

La chiamata della funzione viene fatta cos: void main(void) { int A=23; int B=-31; int risultato; risultato = somma(A,B); printf("somma= %d\n", risultato); }

Il linguaggio C

197/243

Funzioni: esempio
Es. funzione che non restituisce alcun valore: void somma(int a, int b) { int sum; sum = a+b; printf("somma= %d\n", sum); /* non serve la return */ } La chiamata della funzione viene fatta cos: void main(void) { int A = 23; int B = -31; somma(A,B); }

Il linguaggio C

198/243

99

Passaggio degli argomenti: dati semplici


I dati di tipo semplice (char, int, long float, double e puntatori ), che vengono passati come argomenti ad una funzione, vengono passati per valore e non per indirizzo. Ci significa che nel momento della esecuzione della funzione, il valore degli argomenti viene copiato sullo stack, e la funzione usa (ed eventualmente modifica) questa copia degli argomenti. In tal modo il dato originale rimane invariato dopo che la funzione ha restituito il controllo al chiamante. Se invece si vuole che un argomento di tipo semplice passato alla funzione conservi le modifiche apportate dalla funzione durante l'esecuzione della funzione, l'argomento deve essere passato per indirizzo, cio alla funzione deve essere passato l'indirizzo della variabile, in modo che la funzione (tramite l'indirizzo) modifica la variabile originale.

Il linguaggio C

199/243

Passaggio degli argomenti: dati semplici


Es. funzione che somma due valori di tipo int e restituisce un int:

void azzera_variabile( int *ptr_a ) { *ptr_a = 0; }

La chiamata della funzione viene fatta cos: void main(void) { int A=23; azzera_variabile ( &A ); /* viene passato l'indirizzo */ }

Il linguaggio C

200/243

100

Passaggio degli argomenti: array


I dati di tipo array ( vettori, matrici, array di dimensioni maggiori), che vengono passati come argomenti ad una funzione, in C vengono passati per indirizzo, nel senso che "passando l'array per nome si passa l'indirizzo in cui comincia l'array". Ci significa che quando, all'atto della chiamata, passiamo ad una funzione il nome di un vettore, passiamo l'indirizzo del primo elemento del vettore, e non tutti i dati del vettore ( i 100 interi dell' esempio)

void main(void) { int vet[100]; modifica_vet (vet , 100 ); }


di conseguenza la definizione della funzione dovr contenere come argomento formale il puntatore al tipo di dati del vettore (un puntatore ad int nell'esempio)

Il linguaggio C

201/243

Passaggio degli argomenti: array


void modifica_vet( int *v , int size ) { v[0] = 137; /* modifica conservata fuori dalla funzione */ v = NULL; /* annullo l'indirizzo in v, ma questa modifica NON viene mantenuta fuori dalla funzione */ } In tal modo se il vettore passato come argomento viene modificato dalla funzione, cio se la funzione modifica il contenuto degli elementi del vettore, queste modifiche sono permanenti, cio restano nel vettore anche dopo che la funzione terminata.

Il linguaggio C

202/243

101

Vettori
Quando l'argomento passato un vettore, nella definizione della funzione l'argomento pu essere indicato o come un puntatore oppure in un modo alternativo, come segue: void modifica_vet( int v[] , int size ) { v[0] = 137; /* modifica conservata fuori dalla funzione */ v = NULL; /* annullo l'indirizzo in v, ma questa modifica NON viene mantenuta fuori dalla funzione */ } Questa notazione int v[] vuole indicare che v un vettore di interi di dimensione sconosciuta (non si sa di quanti interi composto il vettore). Comunque le due notazioni int *v e int v[] sono assolutamente equivalenti, entrambi i parametri vengono trattati come puntatori.
Il linguaggio C 203/243

Matrici
Analogamente ai vettori, quando, all'atto della chiamata, passiamo ad una funzione il nome di una matrice, passiamo l'indirizzo del primo elemento della matrice, quindi le modifiche sui dati della matrice vengono conservate dopo l'uscita dalla funzione.
void main(void) { int mat[10][20]; modifica_mat ( mat ); } void modifica_mat( int m[][20] ) { m[1][0] = 137;/* modifica conservata fuori dalla funz. */ m = NULL; } /* questa modifica NON viene mantenuta */

Analogamente ai vettori, la definizione della funzione dovr contenere un puntatore ad array di 20 (= num. colonne) interi. Notare: si passa la dimensione delle righe, per calcolare l'indice r*NC+c.
Il linguaggio C 204/243

102

Passaggio degli argomenti: strutture


I dati di tipo struct possono venire passati alle funzioni sia per valore sia per puntatore, esplicitamente.
struct point { int x; int y; } /* copia i valori di p1 nella struttura puntata da pp2 */ void copia_e_modifica_struct( struct point p1, struct point *pp2 ) { pp2-> x = p1.x + 10; /* modifiche permanenti */ pp2-> y = p1.y + 10; } void main(void) { struct point p1 = { 14 , 27 }; struct point p2; copia_e_modifica_struct( p1, & p2 ); }

Se la struttura da passare molto grande, bene passarla per puntatore, per non sovraccaricare lo stack, soprattutto quando le chiamate sono molto annidate.

Il linguaggio C

205/243

Passaggio di parametri: confronto C-Java


Java manipola gli oggetti per riferimento, e tutte le variabili che si riferiscono a oggetti sono riferimenti (handle). Java, per, effettua il passaggio di parametri esclusivamente per valore. public void badSwap(int var1, int var2) { int temp = var1; var1 = var2; var2 = temp; } Quando la funzione termina, il valore dei parametri var1 e var2 non sono cambiati, dal punto di vista del chiamante. Ci resta valido anche cambiando il tipo da int a Object.

Il linguaggio C

206/243

103

Scambio di parametri in C
void swap(int x, int y){ int temp; temp = x; x = y; y = temp; } main() { int a = 3; int b = 5; swap(a,b); printf(%d %d\n,a,b); }

Qual loutput del programma ? 3 5 Ci accade perch la funzione swap agisce solo su una copia delle variabili a e b Per far s che la funzione agisca sulle variabili stesse e non su delle copie occorre passare gli indirizzi delle variabili

Il linguaggio C

207/243

Scambio di parametri in C
void swap(int *x_ptr, int *y_ptr) { int temp; temp = *x_ptr; *x_ptr = *y_ptr; *y_ptr = temp; }

main() { int a = 3; int b = 5; swap(&a,&b); printf(%d %d\n,a,b); }


Il linguaggio C 208/243

104

Tipi di dato restituiti dalle funzioni


Le funzioni possono restituire: dati di tipo semplice, come char, int, long, float, double, puntatori a void, o puntatori a qualche tipo di dato, ma anche strutture (struct) o puntatori a struct. All'atto della chiamata, il valore restituito da una funzione: - pu essere utilizzato come espressione booleana: double somma( double f, double g); if ( somma(a,b) > 100.3 ) pu essere utilizzato come membro di destra in un'istruzione di assegnamento, f = somma(a,b); oppure pu non essere considerato affatto. somma(a,b);
Il linguaggio C 209/243

Tipi di dato restituiti dalle funzioni


Vediamo un esempio di restituzione di una struct.
struct point { int x; int y; }; struct point crea_point( int x, int y ) { struct point p; p.x = x; p.y = y; return p ; } void main(void) { struct point p1; int x=21 , y = -10987; p1 = crea_point( x, y ); printf ( "p1.x=%d p1.y=%d \n" , p1.x, p1.y ); }
Il linguaggio C 210/243

105

Considerazioni sui puntatori restituiti dalle funzioni


Una funzione pu restituire un puntatore ad un qualche tipo di dato, ma la correttezza nell'uso di questo puntatore si pu fare, dipende da come lo spazio in memoria allocato. Cio: Esempio corretto: p allocato dinamicamente quindi, anche se p una variabile locale, lo spazio allocato sopravvive alla terminazione della funzione

int *alloca_vettore( int size ) { int *p; p = malloc(size*sizeof(int)); return p ; }

void main(void) { int *v; v = alloca_vettore ( 10 ) ; ... usa v .... }

Il linguaggio C

211/243

Considerazioni sui puntatori restituiti dalle funzioni


Esempio sbagliato: p una variabile locale, lo spazio allocato non sopravvive alla terminazione della funzione
int *alloca_vettore( int size ) { int p[1000]; return p ; } } void main(void) { int *v; v = alloca_vettore ( 10 ) ;

Esempio corretto: vet_globale una variabile globale, quindi sopravvive alla terminazione della funzione
int vet_globale[10000]; int *spazio_pre_allocato( void ) { return vet_globale ; } }
Il linguaggio C 212/243

void main(void) { int *v; v = spazio_pre_allocato(); ... usa v ....

106

Il Prototipo delle funzioni


Il prototipo o dichiarazione della funzione un'istruzione che ripete l'intestazione della funzione, senza il codice. es: double somma( double a, double b) ; void main( void) { double A=10 , B=29, C; C = somma(A,B); } dichiarazione

chiamata

double somma( double a, double b) { return a+b ; }

definizione

Il linguaggio C

213/243

Il Prototipo delle funzioni


Il prototipo serve a due scopi: 1) dare visibilit alla funzione quando il luogo in cui chiamata precede il luogo in cui definita (se stiamo sullo stesso file), altrimenti il compilatore non sa cos' il simbolo nome della funzione. Se il compilatore trova un simbolo nuovo seguito da parentesi tonde, lo tratta come una funzione che restituisce un valore intero. 2) informare il compilatore su come deve trattare il dato restituito dalla funzione, e su come passare i dati agli argomenti della funzione. Quindi il prototipo e la definizione della funzione devono essere coerenti, altrimenti il main (nell'esempio) tratterebbe i dati scambiati in modo diverso da come la funzione somma si aspetta, generando errori. Il compilatore si accorge di un'inconsistenza tra prototipo e definizione della funzione solo se entrambe stanno su uno stesso modulo. Se stanno su due moduli diversi il compilatore non se ne accorge e non ci avvisa.

Il linguaggio C

214/243

107

Ordine di valutazione degli argomenti passati alle funzioni.


Il compilatore C opera in modo che le espressioni passate come argomenti alle funzioni sono prima valutate, ed il risultato della valutazione viene scritto sullo stack per essere disponibile al codice che implementa la funzione stessa. Sorgono due problemi: - in che ordine vengono valutate le espressioni? -in che ordine vengono scritti sullo stack i risultati delle valutazioni ? La risposta alle due domande la stessa: vengono prima valutate (e il risultato scritto sullo stack) le espressioni passate come ultimo argomento della funzione (le pi a destra), e poi via via quelle pi a sinistra.

Il linguaggio C

215/243

Ordine di valutazione degli argomenti passati alle funzioni.


void stampa3( int a, int b, int c){ printf("a=%d b=%d c=%d\n", a, b, c);

printf("&a=%p &b=%p &c=%p\n,&a,&b,&c);

} void main( void) { int x=0; stampa3 (x++, x++, x++); } L'output del programma sar del tipo: a=2 b=1 c=0 (c valutato per primo)

&a=9003:0FF8 &b=9003:0FFA &c=9003:0FFC (c primo su stack) che indica come il terzo argomento (ultimo, cio pi a destra) venga valutato (e scritto sullo stack) per primo, e poi gli argomenti via via pi a sinistra.
Il linguaggio C 216/243

108

Puntatori a funzione
In C possibile utilizzare dei puntatori a funzioni, ovvero delle variabili a cui possono essere assegnati gli indirizzi in cui risiedono le funzioni, e tramite questi puntatori a funzione, le funzioni puntate possono essere chiamate all'esecuzione. Confrontiamo la dichiarazione di una funzione:
tipo_restituito nome_funzione (paramdef1, paramdef2, ...)

con la dichiarazione di un puntatore a funzione:


tipo_restituito ( * nome_ptr_a_funz ) (paramdef1, paramdef2, ...)

dove: paramdef1, paramdef2, ecc. sono la definizione degli argomenti da passare alla funzione all'atto della chiamata (ad es.: int i). tipo_restituito il tipo del dato che viene restituito come risultato dalla funzione. Ad esempio la seguente dichiarazione definisce un puntatore a funzione che punta a funzioni le quali prendono come argomenti due double, e restituiscono un double (void). double (*ptrf) ( double g, double f);
Il linguaggio C 217/243

Puntatori a funzione
Il C tratta i nomi delle funzioni come se fossero dei puntatori alle funzioni stesse. Quindi, quando vogliamo assegnare ad un puntatore a funzione l'indirizzo di una certa funzione dobbiamo effettuare un operazione di assegnamento del nome della funzione al nome del puntatore a funzione Se ad es. consideriamo la funzione dell'esempio precedente: double somma( double a, double b); allora potremo assegnare la funzione somma al puntatore ptrf cos: ptrf = somma;

Il linguaggio C

218/243

109

Puntatori a funzione: esecuzione


Analogamente, l'esecuzione di una funzione mediante un puntatore che la punta,viene effettuata con una chiamata in cui compare il nome del puntatore come se fosse il nome della funzione, seguito ovviamente dai necessari parametri. Per es. riprendiamo l'esempio della somma: double somma( double a, double b); /* dichiarazione */ void main( void) { double A=10 , B=29, C; double (*ptrf) ( double g, double f); ptrf = somma; C = ptrf (A,B); /* chiamata alla funz. somma */ } double somma( double a, double b) { /* definizione */ return a+b ; } Spesso complicato definire il tipo di dato puntatori a funzione, ed ancora di pi definire funzioni che prendono in input argomenti di tipo puntatori a funzione. In queste situazioni sempre bene ricorrere alla typedef per creare un tipo di dato puntatore a funzione per funzioni che ci servono, ed utilizzare questo tipo di dato nelle altre definizioni.
Il linguaggio C 219/243

Usare la typedef per rendere leggibile il codice C


Esempio: esiste in ambiente unix una funzione detta signal che puo' servire ad associare una funzione al verificarsi di un evento. Ad esempio pu servire a far eseguire una funzione allo scadere di un timer. Il prototipo della funzione, contenuto in signal.h, e' il seguente: #include <signal.h> void (*signal(int signum, void (*handler)(int) ) )(int); NON E' SUBITO CHIARISSIMO COSA SIA QUESTA ROBA !!! Il significato che 1) la funzione signal vuole come parametri un intero signum, ed un puntatore handler ad una funzione che restituisce un void e che vuole come parametro un intero. 2) la funzione signal restituisce un puntatore ad una funzione che restituisce un void e che vuole come parametro un intero.
Il linguaggio C 220/243

110

Usare la typedef per rendere leggibile il codice C


Converrebbe definire un tipo di dato come il puntatore a funzione richiesta:
typedef void (*tipo_funzione) (int);

ed utilizzarlo per definire la signal cos:


tipo_funzione signal(int signum, tipo_funzione handler);

Nel man della signal e' presente questo commento: If you're confused by the prototype at the top of this manpage, it may help to see it separated out thus: typedef void (*handler_type)(int); handler_type signal(int signum, handler_type handler);

Il linguaggio C

221/243

Funzioni con numero di argomenti variabile


In C e' possibile definire funzioni aventi un numero di argomenti variabile, cioe' funzioni che in chiamate diverse possono avere un numero di argomenti diverso, ma ne debbono avere sempre almeno uno. Ne un esempio la printf(); Si utilizza a questo scopo una struttura va_list definita nel file stdarg.h. Vediamo un esempio di funzione che riceve in input n argomenti, di cui il primo e' un intero che contiene il numero di argomenti seguenti, e gli altri sono delle stringhe, cioe' dei puntatori a char. La funzione deve solo stampare tutti gli argomenti passati.

Il linguaggio C

222/243

111

Funzioni con numero di argomenti variabile


void print_lista_argomenti(int narg, ...) { va_list va; int i; char *ptrchar; /* va_start inizializza va_list all'argomento, tra passati a print_lista_argomenti, che segue l'argomento narg indicato come secondo argomento nella va_start, cio inizializza la lista al primo degli argomenti variabili */ va_start(va,narg); for(i=0;i<narg;i++) { ptrchar=va_arg(va,char*); /*ptrchar punta alla stringa passata*/ printf("%d %s\n", i, ptrchar); } va_end(va); }

la funzione potra' essere chiamata in una di questi modi: print_lista_argomenti(0); print_lista_argomenti(4,"primo","secondo","terzo","quarto");

Il linguaggio C

223/243

Funzioni con numero di argomenti variabile


Vediamo un esempio complicato ma utile di uso della lista di argomenti variabili. Stampa gli argomenti passati, anche se sono di tipo diverso. Usa un primo parametro come formato per sapere cosa viene passato nei successivi argomenti, indicando con s una stringa, con d un intero, con c un carattere.
#include <stdio.h> #include <stdarg.h> void stampa_argomenti(char *fmt, ...) { va_list ap; int d; char c, *p, *s; va_start(ap, fmt); while (*fmt) switch(*fmt++) { case 's': /* string */ s = va_arg(ap, char *); printf("string %s\n", s); break;
Il linguaggio C 224/243

112

Funzioni con numero di argomenti variabile


case 'd': /* int */ d = va_arg(ap, int); printf("int %d\n", d); break; case 'c': /* char */ c = va_arg(ap, char); printf("char %c\n", c); break; } va_end(ap); } void main (void) { stampa_argomenti ( "sdc" , "stringa" , (int) 10 , (char)'h' ); stampa_argomenti ("s" , "stringa2"); }

Il linguaggio C

225/243

INPUT ed OUTPUT
Le funzioni della libreria standard per il C per l'I/O sono definite nell'header file stdio.h . (input / output standard) Quando un programma C entra in esecuzione l'ambiente del sistema operativo si incarica di aprire 3 files di I/O, ovvero 3 flussi di dati, e di fornire i rispettivi puntatori di tipo FILE * globali. La struttura chiamata FILE contiene le informazioni fondamentali sul file, quali il modo di apertura del file, il nome del file, l'indirizzo di un buffer in cui sono conservati i dati letti dal file e la posizione corrente nel buffer. L'utente non deve conoscere questi dettagli, ma deve memorizzare in una variabile di tipo puntatore a FILE (FILE*) l'indirizzo di questa struttura per utilizzarla nelle letture o scritture su file.

Il linguaggio C

226/243

113

INPUT ed OUTPUT
I flussi di dati standard sono: STANDARD INPUT serve per l'input normale (per default da tastiera), e ha come puntatore a FILE la variabile globale stdin. Dallo standard input prendono i dati le funzioni getchar, gets e scanf. STANDARD OUTPUT serve per l'output normale (per default su schermo), e ha come puntatore a FILE la variabile globale stdout. Sullo standard output scrivono i loro dati le funzioni putc, printf e puts STANDARD ERROR serve per l'output che serve a comunicare messaggi di errore all'utente (per default anche questo su schermo), e ha come puntatore a FILE la variabile globale stderr.

stdin, stdout e stderr sono delle costanti globali contenute in stdio.h,. e non possono essere modificate. Questi flussi possono pero' essere ridirezionati in modo da scrivere su file su disco, oppure di leggere da file su disco.
Il linguaggio C 227/243

INPUT
- Il sistema prevede che l'input sia organizzato a linee, ciascuna terminata da un carattere new line (\n), solitamente fornite dall'utente tramite la tastiera, ma a volte anche fornite via file. - L'utente da tastiera pu digitare i caratteri e cancellarli, ma tali caratteri non vengono passati all'applicazione fino a che l'utente non preme <RETURN> nel qual caso i dati presenti sul buffer di tastiera vengono inseriti in un vettore di caratteri, e in fondo viene aggiunto un carattere NEW LINE (individuato da \n ). - NOTARE che l'utente non ha a disposizione i caratteri fino a che non viene premuto RETURN, dopodich i dati, in forma di linea di testo terminante con un NEW_LINE sono pronti per essere letti dall'applicazione. La lettura dei dati pu essere effettuata dallo standard input un carattere alla volta oppure una linea alla volta.

Il linguaggio C

228/243

114

INPUT
La lettura carattere per carattere viene effettuata mediante la funzione: int getchar(void); che restituisce il successivo carattere in input in forma di codice ASCII, cioe' restituisce ad es. 65 per 'A', 66 per 'B', ecc. 97 per 'a'. 98 per 'b', oppure restituisce EOF quando incontra la fine del flusso di input, che viene rappresentata come la fine del file di input. Tale funzione e' bloccante nel senso che quando una linea di caratteri precedentemente data in input e' finita, rimane in attesa che dallo standard input arrivi una nuova linea di dati.

La lettura di una intera linea viene effettuata mediante la funzione: char *gets( char *dest); che scrive in dest la linea e restituisce un puntatore a dest se tutto va bene, restituisce NULL altrimenti.
Il linguaggio C 229/243

Fine dell'INPUT
Se l'utente preme contemporaneamente la coppia di tasti (CTRL+d in UNIX, CTRL+z in DOS seguito prima o poi da RETURN), in un certo senso segnala all'applicazione che il flusso di dati e' terminato. Nella linea di testo viene aggiunto un carattere EOF (che di solito e' rappresentato dal valore -1) ma che e' bene sempre confrontare con la costante EOF (definita in stdio.h), ed il flusso di input viene chiuso, cio da quel momento in avanti ogni successiva chiamata a getchar restituir sempre EOF. ESEMPIO di lettura dallo standard input
#include <stdio.h> void main(void) { int ch; while( (ch=getchar()) != EOF ) { /* uso il carattere, ad es lo incremento di 1 e lo stampo*/ ch ++; putchar(ch); } }
Il linguaggio C 230/243

115

Ridirezionamento di input e output


L'utente puo' utilizzare lo standard input dare input non solo da tastiera ma anche da file, ridirezionando un file sullo standard input nel seguente modo, al momento dell'esecuzione del programma: program < file_input Analogamente l'output di un programma puo' essere ridirezionato su un file, su cui sara' scritto tutto l'output del programma invece che su video program > file_output e le due cose possono essere fatte assieme program < file_input > file_output In UNIX si pu ridirezionare assieme standard output e standard error: program >& file_error_and_output

Il linguaggio C

231/243

Output non formattato


La scrittura dei dati sullo standard output (video) puo' essere effettuata un carattere alla volta mediante la funzione: int putchar(int ch); che restituisce il carattere scritto ch se va tutto bene, altrimenti restituisce EOF in caso di errore.

Il linguaggio C

232/243

116

Output formattato
L'output pu essere effettuato anche in maniera formattata, e non solo un carattere alla volta. La funzione printf serve proprio a stampare un output a blocchi sullo standard output secondo un formato specificato, traducendo le variabili in caratteri, cio printf stampa sullo standard output gli argomenti arg1, arg2, ... secondo le specifiche contenute nel format. int printf(char *format, ... ); meno formalmente int printf(char *format, arg1, arg2, arg 3, ...); il format e' una stringa di caratteri null-terminata, in cui compaiono o dei semplici caratteri che verranno stampati cos come sono oppure degli specificatori di formato nella forma %qualcosa. "%d" "%10d" stampa un intero stampa un intero e un minimo di 10 caratteri almeno in cui l'intero e' a destra

"%-10d" stampa un intero e un minimo di 10 caratteri almeno in cui l'intero e' a sinistra "%f" "%lf" "%10f" stampa un float stampa un double stampa un float e un minimo di 10 caratteri almeno in cui il float e' a destra
Il linguaggio C 233/243

Output formattato
"%-10f" stampa un float e un minimo di 10 caratteri almeno in cui il float e' a sinistra "%10.5f" stampa un float e un minimo di 10 caratteri con al massimo 5 cifre dopo il punto decimale "%s" stampa tutti i caratteri di una stringa (cioe' un vettore di caratteri con un \0 in fondo) fino a che incontra lo 0 finale "%10s" stampa almeno 10 caratteri di una stringa, con la stringa a destra

"%15.10s" stampa almeno 15 caratteri, prendendone al massimo 10 dalla stringa, con la stringa a destra "%c" stampa un singolo carattere NB. per stampare un % devo scrivere %%

NB alcuni caratteri di controllo possono essere scritti mediante la loro sequenza di escape NEW LINE (a capo linea) TABULAZIONE BACKSPACE \n \t \b torna indietro di un carattere ma non lo cancella se per scrivo qualcosa gli passo sopra

Il linguaggio C

234/243

117

Output
NB poich le stringhe vengono delimitate da due doppi apici " per stampare un doppio apice devo scriverlo preceduto da un BACKSLASH cio devo scrivere \" Analogamente, poich la \ serve a indicare una sequenza di escape, per stampare una \ devo scriverne due. (vedi nomi file nella fopen). esempio: int i=16; double g=107.13987626; char *str="pippo"; printf("%d) %10.3lf \"%s\"\n",i,g,str); da' come risultato 16) 107.139 "pippo"

Il linguaggio C

235/243

Input formattato da stdin


Per estrarre dal flusso dello standard input i dati e' spesso utilizzata la funzione scanf. La scanf cioe' prende l'input dallo standard input (aspetta un RETURN) e cerca di estrarre i dati che sono specificati nel suo primo agomento int scanf(char *format, ....); int scanf(char *format, &arg1, &arg2, &arg 3, ... );

I dati estratti vengono scritti nelle variabili i cui indirizzi sono passati come argomenti successivi al primo nella scanf.

NB. L'errore classico e' passare come argomento la variabile e non il suo indirizzo.

Il linguaggio C

236/243

118

Input formattato da stdin


La stringa di formato pu contenere sia dei semplici caratteri, che allora devono corrispondere ai caratteri che vengono digitati in input, sia degli specificatori di conversione nella forma %qualcosa che indicano come devono essere interpretati i caratteri che costituiscono il flusso di input.

%c viene letto un singolo carattere e copiato singolarmente nel primo byte indicato dal puntatore %10c vengono letti 10 caratteri e copiati nei primi 10 byte indicati dal puntatore

%s viene letta la stringa e scritta interamente a partire dal puntatore passato, e in fondo viene aggiunto un carattere '\0' di terminazione %d %f %lf viene letto un intero viene letto un float viene letto un double

Il linguaggio C

237/243

Input formattato da stdin


La scanf termina quando esaurisce la sua stringa di formato o quando verifica una inconsistenza tra l'input e le specifiche del formato. La scanf restituisce il numero di elementi trovati. Se non stato scritto nessun elemento possono esserci due motivi diversi: o lo standard input era stato chiuso e allora la scanf restituisce EOF, oppure lo standard input era ancora aperto ma l'input non era consistente con le richieste del formato e c' stato errore nella conversione ed allora viene restituito 0. Es. se in input l'utente scrive: "punti: 14 9.1 7 21" mediante la sequenza di istruzioni int i,j,k,result; double g; result=scanf("punti: %d %lf %d %d", &i , &g , &j , &k); if(result==EOF) printf("FINE FILE INPUT\n"); else if(result<4) printf("ERRORE, solo %d dati in input\n",result); else printf("OK: i=%d g=%lf j=%d k=%d\n",i,g,j,k);
Il linguaggio C 238/243

119

Input formattato da stdin


ottiene a video: OK: i==14 g==9.1 j==7 k==21

NB la stringa di formato viene scandita dalla scanf, e se nel formato c' un blank (uno spazio bianco) l'input pu contenere pi caratteri di spaziatura blank tab newline ecc. che non vengono considerati (cio vengono considerati come un unico carattere blank).

Il linguaggio C

239/243

Accesso ai files
Problema: Leggere i dati da un file, e scrivere dei dati su un altro file. Supponiamo di avere un file c:\users\input.txt di testo formato da linee costituite da due double x e y (ovvio in forma di caratteri ascii). 139.2 -13.1 .... 29.1 1009.0 ....

Vogliamo leggere tutte le coppie e scrivere su un file di testo c:\users\output.txt le sole coppie in cui x>0

Il linguaggio C

240/243

120

Accesso ai files
int main(void) { FILE *finput, *foutput; double x,y; int result; finput=fopen("c:\\input.txt","rt"); if ( finput==NULL ) { printf("errore: impossibile aprire file di input\n"); exit(1); } foutput=fopen("c:\\output.txt","wt"); if ( foutput==NULL ) { printf("errore: impossibile aprire file di output\n"); exit(1); }
Il linguaggio C

241/243

Accesso ai files
/* fino a che non sono alla fine del file di input */ while( (result=fscanf(finput,"%lf %lf\n", &x, &y)) != EOF ) { if(result != 2) { /* ho letto meno campi di quelli richiesti */ printf("errore in lettura\n"); exit(1); } if( x > 0.0 ) fprintf(foutput,"%f %f\n",x,y); } fclose(finput); fclose(foutput); return(0); }

Il linguaggio C

242/243

121

Accesso ai files
NB. prima di essere letto o scritto un file deve essere aperto mediante la funzione fopen FILE *fopen( char *nomefile, char *modo); il primo parametro specifica il nome del file ed eventualmente il percorso per raggiungerlo come ad es: c:\\pippo.txt. NOTARE la necessit d'usare la doppia BACKSLASH \\ posto di una sola. il secondo parametro specifica il modo di apertura del file, che sara' diverso a seconda dell'uso che si vuole fare del file. Il modo puo' essere uno dei seguenti "r" apertura in sola lettura, per leggere, posiziona all'inizio del file. "w" apertura in sola scrittura, crea il file se non esiste, lo cancella e si posiziona all'inizio se gia' esiste. "a" apertura in scrittura per aggiungere dati alla fine del file, se il file esiste gia' si posiziona alla fine del file per poter aggiungere dati senza cancellare quelli presenti A questi modi si puo' aggiungere un "+" ottenendo "r+" "w+" "a+" per permettere l'aggiornamento in lettura e scrittura. La posizione in cui ci si colloca e' quella dovuta a "r" o "w" o "a" A questi modi si puo'' aggiungere un "t" per indicare che si apre il file come un file di testo, ed allora saranno utilizzabili le primitive di lettura e scrittura di testo fgets, fputs, oppure un "b" ad indicare che il file verra' aperto come un file binario. Per default il modo di apertura di tipo testo "t".
Il linguaggio C 243/243

Accesso ai files
La funzione fopen restituisce un PUNTATORE a FILE detto file pointer che punta ad una struttura chiamata FILE che contiene le informazioni fondamentali sul file, quali il modo di apertura del file, il nome del file, l'indirizzo di un buffer in cui sono conservati i dati letti dal file e la posizione corrente nel buffer. L'utente non deve conoscere questi dettagli, ma deve memorizzare in una variabile questo valore restituito ed utilizzarlo in tutte le altre letture o scritture su file. Quando il file e' stato aperto, possiamo leggerlo e/o scriverlo a seconda del modo di apertura utilizzando le seguenti funzioni di libreria.

int getc(FILE *f); int putc(int c, FILE *f); int fscanf(FILE *finput, char *format, ... ); int fprintf(FILE *foutput, char *format, ... ); char *fgets(char *dest, int nchar, FILE *finput);

Il linguaggio C

244/243

122

I/O da file
Quando il file non serve pi, deve essere chiuso con la chiamata alla funzione di libreria int fclose( FILE *f); che restituisce 0 in caso vada tutto bene, 1 in caso di errore. int getc(FILE *f); legge il prossimo carattere dal file f, restituisce il carattere oppure EOF in caso di errore o di fine file. int putc(int c, FILE *f); scrive il carattere c sulla posizione corrente del file. restituisce il carattere scritto oppure EOF in caso di errore.

Il linguaggio C

245/243

I/O da file
int fscanf(FILE *finput, char *format, ... ); e' analoga alla scanf ma prende input dal file finput invece che dallo standard input restituisce il numero di campi letti oppure EOF se la posizione corrente nel file e' a fine file int fprintf(FILE *foutput, char *format, ... ); e' analoga alla printf ma scrive sul file invece che sullo standard output restituisce il numero di byre scritti

La lettura scrittura da un file puo' essere effettuata anche linea per linea usando le seguenti funzioni: char *fgets(char *dest, int nchar, FILE *finput); legge dal file finput una linea di input (compreso il NEWLINE \n) fino ad un massimo di nchar1 caratteri e la scrive nel vettore dest. Aggiunge dopo l'ultimo carattere un '\0' per terminare la stringa. Restituisce un puntatore alla stringa in cui ha scritto oppure NULL in caso di errore int *fput(char *string, FILE *foutput); scrive sul file foutput una stringa ( con o senza il NEWLINE \n ) restituisce 0 se va tutto bene, EOF in caso di errore.
Il linguaggio C 246/243

123

La gestione dellerrore
Esiste una variabile globale intera, definita nell'header errno.h che viene settata nel caso in cui una chiamata di sistema non possa eseguire correttamente il suo compito. Tale variabile allora indica il tipo di errore avvenuto. #include <errno.h> extern int errno; Tale variabile pu essere letta e scritta come ogni altra variabile. In particolare il valore di questa variabile serve come indice per una variabile di sistema che un vettore di stringhe. Queste stringhe contengono dei messaggi di errore caratteristici dell'errore avvenuto. #include <stdio.h> const char *sys_errlist[];

Il linguaggio C

247/243

La gestione dellerrore
Nel caso una funzione di sistema ci avvisi di un errore possiamo farci visualizzare a video (sullo standard error) notifica l'errore, utilizzando la funzione void perror(const char *str); La stringa puntata da str viene visualizzata prima del messaggio di errore. Se l'ultima chiamata di sistema non ha causato errno, perch il suo valore non definito. #include <stdio.h> #include <errno.h> void apri_file(void) { FILE *finput; finput=fopen(/home/user/input.txt","rt"); if ( finput==NULL ) { perror("errore in funzione apri_file); exit(1); } }
Il linguaggio C 248/243

124

La gestione dellerrore
errno una variabile globale definita dalla libreria. Se la si legge senza che si sia verificato un errore il risultato indefinito, la stess cosa succeder per la funzione perror. Prima di poter utilizzarle, quindi necessario verificare che si sia verificato un errore: finput=fopen(/home/user/input.txt","rt"); if ( finput==NULL ) { perror("errore in funzione apri_file); exit(1); }

Il linguaggio C

249/243

I/O da file
Quando un file precedentemente aperto non serve pi, deve essere chiuso con la chiamata alla funzione di libreria int fclose( FILE *f); che restituisce 0 in caso vada tutto bene, 1 in caso di errore. Poich l'I/O con le funzioni viste bufferizzato, potrebbero essere rimaste ancora delle operazioni di scrittura da completare, cio qualche byte scritto sul file in precedenza potrebbe essere ancora in un buffer temporaneo, e non essere stato ancora fisicamente scritto sul file. Con la chiamata alla fclose si effettuano definitivamente eventuali operazioni di scrittura rimaste in sospeso. La funzione int fflush( FILE *f); effettua esplicitamente lo svuotamento dei buffer facendo completare le operazioni rimaste in sospeso riguardanti lo stream di output indicato da f. Serve ad assicurarci che ad un certo istante le operazioni precedenti siano state effettuate. Pu servire ad esempio dopo alcune printf per far effettivamente scrivere i caratteri sul video. In qualche caso infatti tale operazione potrebbe essere dilazionata. fflush() restituisce 0 in caso vada tutto bene, EOF in caso di errore.

Il linguaggio C

250/243

125

I/O da file
La funzione int feof( FILE *f); restituisce un valore diverso da 0 se la posizione corrente del file ha raggiunto la fine del file. Restituisce 0 se non siamo alla fine del file. La funzione int ferror( FILE *f); restituisce un valore diverso da 0 se lo stream ha verificato un qualche tipo di errore.

Il linguaggio C

251/243

I/O in blocchi
INPUT / OUTPUT di BLOCCHI di byte da file ANSI C mette a disposizione anche delle primitive che permettono di leggere/scrivere su un file un blocco di byte di una dimensione specificata. I prototipi di queste finzioni sono anch'essi contenuti nell'header stdio.h, e tali funzioni sono:
size_t fread(void *ptr, size_t size, size_t number, FILE *finput);

cerca di leggere dal file finput un blocco di byte formato da number blocchi ciascuno di size byte. I dati letti sono scritti nell'area dati puntata da ptr, che deve ovviamente essere correttamente allocata, cio di dimensioni sufficienti. fread restituisce il numero di blocchi letti, e non il numero di byte letti. Se all'inizio della lettura si incontra la fine del file la funzione fread restituisce 0, perch non riesce a leggere neanche un blocco. Per anche in caso di un qualche tipo di errore fread restituisce 0. Quindi se fread restituisce zero necessario utilizzare le funzioni ferror o feof per capire se accaduto un errore o se si raggiunta la fine del file.
size_t fwrite(const void *ptr, size_t size, size_t num, FILE*fout);

cerca di scrivere sul file foutput un blocco di byte formato da number blocchi ciascuno di size byte, copiandoli dall'area dati puntata da ptr. fwrite restituisce il numero di blocchi scritti, e non il numero di byte. Come per la fread in caso di un qualche tipo di errore fwrite() restituisce 0.

Il linguaggio C

252/243

126

Riga di comando
LANSI C mette a disposizione il modo di passare al programma dei parametri nel momento in cui questo viene lanciato. Partendo ad esempio da una shell di comandi di Windows NT (una finestra simil DOS per intenderci) possiamo far seguire al nome del programma una serie di caratteri separati da spazi, che verranno copiati e passati sotto forma di stringhe in un vettore di puntatori a char passati come argomento al main. Il main dovr essere a tal scopo definito come nell'esempio seguente, dove: - argc indica il numero di parametri passati pi uno (il nome del programma). - argv un vettore di stringhe che contiene la riga di comando, in prima posizione il nome del programma lanciato, nelle seguenti i parametri passati Il programma stampa il numero dei parametri passati a riga di comando compreso il nome del programma, e poi li stampa ad uno ad uno

Il linguaggio C

253/243

Riga di comando
#include <stdio.h> int main(int argc, char *argv[]) { int i; printf("argc=%d { ",argc); for ( i=0; i<argc; i++) printf("%d:%s ", i, argv[i]); printf("}\n"); }
Se il programma si chiama param, pu essere eseguito "lanciandolo" dalla shell di comando mediante una delle seguenti linee di comando, ottenendo come output:
linea di comando | output | param calvin hobbes 3.0 40 | argc=5 { 0:param 1:cal 2:hobbes 3:3.0 4:40} param param calvin | argc=1 { 0:param } | argc=2 { 0:param 1:calvin }

Il linguaggio C

254/243

127

I/O di basso livello


Abbiamo in precedenza visto che possibile effettuare I/O da file, utilizzando primitive di tipo fopen, fread fwrite, fscanf, fgets. Tali operazioni sono per limitate ai file. Quello che si vorrebbe avere a disposizione sono delle primitive che possono agire indifferentemente su file o su dispositivi di I/O di tipo diverso, quali interfacce di rete, interfacce seriali ecc. Si vorrebbe cio poter utilizzare una stessa primitiva per accedere a entit di tipo diverso. Esistono a tale scopo delle funzioni di librerie cosiddette per l'I/O di basso livello, che permettono di operare su diversi device. Vengono ad esempio utilizzate per scrivere applicazioni per comunicazioni via rete. Ogni dispositivo per I/O, verr aperto con una funzione detta open che restituisce un intero detto "descrittore di file". Questo numero intero rappresenta un indice per una struttura dati che descrive le modalit con cui operare sul dispositivo. Tale descrittore verr utilizzato in tutte le chiamate per l'effettuazione di I/O. Poich queste istruzioni sono di basso livello, non esiste pi distinzione tra file di testo e file binari. Tutti i file sono visti come concatenazione di byte.

Il linguaggio C

255/243

I/O di basso livello


Un esempio d'uso di queste primitive qui di seguito riportato. /*leggo i byte di un file binario. */ #include <stdio.h> #include <fcntl.h> main(int argc,char **argv) { int fd; int bytes_read; char byte; if((fd=open(argv[1],O_RDONLY))==-1) exit(1); /*errore,file non aperto*/ while ( (bytes_read=read(fd,&byte,1))>0 ) { /* leggo un byte */ .... uso il byte letto } if (bytes_read==-1) exit(1); /* errore in lettura file */ close(fd); }

Il linguaggio C

256/243

128

I/O
#include <stdio.h> #include <fcntl.h> Per aprire un file si usa la funzione:

int open(char *filename, int flag, int perms)


che ritorna un file descriptor con valore >=0, oppure -1 se l'operazione fallisce. Il parametro flag controlla l'accesso al file ed una combinazione (un OR bit a bit) ha i seguenti predefiniti valori definiti nel file fcntl.h: O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_WRONLY ecc., con i seguenti significati: O_APPEND apre per aggiungere dati in fondo al file, e posiziona il puntatore alla posizione corrente alla fine del file O CREAT crea un file nuovo se non esiste O_EXCL viene usato con O_CREAT, se il file gi esiste causa errore O_RDONLY apre e consente solo la lettura, si posiziona all'inizio del file O RDWR apre e consente sia lettura che scrittura si posiziona all'inizio O_WRONLY apre e consente solo la scrittura O_TRUNC se il file esiste viene troncato, riparte da lunghezza zero Il parametro "perms" specifica i permessi da assegnare al file quando questo viene creato, in caso contrario viene ignorato.
Il linguaggio C 257/243

I/O
Per creare un file si puo' anche usare la funzione: int creat(char *filename, int perms) Per chiudere un file si usa: int close(int fd)

Per leggere/scrivere uno specificato numero di bytes da/su un file immagazzinati in una locazione di memoria specificata da "buffer" si utilizzano: int read(int fd, char *buffer, unsigned length) int write(int fd, char *buffer, unsigned length) Queste due funzioni ritornano il numero di byte letti/scritti o -1 se falliscono.

Il linguaggio C

258/243

129

I/O ad accesso casuale


Un file pu essere acceduto in lettura o scrittura anche in una maniera diversa da quella sequenziale, cio possibile spostarsi in un file aperto ovvero spostare il cosiddetto puntatore alla posizione corrente nel file. Questo puntatore un intero e indica il prossimo byte da leggere o scrivere. Il primo byte del file indicato da 0, il secondo da 1 e cos via. E possibile spostare la posizione corrente utilizzando una primitiva C: int fseek(FILE *stream, long offset, int ptrname); fseek stabilisce la nuova posizione del prossimo byte da leggere o scrivere. La nuova posizione viene indicata da due parametri: una base indicata da ptrname che puo' essere o l'inizio del file (SEEK_SET) o la posizione corrente (SEEK_CUR) o la fine del file (SEEK_END), ed uno scostamento cio la distanza dalla base espressa in numero di byte, che il parametro offset. La funzione restituisce -1 in caso di errore, 0 se tutto OK.
Il linguaggio C 259/243

I/O ad accesso casuale


/* lettura del byte in posizione 135 nel file */ int c; if ( ! fp=fopen("prova.dat","rt") ) exit(1); if ( (fseek(fp, (long)135, SEEK_SET)) <0 ) exit(2); c = getc(fp); /* errore */

Il linguaggio C

260/243

130

Operatori sui bit


Il C mette a disposizione degli operatori che lavorano su numeri di tipo intero (char, int, long int) manipolando il dato a livello di singolo bit. Non si applicano ad operandi floating point o a dati di tipo strutturato. & | ^ ~ >> << AND (bit a bit) OR ''

OR ESCLUSIVO (XOR) NOT (complemento ad uno) SHIFT (SCORRIMENTO) a DESTRA SHIFT (SCORRIMENTO) a SINISTRA

Il linguaggio C

261/243

Operatori sui bit


L'operatore AND & effettua un and bit a bit tra due operandi, ovvero setta ad 1 un bit in una certa posizione quando entrambi i bit in quella posizione nei due operandi valgono 1, altrimenti lo setta a 0. unsigned char op1, op2, op3; op1=63; op2=240; op3 = op1 & op2; op3 assume valore 48 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 0 0 0 0

L'operatore OR | effettua un or bit a bit tra due operandi, ovvero setta ad 1 un bit in una certa posizione quando almeno uno dei bit in quella posizione dei due operandi vale 1 altrimenti lo setta a 0. unsigned char op1, op2, op3; op1=60; op2=240; op3 = op1 | op2; op3 assume valore 252 0 0 1 1 1 1 0 0 1 1 1 1 0 0 0 0 1 1 1 1 1 1 0 0

Il linguaggio C

262/243

131

Operatori sui bit


L'operatore XOR ^ effettua un or esclusivo bit a bit tra due operandi, ovvero setta ad 1 un bit in una certa posizione quando i bit in quella posizione nei due operandi sono diversi, altrimenti lo setta a 0. unsigned char op1, op2, op3; op1=60; op2=240; op3 = op1 ^ op2; op3 assume valore 204 Questo operatore presenta una tabella di verit (bit a bit) siffatta: op1 0 1 0 1 op3 assume valore 195
Il linguaggio C 263/243

0 0 1 1 1 1 0 0 1 1 1 1 0 0 0 0 1 1 0 0 1 1 0 0

op2 0 0 1 1

op1^op2 0 1 1 0

Operatori sui bit


L'operatore NOT ~ un operatore unario che inverte lo stato di ogni bit, cio setta ad 1 i bit che valgono 0, e viceversa setta a 0 i bit che valgono 1.

op1=60; op3 = ~op1;

00111100 11000011

Il linguaggio C

264/243

132

Operatori sui bit


Gli operatori di scorrimento (SHIFT) sono operatori unari che realizzano lo spostamento a sinistra o a destra dei bit di una variabile di tipo intero, mettendo a 0 i bit che entrano, cio non rientrano da sinistra i bit usciti da destra. La forma generale dell'istruzione di SHIFT A DESTRA del tipo: variabile_intera >> numero_posizioni Significa che i bit di variabile_intera vengono tutti spostati a destra di numero_posizioni. op1=61; op3 = op1>>1; op3 assume valore 30 0 0 1 1 1 1 0 1 0 0 0 1 1 1 1 0 (in neretto il bit entrante)

Si noti come l'operatore corrisponda ad una divisione intera per multipli di 2. Shiftare a destra di 1 significa dividere per 2, shiftare di 2 dividere per 4. op3 = op1>>3; 0 0 0 0 0 1 1 1

op3 assume valore 7 (in neretto i bits entranti)


Il linguaggio C 265/243

Operatori sui bit


La forma generale dell'istruzione di SHIFT A SINISTRA del tipo: variabile_intera << numero_posizioni Significa che i bit di variabile_intera vengono tutti spostati a sinistra di numero_posizioni. I bits entranti da destra sono posti a 0. op1=61; op3 = op1<<1; op3 assume valore 122 0 0 1 1 1 1 0 1 0 1 1 1 1 0 1 0 (in neretto il bit entrante)

Notare come questo operatore corrisponda al risultato di una moltiplicazione intera per un multiplo di due finch a sinistra non esce qualche bit ad 1. Shiftare a sinistra di 1 significa moltiplicare per 2 op3 = op1<<3; op3 assume valore 232 entranti) 1 1 1 0 1 0 0 0 (in neretto i bit
Il linguaggio C 266/243

133

Operatori compositi sui bit


Il C mette a disposizione anche operatori che coniugano un'operazione bit a bit all'operazione di assegnamento. AND bit a bit e Assegnamento OR bit a bit e Assegnamento XOR bit a bit e Assegnamento Shift a Destra e Assegnamento Shift a Sinistra e Assegnamento x ^= y x >>= y x <<= y x &= y equivale a x |= y x = x&y x = x|y

equivale a x = x^y x = x>>y x = x<<y

equivale a equivale a equivale a

Il linguaggio C

267/243

Alcune funzioni della libreria C standard


Manipolazione dei buffer #include <memory.h> void *memchr (void *s, int c, size_t n) - Cerca un carattere c nei primi n caratteri di un buffer puntato da s. Restituisce un puntatore al primo carattere c trovato, o NULL se non lo trova. int memcmp (void *s1, void *s2, size_t n) - Paragona i primi n byte di due buffers s1 ed s2. Restituisce un risultato minore di zero se s1<s2, uguale a zero se s1=s2, maggiore di zero se s1>s2. void *memcpy (void *dest, void *src, size_t n) - Copia n byte di un buffer in un altro. Causa problemi se le due aree sono sovrapposte. void *memmove (void *dest, void *src, size_t n) - Copia n byte di un buffer in un altro. Funziona correttamente anche se le due aree sono sovrapposte. Restituisce un puntatore alla destinazione. void *memset (void *s, int c, size_t n) - Setta tutti i bytes di un buffer ad un dato carattere. Restituisce un puntatore al buffer s.
Il linguaggio C 268/243

134

Classificazione di caratteri
#include <ctype.h>
int isalnum(int c) - Vero se "c" e' alfanumerico, '0'...'9' 'a'...'z' 'A'...'Z'. int isalpha(int c) Vero se "c" e' una lettera dell'alfabeto. int iscntrl(int c) - Vero se "c" e' un carattere di controllo, i primi 31. int isdigit(int c) - Vero se "c" e' un numero decimale. int islower(int c) - Vero se "c" e' una lettera minuscola. int isprint(int c) - Vero se "c" e' un carattere stampabile. int ispunct (int c) - Vero se "c" e' un carattere di punteggiatura. int isspace(int c) - Vero se "c" e' un carattere spazio cio tab o blanck o newline o carriage return.. int isupper(int c) - Vero se "c" e' una lettera maiuscola. int isxdigit(int c) - Vero se "c" e' un numero esadecimale. int toascii(int c) - Converte "c" in ASCII, settando a zero il bit piu significativo. tolower(int c) - Converte "c" in minuscolo. int toupper(int c) - Converte "c" in maiuscolo.
Il linguaggio C 269/243

Conversione dei dati


#include <stdlib.h> double atof(char *string) -Converte una stringa in un valore floating point. int atoi(char *string) - Converte una stringa in un valore integer. int atol(char *string) - Converte una stringa in un valore long integer. char *itoa(int value, char *string, int radix) - Converte un valore integer in una stringa utilizzando il "radix" come base per la conversione (es 10 per conversione di numeri in base 10). Le seguenti funzioni effeftuano un maggiore controllo sull'errore: char *ltoa(long value, char *string, int radix) - Converte un valore long integer in una stringa in un dato "radix". double strtod(char *string, char *endptr) - Converte una stringa in un valore in floating point. long strtol(char *string, char *endptr, int radix) - Converte una stringa in un valore long integer utilizzando un dato "radix". unsigned long strtoul(char *string, char *endptr, int radix) - Converte una stringa in un valore long senza segno.

Il linguaggio C

270/243

135

Alcune funzioni matematiche


#include <math.h> nota le funzioni matematiche vengono definite nella libreria libm.a, per includerla necessario effettuare il link con lopzione lm.
int abs (int n) - valore assoluto di un intero. double acos(double x) - arcocoseno di x. double atan(double x) - arcotangente double ceil(double x) - il piu piccolo intero maggiore o uguale a x. double cos(double x) double exp(double x) double log(double x) void randomize(void) - coseno di angolo x in raduanti - esponenziale - logaritmo naturale - inizializza il generatore di numeri casuali. - valore assoluto di un double - inizializza il generatore di numeri casuali. - genera un numero casuale tra 0 e max_num. - potenza, x elevato alla y.
Il linguaggio C 271/243

double fabs (double x ) void srand(unsigned seed) int random(int max_num)

double pow (double x, double y)

Allocazione di Memoria
#include <malloc.h> void *calloc(size_t num elems, size_t elem_size) Alloca un vettore ed inizializza tutti gli elementi a zero. void free(void *mem address) - Libera un blocco di memoria. void *malloc(size_t num bytes) - Alloca un blocco di memoria.

Il linguaggio C

272/243

136

Funzioni su stringhe
#include <string.h>

int strlen(char *string)

- Determina la lunghezza di una stringa.

int strcmp(char *string1, char *string2) - Confronta string1 e string2 per determinare l'ordine alfabetico. char *strcpy(char *string1, char *string2)- Copia string2 in stringl. char *strncat(char *string1, char *string2, size_t n) - Aggiunge "n caratteri di string2 in string1. int strncmp(char *string1, char *string2, size_t n) stringhe. - Confronta i primi "n caratteri di due

char *strncpy(char *string1, char *string2, size_t n) - Copia i primi "n caratteri di string2 in string1.

int sprintf(char *string, char *format_string, args) Scrive output formattato su una stringa, comportamento analogo alla printf, ma scrive su una stringa. int sscanf(char *buffer, char *format_string, args) Legge input formattato da una "string", comportamento analogo alla scanf, ma prende l'input da una stringa Il linguaggio C
273/243

Funzioni per la cattura del tempo


#include <time.h> time_t time(time_t *t); restituisce il tempo trascorso a partire dal 1 gennaio del 1979, misurato in secondi. Se il puntatore passato come argomento e' diverso da NULL, scrive lo stesso valore nella locazione di memoria puntata dal puntatore. In caso di errore restituisce -1 e setta errno in modo appropriato. int gettimeofday(struct timeval *tv, struct timezone *tz); l'argomento di tipo timezone e' obsoleto, e viene istanziato a NULL. La struttura di tipo timeval e' cos definita: struct timeval { long tv_sec; /* seconds */

long tv_usec; /* microseconds */ }; La funzione scrive nella struttura puntata da tv il valore del clock corrente, ed e' quindi piu' precisa della time che mette a disposizione solo un valore in secondi. Restituisce 0 se tutto OK, altrimenti 1 se si verifica qualche errore.

Il linguaggio C

274/243

137

Funzioni per la cattura del tempo


clock_t clock(void); restituisce il tempo di CPU utilizzato dal processo chiamante, a partire dalla prima volta che stata chiamata la clock(). Il tempo restituito la somma del tempo utilizzato dal processo chiamante e dai processi di sistema nell'esecuzione delle chiamate di sistema effettuate dal processo chiamante. Il valore restituito convenzionalmente il numero di clock eseguiti. Per ottenere il tempo in secondi deve essere diviso per la costante CLOCKS_PER_SEC.
clock_t start, end; start = clock(); istruzioni del programma ... end = clock(); printf("tempo consumato in secondi: %lf\n", (double)(end-start) / (double)CLOCKS_PER_SEC );

Il linguaggio C

275/243

Changelog
14/10/2003 Sostituiti exit(0) con exit(1) nelle slide. 196, 197, 203, 233. Aggiunta la slide Changelog. Eliminate le slides con esempi finali. Stile bold su alcuni pezzi di codice. Aggiunta la slide 204 sulla gestione degli errori. Modificata la agina sulle librerie matematiche. Dopo una serie innumerevole di aggiusti grafici, aggiunti i riferimenti al C99 e le macro con parametri (con tutta lambaradan sul preprocessore) (UV) Aggiusti grafici e di testo. (UV) 29/9/2005 10/2005

Il linguaggio C

276/243

138