Makefile e opzioni compilatore C Massimo Bernaschi massimo.bernaschi@cnr.it materiale originale di David A. Gaitros
Introduzione Il comando Unix make è uno degli strumenc disegnac da S. I. Fieldman degli AT&T Bell labs nel (circa) 1977. Ne esistono ormai molte versioni. L obieqvo originale di make è permerere agli sviluppatori di compilare efficientemente grandi programmi composc da molc componenc in maniera semplice. Si possono ovviamente inserire tuq i comandi necessari per la compilazione in uno script ma in questo modo vengono ricompilac ogni volta tuq i componenc. Il comando make permere di compilare solo i moduli che sono cambiac e quelli che dipendono da quesc.
Come funziona? Quando si lancia il comando make il sistema operacvo cerca un file con il nome makefile o Makefile. È comunque possibile specificare un nome diverso con l opzione -f. Il makefile concene una serie di dire2ve che dicono al comando make come compilare il programma ed in quale ordine. Ad ogni file è associata una lista di altri file da cui dipende. La lista è chiamata linea di dipendenze. Se uno qualsiasi dei file contenuc nella lista è stato modificato, il comando make esegue il comando che è immediatamente soro la linea di dipendenza.
Come funziona? Il comando make è ricorsivo. Per esempio, anche se un solo componente da cui dipende una catena di moduli viene cambiato, potrebbe comunque essere eseguita la ricompilazione (o almeno il re-link) di tuq i moduli. Quando il comando finisce la scansione del file, esegue un secondo controllo per assicurarsi che ture le dipendenze siano state risolte.
Un esempio (semplice) hello: main.o factorial.o hello.o g++ main.o factorial.o hello.o -o hello main.o: main.cpp g++ -c main.cpp factorial.o: factorial.cpp g++ -c factorial.cpp hello.o: hello.cpp g++ -c hello.cpp clean: rm -rf *o hello
Il corrispondente albero hello main.o factorial.o hello.o factorial.o factorial.cpp hello.cpp main.cpp clean
ComponenC di un Makefile CommenC Regole Linee di dipendenza Linee per l interprete di comandi (shell) Macro Regole di inferenza
CommenC Un commento viene indicato dal cararere #. TuRo ciò che viene dopo viene ignorato fino a quando non viene rilevato il cararere di fine linea. I commenc possono iniziare in qualunque punto. Esempi # # This is a comment projecte.exe : main.obj io.obj # this is also a comment.
Regole Le regole dicono a make quando e cosa fare per creare un file. Il formato è il seguente: Una regola deve avere un linea di dipendenze e può avere una linea di azione o un comando di shell dopo di essa. La linea di azione è eseguita se la linea di dipendenza non è aggiornata. Esempio: hello.o: hello.cpp g++ -c hello.cpp hello.o è un modulo che richiede hello.cpp come codice sorgente. Se la data di ulcma modifica di hello.cpp è più recente di hello.o, allora viene eseguita la linea successiva ed hello.cpp viene ricompilato.. Le due linee unite definiscono una regola
Linee di dipendenza Le linee con un : sono dere linee di dipendenza. Alla sinistra ci sono le dipendenze Alla destra ci sono i sorgenc necessari per costruire le dipendenze. Quando si lancia il comando make, vengono controllate la data e l ora in cui il target (le dipendenze) è stato creato e confrontate con quelle dei sorgenc necessari per costruirle. Se uno qualsiasi ha una data più recente, allora viene eseguita la linea comandi dopo la linea di dipendenza. Il processo di make è ricorsivo nel senso che controlla ture le dipendenze per essere sicuri che turo sia aggiornato prima di completare il processo di costruzione dei target. È importante che tu6e le dipendenze siano piazzate in ordine discendente nel makefile. Alcuni file possono avere le stesse dipendenze. Supponiamo, ad esempio che due file dipendano dal file bitvect.h. Possiamo avere la linea di dipendenza: main.o this.o: bitvect.h
Linee comandi (shell) Le linee indentate (devono avere un tab iniziale!) che seguono ogni linea di dipendenza sono dere linee di shell. Le linee di shell dicono a make come costruire il target. Un target può essere seguito da più linee comandi. Ogni linea deve essere preceduta da un tab. Dopo che ogni comando è eseguito, make controlla che non ci siano stac errori. Gli errori possono essere ignorac (opzione i) ma non è consigliabile.
Linee comandi (shell) Le linee di shell che ritornano un codice zero (0) sono state eseguite senza errori mentre un codice diverso da zero indica un errore. La prima linea comandi che ritorna un codice diverso da zero forza la terminazione del comando make che mostra l errore che ha generato la terminazione. Si può modificare questo comportamento inserendo un - di fronte al comando (ma non è consigliabile) Esempio: - gcc o my my.o mylib.o
Macro Viene dal Greco makros (significa grande ). In breve è un modo comparo per rappresentare qualcosa o un alias usato in the makefile Un stringa di carareri è associata con un altra (in genere) più lunga stringa Nel makefile, per espandere una macro, si deve inserire la stringa in $( ). La stringa viene espansa (per sosctuzione) durante l esecuzione del comando make.
Macro Esempi di macro: HOME = /home/corso/pds/spring17 CPP = $(HOME)/cpp TCPP = $(HOME)/tcpp PROJ =. INCL = -I $(PROJ) I$(CPP) I$(TCPP) Si possono definire macro anche sulla linea comandi Make HOME = /home/esame/aaa/finale Queste macro hanno la precedenza sulle corrispondenc definite nel makefile.
Regole di inferenza Le regole di inferenza sono un metodo per generalizzare il processo di costruzione. In sostanza è una sorta di notazione basata su wild card. Il cararere % è usato per indicare una wild card. Esempio: %.o : %.c $(CC) $(FLAGS) c $(.SOURCE) TuQ i.o files hanno dipendenze da tuq i %.c file dello stesso nome.
Variabili predefinite Sono disponibili variabili predefinite che fanno riferimento a parc specifiche delle regole target : dipendenze TAB commandi #comandi di shell eval.o : eval.c eval.h g++ -c eval.c $@ - Il nome del target della regola (eval.o). $< - Il nome della prima dipendenza (eval.c). $^ - I nomi di ture le dipendenze (eval.c eval.h). $? - I nomi di ture le dipendenze che sono più nuove del target
Phony target Phony target: Target che non hanno dipendenze. Sono usac solo come nomi per comandi da eseguire su richiesta. clean: rm $(OBJS) Per invocarlo: make clean Tipici phony target: all costruisce tuq i target di primo livello o.phony : all all: my_prog1 my_prog2.phony : clean clean: rm $(OBJS) clean cancella tuq i file che sono normalmente creac da make print stampa il listato dei sorgenc che sono stac modificac
DireQve Condizionali Le direqve condizionali sono: if ifeq ifneq ifdef ifndef TuQ devono essere chiusi da endif. È possibile uclizzare elif ed else. Esempio: libs_for_gcc = -lgnu normal_libs = ifeq ($(CC),gcc) else libs=$(libs_for_gcc) libs=$(normal_libs) endif #no tab at the beginning #no tab at the beginning
Esempio di makefile CC = gcc DIR = /home/massimo/corso/lib CFLAGS = -g -I$(DIR) -I. -c LFLAGS = -g opt: analysis.o flow.o io.o misc.o opt.o opts.o peephole.o regs.o vect.o $(CC) $(LFLAGS) -o opt analysis.o flow.o io.o misc.o opt.o opts.o peephole.o regs.o vect.o analysis.o: analysis.c analysis.h $(DIR)/misc.h $(DIR)/opt.h $(DIR)/vect.h $(CC) $(CFLAGS) analysis.c flow.o: $(DIR)/flow.c $(DIR)/flow.h $(DIR)/opt.h $(CC) $(CFLAGS) $(DIR)/flow.c io.o: $(DIR)/io.c $(DIR)/io.h analysis.h $(DIR)/misc.h $(DIR)/opt.h peephole.h $(DIR)/regs.h $(CC) $(CFLAGS) $(DIR)/io.c
misc.o: $(DIR)/misc.c $(DIR)/misc.h $(DIR)/opt.h $(CC) $(CFLAGS) $(DIR)/misc.c opt.o: $(DIR)/opt.c $(DIR)/opt.h $(CC) $(CFLAGS) $(DIR)/opt.c opts.o: opts.c $(DIR)/misc.h $(DIR)/regs.h $(DIR)/opt.h opts.h $(CC) $(CFLAGS) opts.c peephole.o: peephole.c $(DIR)/misc.h $(DIR)/regs.h $(DIR)/opt.h peephole.h $(CC) $(CFLAGS) peephole.c regs.o: $(DIR)/regs.c $(DIR)/regs.h $(DIR)/opt.h $(CC) $(CFLAGS) $(DIR)/regs.c vect.o: $(DIR)/vect.c $(DIR)/vect.h $(DIR)/opt.h $(CC) $(CFLAGS) $(DIR)/vect.c
Principali opzioni compilatore C -c: richiede la sola compilazione senza link -O: aqva l oqmizzazione (più livelli) -o name: crea un file con il nome name -g: include nell object file informazioni ucli durante il debugging -pg: include informazioni per il profiling -s: elimina i simboli (striping) -W: fornisce maggiori informazioni sul processo di compilazione (vari livelli)
La funzione rand() La funzione rand() della libreria C standard può essere uclizzata per generare numeri interi random nel range [0 RAND_MAX] (definito in stdlib.h. Per inizializzare il generatore random, si uclizza la srand(unsigned seed). Ovviamente partendo dallo stesso seme si rioqene esaramente la stessa sequenza di numeri pseudo-random.
Come cancellare un file La funzione UNIX #include <unistd.h> int unlink(const char *path) rimuove il link di path con la sua directory e decrementa il contatore nell inode. La funzione window equivalente è: BOOL DeleteFile(LPCTSTR lpfilename); ARenzione ai valori di ritorno! La funzione unix ritorna 0 in caso di successo Quella windows torna 1 in caso di successo