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 lapplicazione. PROGETTI STRUTTURATI SU PIÙ FILE
Una funzione è un componente software (servitore) riutilizzabile che costituisce una unità di traduzione: –può essere definita in un file a sé stante –e compilata per proprio conto –pronta per essere usata da chiunque FUNZIONI COME COMPONENTI SW
Per usare tale componente software, il cliente: non ha bisogno di sapere come è fatto (cioè, di conoscerne la definizione) deve conoscerne solo linterfaccia, ossia la dichiarazione. FUNZIONI COME COMPONENTI SW
int fact(int); main() { int y = fact(3); } int fact(int n) { return (n<=1) : 1 : n*fact(n-1); } DALLESEMPIO SU UN SOLO FILE... File prova1.c
int fact(int); main() { int y = fact(3); }... ALLESEMPIO SU DUE FILE Dichiarazione della funzione Uso (chiamata) File main.c int fact(int n) { return (n<=1) : 1 : n*fact(n-1); } Definizione della funzione File fact.c
1) Compilare i singoli file che costituiscono lapplicazione –File sorgente:estensione.c –File oggetto:estensione.o o.obj COMPILAZIONE DI UNAPPLICAZIONE f1.c f2.c f3.c f1.obj f2.obj f3.obj compilatore
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 COLLEGAMENTO DI UNAPPLICAZIONE prog.exe f1.obj f2.obj f3.obj linker
Perché la costruzione vada a buon fine: ogni funzione deve essere definita una e una sola volta in uno dei file sorgente –se la definizione manca, si ha errore di linking ogni cliente che usi una funzione deve incorporare la dichiarazione opportuna –se la dichiarazione manca, si ha errore di compilazione nel file del cliente (..forse...!!) COSTRUZIONE DI UNAPPLICAZIONE
Perché, esattamente, serve il linker? Il compilatore deve lasciare in bianco i riferimenti alle chiamate di funzione che non sono definite nel medesimo file Compito del linker è risolvere tali riferi- menti, riempiendo gli spazi bianchi con lindirizzo effettivo del codice della funzione. IL RUOLO DEL LINKER
Attivare il compilatore su ogni singolo file sorgente C:\PROVA> gcc -c fact.c C:\PROVA> gcc -c main.c Attivare il linker per unire i rispettivi file oggetto e le librerie di sistema C:\TMP> ld -o prog.exe fact.obj main.obj –lc... un lavoraccio! COSTRUZIONE MANUALE
Negli ambienti integrati, tutto ciò viene automatizzato Si predispone un progetto che contenga tutti i file sorgente (.c) necessari Si costruisce lapplicazione normalmente (Make / F9) COSTRUZIONE NEGLI AMBIENTI INTEGRATI
PROGETTI SU PIÙ FILE IN DJGPP/RHide Dalla finestra Add item che appare si selezionano tutti i file sorgente (.c) da inserire nel progetto.
PROGETTI SU PIÙ FILE IN TURBO C Dalla finestra Add to Project List si selezionano tutti i file sorgente (.c) da inserire nel progetto.
Anche una variabile globale è un componente software –in particolare, un componente che fornisce dati (non comportamenti) come tale, costituisce una unità di traduzione: –può essere definita in un file a sé stante –e compilata per proprio conto –pronta per essere usata da chiunque VARIABILI GLOBALI come COMPONENTI SW
Il cliente deve incorporare la dichia- razione della variabile globale che intende usare extern int trentadue; Uno dei file sorgente nel progetto dovrà contenere la definizione (ed eventualmente linizializzazione) della variabile globale int trentadue = 10; VARIABILI GLOBALI come COMPONENTI SW
float fahrToCelsius(float f); main() { float c = fahrToCelsius(86); } extern int trentadue; float fahrToCelsius(float f) { return 5.0/9 * (f-trentadue); } int trentadue = 32; DALLESEMPIO SU UN SOLO FILE... File prova4.c
float fahrToCelsius(float f); main() { float c = fahrToCelsius(86); }... ALLESEMPIO SU TRE FILE File main.c extern int trentadue; float fahrToCelsius(float f) { return 5.0/9 * (f-trentadue); } File f2c.c int trentadue = 32; File 32.c
ARCHITETTURA DELLAPPLICAZIONE Chi usa cosa –Il main usa la funzione fahrToCelsius –La funzione fahrToCelsius usa la variabile globale trentadue File 32.c File main.c File f2c.c usa
Perché queste architetture funzionino, ogni cliente deve contenere le dichiara- zioni di tutte le funzioni (e le variabili globali) che usa In una applicazione complessa, fatta di decine di file, non è pensabile che questo venga fatto a mano, mediante copia & incolla file per file: OCCORRE UN AUTOMATISMO GESTIRE PROGETTI COMPLESSI
Quando si compila un programma C, il compilatore non riceve esattamente il testo del programma da noi fornito riceve una versione "riveduta e corretta" da "qualcuno" che si interpone tra noi e il compilatore vero e proprio: il PRE-PROCESSORE C IL PRE-PROCESSORE C File sorgente.c File modificato preproc. al compi- latore C
Il pre-processore modifica il testo del programma prima che esso raggiunga il compilatore C vero e proprio. Così, può svolgere alcune utili funzioni di manipolazione del testo al nostro posto IL PRE-PROCESSORE C File sorgente.c File modificato preproc. al compi- latore C un testo un altro testo
Il pre-processore non è un compilatore C non conosce il linguaggio C non può interpretare le istruzioni C, né controllarne la correttezza non sa cosa fa: è solo un automa che agisce sul testo del programma potrebbe manipolare qualunque testo, non solo programmi C programmi Pascal, poesie, lettere commerciali, lettere damore... IL PRE-PROCESSORE C
Cosa può fare? includere altre porzioni di testo, prese da altri file effettuare ricerche e sostituzioni (più o meno sofisticate) sul testo inserire o sopprimere parti del testo a seconda del verificarsi di certe condizioni da noi specificate. IL PRE-PROCESSORE C
Come si controlla il suo funzionamento? mediante direttive inserite nel testo. Attenzione: le direttive non sono istruzioni C non ne hanno neanche la sintassi! infatti, non sono destinate al compilatore, che non le vedrà mai vengono soppresse dal pre-processore dopo essere state da esso interpretate. IL PRE-PROCESSORE C
Principali direttive includere altre porzioni di testo #include nomefile effettuare ricerche e sostituzioni #define testo1 testo2 inserire o sopprimere parti del testo #ifdef condizione …testo... #endif DIRETTIVE AL PRE-PROCESSORE C
Sintassi: #include #include miofile.h Effetto: include il contenuto del file specificato esattamente nella posizione in cui si trova la direttiva stessa. LA DIRETTIVA #include
ESEMPIO #include "f2c.h" main() { float c = fahrToCelsius(86); } File main.c float fahrToCelsius(float f); File f2c.h Situazione iniziale:
ESEMPIO float fahrToCelsius(float f); main() { float c = fahrToCelsius(86); } File main.c modificato dal pre-processore Situazione dopo il pre-processing: Dopo che il pre-processing è avvenuto, il file.h non serve più.
SE SIETE CURIOSI......il pre-processing si può vedere: C:\PROVA> gcc -E main.c -E effettua solo il pre-processing C:\PROVA> gcc -C -P -E main.c -P non numera le righe (che di solito vengono numerate) -C non toglie i commenti (che di solito vengono tolti)
SE SIETE ANCORA PIÙ CURIOSI si può vedere perfino il programma tradotto in assembler: C:\PROVA> gcc -S main.c -S crea un file main.S con lassembler che sarà generato _main: pushl %ebp call _fahrToCelsius movl %esp,%ebp addl $4,%esp subl $4,%esp fstps -4(%ebp) call ___main leave pushl $0x42ac0000 ret
FILE HEADER float fahrToCelsius(float f); File f2c.h Per automatizzare lincorporazione delle dichiarazioni necessarie, si usa predisporre uno o più file di intestazione (header) estensione.h destinati a essere inclusi dai clienti (file.c ) mediante direttive #include.
FILE HEADER Convenzione: se un componente è definito in xxx.c il file header corrispondente, che i clienti dovranno includere, si chiama xxx.h #include "f2c.h" main() { float c = fahrToCelsius(86);} File main.c (cliente) float fahrToCelsius(float f){...} File f2c.c (servitore)
FILE HEADER Attenzione: i file header non sono parte del progetto sono usati dai file sorgente Due formati: #include include lheader di una libreria di sistema il sistema sa già dove trovarlo #include miofile.h include uno header scritto da noi occorre indicare dove reperirlo
FILE HEADER - CAUTELE DUSO Un file header deve contenere solo dichiarazioni ! Se contiene anche solo una definizione possono crearsi situazioni di errore (rischio di definizioni duplicate).
FILE HEADER - CAUTELE DUSO Esempio un main usa le funzioni f1 e f2 sia f1 sia f2 usano la funzione f lo header di f contiene la definizione invece della dichiarazione main.c #include f1.h #include f2.h... È un falso header ! f1.c #include f.h... f2.c #include f.h... f.h int f(int x) { return … ; }
FILE HEADER - CAUTELE DUSO La compilazione fila liscia: f1.c e f2.c si compilano senza problemi Attenzione !! - ognuno include una definizione di f il main si compila senza problemi... main.c #include f1.h #include f2.h... f1.c #include f.h... f2.c #include f.h... gcc
FILE HEADER - CAUTELE DUSO Ma il linker dà errore in fase di collegamento Infatti, la definizione di f risulta due volte il relativo codice è duplicato!! main.of1.of2.o link