Programmi con argomenti passati da linea di comando Parametri del main() Quando compiliamo un programma viene generato un file eseguibile (estensione.exe nei sistemi Windows). I programmi possono prendere degli argomenti dalla riga di comando con la quale vengono eseguiti. Esempio: copy file1 file2 I programmi visti finora non prendevano argomenti. La funzione main non prendeva parametri: int main(void) {... Fondamenti di Informatica 2 1
Parametri del main() Per poter usare gli argomenti con cui il programma è stato lanciato, la definizione di main deve diventare: int main(int argc, char *argv[]) {... argc... numero di argomenti con cui il programma è stato lanciato più 1 argv... vettore di argc stringhe che compongono la riga di comando argv[0] è il nome del programma stesso argv[1],, argv[argc-1] sono gli argomenti (separati da spazio) ogni argv[i] è una stringa (terminata da \0 ) argc e argv vengono inizializzati dal sistema operativo (le stringhe di argv sono allocate dinamicamente) Fondamenti di Informatica 3 Parametri del main(): esempio 1 Stampa del numero di argomenti ricevuti da linea di comando /* File: argnum.c */ #include <stdio.h> int main(int argc, char *argv[]) { printf("ho ricevuto %d argomenti\n", argc-1); return 0; Compilando si ottiene argnum.exe, che può essere eseguito: > argnum primo secondo terzo > Ho ricevuto 3 argomenti Fondamenti di Informatica 4 2
Parametri del main(): esempio 2 Stampa degli argomenti ricevuti da linea di comando /* File: argprint.c */ #include <stdio.h> int main(int argc, char* argv[]) { int i; printf("ho ricevuto %d argomenti\n", argc-1); printf("questi argomenti sono:\n"); for (i = 1; i <= argc-1; i++) printf("%s\n", argv[i]); return 0; Compilando si ottiene argprint.exe, che può essere eseguito: > argprint primo secondo terzo > Ho ricevuto 3 argomenti > Questi argomenti sono: > primo > secondo > terzo Fondamenti di Informatica 5 Precompilazione 3
Precompilatore Il preprocessore C modifica il codice sorgente in base a "direttive delpreprocessore" inserite nel codice le direttive del preprocessore iniziano con il carattere '#' il preprocessore crea un nuovo "codice sorgente" che è il testo che viene effettivamente compilato questo "codice intermedio" normalmente non viene mandato in output è possibile forzare il compilatore a dare in output questo codice per vedere cosa produce Fondamenti di Informatica 7 Precompilazione La direttiva #include prevede due varianti: #include <nomefile> #include nomefile tale direttiva include il contenuto di un file il file incluso può contenere a sua volte direttive #include nella variante <nomefile> ricerca il file solo negli include delle librerie standard (/usr/include) nella variante nomefile il file nella directory corrente e quindi negli altri path. è possibile specificare path di ricerca aggiuntivi per gli include file tramite opzioni di compilazione Fondamenti di Informatica 8 4
Precompilazione La direttiva #define può essere usata per definire macro sostituzioni. #define text1 text2 chiede al preprocessore di trovare tutte le occorrenze di text1 e di sostituirle con text2. Esempio: #define MAX 1000 Si può usare per macro più complesse (con parametri): #define square(x) ((x)*(x)) La direttiva #undef rimuove la definizione di una certa macro. Fondamenti di Informatica 9 Precompilazione Le direttive #if, #elif, #else, #endif informano il preprocessore di compilare solo alcune parti del codice, al verificarsi o meno di opportune condizioni; possono essere utilizzate per creare codice più efficiente e più portabile: #if condition_1 source_block_1 #elif condition_2 source_block_2... #elif condition_n source_block_n #else default_source_block #endif Fondamenti di Informatica 10 5
Precompilazione E' possibile testare il valore di macro definite con #define Esempio #define ENGLAND 0 #define ITALY 1 #if ENGLAND # include "england.h" #elif ITALY # include "italy.h" #else # include "default.h" #endif Fondamenti di Informatica 11 Precompilazione E' possibile testare l esistenza, a prescindere dal suo valore di una macro con la direttiva #ifdef(macroname) / #ifndef(macroname) #ifdef(debug) some useful debug code #endif oppure con l espressione #if defined(macroname) #if defined(debug) some useful debug code #endif Fondamenti di Informatica 12 6
Precompilazione Uso tipico negli header files per prevenire la doppia inclusione, ad esempio nell header file myheader.h si può scrivere: #if!defined(_myheader_h_included_) #define _myheader_h_included_ contenuto dell header file.. #endif Fondamenti di Informatica 13 Struttura completa di un programma C Ambienti, Variabili globali, Variabili statiche, Dichiarazione e definizioni, Compilazione su più file 7
Struttura di un programma File: prova.c #include <stdio.h>... int m; int f(int); int g(int x){... /* ambiente locale a g */ main(){... /* ambiente locale del main */ int f(int x){... /* ambiente locale a f */ Direttive del preprocessore Dichiarazioni variabili globali e dichiarazioni funzioni (prototipi) Definizione di funzioni Definizione main Definizione di funzioni Fondamenti di Informatica 15 Struttura di un programma Il main è l unica parte obbligatoria Le direttive sono gestite dal preprocessore Le variabili globali sono visibili in tutti gli ambienti del programma Fondamenti di Informatica 16 8
Ambienti Esistono delle regole di visibilità per gli identificatori (nomi di variabili, di funzioni, costanti) che definiscono in quali parti del programma tali identificatori possono essere usati In un programma esistono diversi ambienti: Area globale Main Ogni singola funzione Ogni blocco (delimitato da { e ) Fondamenti di Informatica 17 Ambienti Ambiente globale Dichiarazioni e definizioni che compaiono a livello esterno ( livello di file ), cioè fuori da qualunque funzione. Ambiente locale Dichiarazioni e definizioni che compaiono all interno di una funzione (compresa la sua intestazione) Ambiente di un blocco Dichiarazioni e definizioni che compaiono in un blocco Le definizioni di funzioni non possono essere innestate in altre funzioni o blocchi. E invece possibile inserire dichiarazioni dentro a funzioni o blocchi. Fondamenti di Informatica 18 9
Regole di visibilità (richiami) Un identificatore non è visibile prima della sua dichiarazione Un identificatore definito in un ambiente è visibile in tutti gli ambienti in esso contenuti Se in un ambiente sono visibili due definizioni dello stesso identificatore, la definizione valida è quella dell ambiente più vicino al punto di utilizzo In ambienti diversi si può definire lo stesso identificatore per denotare due oggetti diversi In ciascun ambiente un identificatore può essere definito una sola volta Fondamenti di Informatica 19 Visibilità variabili: considerazioni In C, le variabili della funzione main vivono quanto la funzione main ma non sono visibili in qualunque procedura o funzione, perché il main è una funzione come le altre visibilità delle variabili limitata al blocco in cui sono definite necessità di passarle sempre come parametro alle funzioni che devono usarle Fondamenti di Informatica 20 10
Variabili globali: considerazioni Il C consente di definire delle VARIABILI GLOBALI, ossia delle entità definite fuori da qualunque funzione ( a livello di file ) Esempio: int k; /* VARIABILE GLOBALE */ void modify(void){ k++; /* k è visibile qui... */ main(){ modify(); k *= 5; /*...e anche qui! */ Il C si appoggia al concetto di FILE (fornito dal sistema operativo) come contenitore su cui definire le visibilità k è visibile nel file dove è definita, dal punto in cui è definita in avanti (non sopra!) Fondamenti di Informatica 21 Variabili globali e visibilità int k = 5; /* VARIABILE GLOBALE */ int getvalue(void); main(){ ++k; /* ora k vale 6 */ k = getvalue(); /* valore finale di k = 8 */ int n = 8; /* VARIABILE GLOBALE */ int getvalue(void){ return n; la variabile globale n non è visibile nel main solo getvalue può operare su di essa protezione tuttavia, k e n hanno lo stesso tempo di vita, pari a quello dell intero programma Fondamenti di Informatica 22 11
Variabili globali e visibilità a differenza delle variabili locali alle funzioni (main inclusa!), le variabili globali sono inizializzate automaticamente a zero, se non diversamente specificato int k; /* VARIABILE GLOBALE = 0 */ ma le inizializzazioni implicite non sono leggibili! preferire sempre le inizializzazioni esplicite! int k = 0; /* VARIABILE GLOBALE */ tanto, le inizializzazioni di variabili globali sono calcolate a compile time nessuna inefficienza! Fondamenti di Informatica 23 Variabili globali e visibilità Una variabile globale può essere nascosta da una variabile locale omonima int k = 5; /* VARIABILE GLOBALE */ int getvalue(void); main(){ ++k; /* ora k vale 6 */ k = getvalue(); /* valore finale di k = 8 */ int n = 7; /* VARIABILE GLOBALE */ int getvalue(void){ int k = 1; return k+n; /* è la variabile k locale */ la variabile k definita entro getvalue nasconde quella globale con lo stesso nome dentro getvalue diventa impossibile accedere alla variabile k globale NB: ovviamente, questa getvalue potrebbe evitare di definire k, per l uso che ne fa... Fondamenti di Informatica 24 12
Visibilità variabili locali e globali Riassumendo: Variabili locali e parametri formali sono visibili SOLO all'interno della funzione di cui fanno parte Viceversa, le variabili globali sono visibili ANCHE dentro le funzioni scritte fisicamente di seguito alla definizione della variabile globale stessa, a meno che non siano mascherate da variabili locali omonime Fondamenti di Informatica 25 Variabili globali vs. passaggio dei parametri Le variabili globali rappresentano un altro modo (oltre al classico meccanismo di passaggio dei parametri) con cui far interagire tra loro diversi componenti software: Permettono di restituire in modo diretto i risultati al chiamante. Evitano lunghe liste di parametri (da copiare se passati per valore). ma Fondamenti di Informatica 26 13
Variabili globali vs. passaggio dei parametri ma Il componente non è più autonomo: il suo funzionamento dipende dall esistenza, FUORI DA ESSO, di certi simboli con un certo nome Generalità, riusabilità e portabilità diminuiscono Interazione tra i diversi componenti meno chiara Il componente presenta effetti collaterali Si chiama effetto collaterale (side effect) provocato dalla attivazione di una funzione una qualunque modifica delle variabili non locali a tale funzione Se una funzione ha effetti collaterali, non è più tale in senso matematico. L uso di variabili globali rappresenta dunque una scelta di progetto, legata alla rappresentazione del dominio applicativo Fondamenti di Informatica 27 Tempo di vita delle variabili È legato al luogo di definizione della variabile. Variabili globali: sono allocate all'inizio del programma e vengono distrutte quando il programma termina. Variabili locali e parametri formali delle funzioni: sono allocati ogni volta che si invoca la funzione e distrutti al termine della funzione Non c'è correlazione tra i valori che variabili locali e parametri formali di una funzione assumono nelle diverse attivazioni della funzione stessa. Il loro tempo di vita è quindi pari alla durata dell attivazione della funzione in cui compare la definizione della variabile. Fondamenti di Informatica 28 14
Ancora sulle variabili globali Le variabili globali possono servire anche per costruire componenti software dotati di stato (vedremo più avanti un modo più sicuro per tale realizzazione) Esempio Si vuole costruire una funzione int prossimodispari(void) che restituisca via via il "successivo" dispari Per fare questo, tale componente deve tenere memoria al suo interno dell'ultimo valore fornito Dunque, non è una funzione in senso matematico, perché, interrogata più volte, dà ogni volta una risposta diversa Fondamenti di Informatica 29 Esempio Si utilizza una variabile globale per mantenere lo stato: int ultimovalore = 0; int prossimodispari(void){ return 1 + 2 * ultimovalore++; /* (sfrutta il fatto che i dispari hanno la forma 2k+1) */ int main(){ int i, a; for (i=0; i<30; i++){ a = prossimodispari(); printf( numero dispari: %d\n, a); Fondamenti di Informatica 30 15
Ambiente globale e protezione Il fatto che le variabili globali in C siano potenzialmente visibili in tutti i file dell applicazione pone dei problemi di protezione: Cosa succede se un componente dell'applicazione altera una variabile globale essenziale? Nel nostro esempio: cosa succede se qualcuno altera ultimovalore? Fondamenti di Informatica 31 Ambiente globale e protezione Potrebbe essere utile avere variabili: globali nel senso di permanenti come tempo di vita (per poter costruire componenti dotati di stato)... ma anche protette, nel senso che non tutti possano accedervi Variabili statiche Fondamenti di Informatica 32 16
Variabili statiche (static) In C, una variabile può essere dichiarata static: è permanente come tempo di vita ma è protetta, in quanto è visibile solo entro il suo scope di definizione Nel caso di una variabile statica globale, ogni tentativo di accedervi da altri file, tramite dichiarazioni extern, sarà impedito dal compilatore Fondamenti di Informatica 33 Esempio rivisitato Realizzazione alternativa del componente precedente static int ultimovalore = 0; int prossimodispari(void){ return 1 + 2 * ultimovalore++; /* (sfrutta il fatto che i dispari hanno la forma 2k+1) */ int main(){ int i, a; for (i=0; i<30; i++){ a = prossimodispari(); printf( numero dispari: %d\n, a); Fondamenti di Informatica 34 17
Esempio rivisitato In che senso la variabile static è "protetta"? La variabile ultimovalore èora inaccessibile dall'esterno di questo file: l unico modo di accedervi è tramite prossimodispari() Ma in questo esempio, essendo ultimovalore anche variabile globale, è ancora modificabile dalle altre funzioni Fondamenti di Informatica 35 Variabili statiche dentro a funzioni Una variabile statica può essere definita anche dentro a una funzione. Così: è comunque protetta, in quanto visibile solo dentro alla funzione (come ogni variabile locale) ma è anche permanente, in quanto il suo tempo di vita diventa quello dell intero programma Consente di costruire componenti (funzioni) dotati di stato, ma indipendenti dall'esterno Fondamenti di Informatica 36 18
Esempio rivisitato (2) Ulteriore realizzazione alternativa del componente int prossimodispari(void){ static int ultimovalore = 0; return 1 + 2 * ultimovalore++; /* (sfrutta il fatto che i dispari hanno la forma 2k+1) */ int main(){ int i, a; for (i=0; i<30; i++){ a = prossimodispari(); printf( numero dispari: %d\n, a); Fondamenti di Informatica 37 Variabili statiche: conclusioni Quindi, la parola chiave static ha sempre e comunque due effetti rende l oggetto permanente rende l oggetto protetto (invisibile fuori dal suo scope di definizione) ma se ne vede sempre uno solo per volta una variabile definita in una funzione, che è comunque protetta, viene resa permanente una variabile globale, già di per sé permanente, viene resa protetta Fondamenti di Informatica 38 19
Funzioni come componenti software Una funzione è un componente software (servitore) riutilizzabile: costituisce una unità di traduzione: può essere definita in un unico file e compilata per proprio conto pronta per essere usata da chiunque Fondamenti di Informatica 39 Funzioni come componenti software Per usare tale componente software, il cliente: Non ha bisogno di sapere come è fatto (cioè di conoscerne la definizione) Deve conoscerne solo l interfaccia: Nome Numero e tipo dei parametri Tipo del risultato Fondamenti di Informatica 40 20
Dichiarazione di funzione La dichiarazione (o prototipo) di una funzione è costituita dalla sola interfaccia, senza corpo (sostituito da un ;) <dichiarazione-di-funzione> ::= <tipovalore> <nome>(<parametri>) ; Fondamenti di Informatica 41 Dichiarazione di funzione Dunque, per usare una funzione non occorre conoscere tutta la definizione basta conoscere la dichiarazione, perché essa specifica proprio il contratto di servizio Fondamenti di Informatica 42 21
Dichiarazione di funzione La definizione di una funzione costituisce l effettiva realizzazione del componente: dice come è fatto il componente La dichiarazione specifica il contratto di servizio fra cliente e servitore, esprimendo le proprietà essenziali della funzione. dice come si usa il componente Per usare una funzione non e necessario sapere come è fatta, anzi è controproducente Fondamenti di Informatica 43 Dichiarazione di funzione La dichiarazione specifica: il nome della funzione numero e tipo dei parametri (non necessariamente il nome) il tipo del risultato interfaccia Il nome avrebbe significato solo nell environment della funzione che qui non c è Fondamenti di Informatica 44 22
Dichiarazione vs. Definizione La definizione di una funzione costituisce l effettiva realizzazione del componente: Non può essere duplicata Ogni applicazione deve contenere una e una sola definizione per ogni funzione utilizzata La compilazione della definizione genera il codice macchina che verrà eseguito ogni volta che la funzione verrà chiamata. Fondamenti di Informatica 45 Dichiarazione vs. Definizione La dichiarazione di una funzione costituisce solo una specifica delle proprietà del componente: Può essere duplicata senza danni Una applicazione può contenerne più di una La compilazione di una dichiarazione non genera codice macchina Fondamenti di Informatica 46 23
Dichiarazione vs. Definizione La definizione è molto più di una dichiarazione definizione = dichiarazione + corpo La definizione funge anche da dichiarazione (ma non viceversa) Fondamenti di Informatica 47 Funzioni e file Un programma C è, in prima battuta, una collezione di funzioni una delle quali è il main Il testo del programma deve essere scritto in uno o più file di testo il file è un concetto del sistema operativo, non del linguaggio C Quali regole osservare? Fondamenti di Informatica 48 24
Funzioni e file Il main può essere scritto dove si vuole nel file viene chiamato dal sistema operativo, il quale sa come identificarlo Una funzione, invece, deve rispettare una regola fondamentale di visibilità prima che qualcuno possa chiamarla, la funzione deve essere stata dichiarata altrimenti, si ha errore di compilazione. Fondamenti di Informatica 49 Esempio scorretto (singolo file) File max.c int main() { int m; m = max(5, 8); NOTA: All atto della chiamata, max non è ancora stata definita Errore di compilazione int max (int x, int y ){ if (x>y) return x ; else return y; Fondamenti di Informatica 50 25
Esempio corretto (singolo file) File max.c int max (int x, int y ){ if (x>y) return x ; else return y; NOTA: Prima definisco max poi la uso int main() { int m; m = max(5, 8); Fondamenti di Informatica 51 Esempio corretto (singolo file) File max.c int max (int, int); int main() { int m; m = max(5, 8); prototipo OPPURE: Prima dichiaro max tramite un prototipo, poi la uso e dopo la definisco int max (int x, int y ){ if (x>y) return x ; else return y; Fondamenti di Informatica 52 26
Altro esempio scorretto File prova.c int f(int x){ if (x > 0) return g(x); else return x; ATTENZIONE: f chiama g e g chiama f Quale definisco prima? int g(int x) { return f(x-2); int main() { int y = f(3); Fondamenti di Informatica 53 Esempio corretto (utilità dei prototipi) int g(int); /* prototipo di g */ int f(int x){ if (x > 0) return g(x); else return x; int g(int x) { return f(x-2); int main() { int y = f(3); Fondamenti di Informatica 54 27
Funzioni e file Regola di visibilità prima che qualcuno possa chiamarla, la funzione deve essere stata dichiarata altrimenti, si ha errore di compilazione. Caso particolare: se la definizione funge anche da dichiarazione, la regola è rispettata se la definizione appare prima della chiamata Fondamenti di Informatica 55 Progetti su più file Una applicazione complessa non può essere sviluppata in un unico file: sarebbe ingestibile! Deve necessariamente essere strutturata su più file sorgente compilabili separatamente da fondere poi insieme per costruire l applicazione Fondamenti di Informatica 56 28
Progetti su più file Per strutturare una applicazione su più file sorgente, occorre che ogni file possa essere compilato separatamente dagli altri Poi, i singoli componenti così ottenuti saranno legati (dal linker) per costruire l applicazione Affinchè un file possa essere compilato singolarmente, tutte le funzioni usate devono essere dichiarate prima dell uso - non necessariamente definite! Fondamenti di Informatica 57 Esempio su due file File prog.c int max (int, int); int main() { int m; m = max(5, 8); File max.c Dichiarazione della funzione Chiamata della funzione Definizione della funzione int max (int x, int y ){ if (x>y) return x ; else return y; Fondamenti di Informatica 58 29
Costruzione di una applicazione a partire da più file 1) Compilare i singoli file che costituiscono l applicazione File sorgente: estensione.c File oggetto: estensione.o o.obj f1.c compilatore f1.obj f2.c compilatore f2.obj f3.c compilatore f3.obj Fondamenti di Informatica 59 Costruzione di una applicazione a partire da più file 2) Collegare i file oggetto fra loro e con le librerie di sistema: File oggetto: estensione.o o.obj File eseguibile: estensione.exe o nessuna f1.obj f2.obj linker prog.exe f3.obj Fondamenti di Informatica 60 30
Compilazione di una applicazione Perché la costruzione vada a buon fine: ogni funzione deve essere definita una e una sola volta in uno e uno solo dei file sorgente se la definizione manca, si ha errore di linking ogni cliente che usa una funzione deve incorporare la dichiarazione opportuna se la dichiarazione manca, si ha errore di compilazione nel file del cliente (..forse...!!) Fondamenti di Informatica 61 Header file Per automatizzare la gestione delle dichiarazioni, si introduce il concetto di header file (file di intestazione) contenente tutte le dichiarazioni relative alle funzioni definite nel componente software medesimo scopo: evitare ai clienti di dover trascrivere riga per riga le dichiarazioni necessarie basterà quindi includere l header file tramite una direttiva #include Fondamenti di Informatica 62 31
Header file Il file di intestazione (header) ha estensione.h ha (per convenzione) nome uguale al file.c di cui fornisce le dichiarazioni Ad esempio: se la funzione max è definita nel file max.c il corrispondente header file, che i clienti potranno includere per usare la funzione max, dovrebbe chiamarsi max.h Fondamenti di Informatica 63 Esempio (singolo file) Singolo file int max (int x, int y ){ if (x>y) return x ; else return y; int main() { int m; m = max(5, 8); Fondamenti di Informatica 64 32
Esempio (due file separati) File prog.c (cliente) int max (int, int); int main() { int m; m = max(5, 8); File max.c (servitore) int max (int x, int y ){ if (x>y) return x ; else return y; Fondamenti di Informatica 65 Esempio (due file separati + header) Per includere automaticamente la dichiarazione occorre introdurre un file header File prog.c (cliente) #include max.h int main() { int m; m = max(5, 8); File max.h (header) int max(int, int); Il file max.c rimane invariato! Fondamenti di Informatica 66 33
Riassumendo Convenzione: Se un componente è definito in xyz.c Il file header che lo dichiara, che i clienti dovranno includere, si chiama xyz.h Fondamenti di Informatica 67 Riassumendo struttura dell esempio Struttura finale dell applicazione: un main definito in prog.c una funzione definita in max.c un file header max.h incluso da prog.c max.h prog.c max.c Fondamenti di Informatica 68 34
File header Esistono due formati per l inclusione degli header: #include <libreria.h> include l header di una libreria di sistema il sistema sa già dove trovarlo #include "miofile.h" include uno header scritto da noi occorre indicare dove reperirlo (attenzione al formato dei percorsi..!!) Fondamenti di Informatica 69 35