Introduzione al C
Davide Gadia
Programmazione Grafica aa2006/2007 2
I linguaggi di programmazione
Linguaggi Compilati
- La scrittura del codice può avvenire tramite un editor di testo, oppure attraverso dei tool IDE.
- Una volta creato il codice viene controllato e compilato generando un file in codice macchina.
- Il file in codice macchina cosi realizzato può essere eseguito.
Vantaggi :: prestazioni ottime, estendibile
Svantaggi :: comprensione non immediata
Linguaggi Interpretati
- La scrittura del codice può avvenire tramite un editor di testo, oppure attraverso dei tool IDE.
- Una volta creato il codice esso viene interpretato ed eseguito senza essere tradotto in linguaggio macchina.
Vantaggi :: alta portabilità, facilità di programmazione
Svantaggi :: lentezza di esecuzione, poco espandibile/versatile
Programmazione Grafica aa2006/2007 3
Il linguaggio C
Caratteristiche
- Linguaggio Compilato- Dimensioni codice/eseguibile ridotte- Efficienza di esecuzione dei programmi- Multi-platform- E’ un linguaggio di alto/basso livello- Allocazione dinamica della memoria- Estensione ad oggetti -> C++
Perchè lo abbiamo scelto?
- Gestione a tutti i livelli di ogni periferica della macchina- Presenza di librerie per quasi ogni tipo di problema- SDK sviluppati in C/C++- In PG, la programmazione più spinta e a basso livello viene eseguita generando codice C/C++- Documentazione molto ampia per ogni tipo di problematica- Il core del codice se ben programmato può essere portato su qualsiasi piattaforma
Programmazione Grafica aa2006/2007 4
Workflow di un programma C
- Stesura del codice- Compilazione- Linking- Debugging- Esecuzione
Compilazione
Debug
EsecuzioneLinkingCodiceSorgente
Workflow
Programmazione Grafica aa2006/2007 5
Compilazione e Linking
Compilazione Statica
- elaborazione del file sorgente dal parte del preprocessore, al quale si possono dare delle direttive- generazione del codice assembler- generazione del file oggetto- linking dei vari file oggetto per ottenere il file eseguibile
Linking Dinamico
- verifica che i simboli/funzioni usati nel codice siano definiti all'interno delle librerie- viene solo trascritta la dipendenza (nome e versione) all'interno dell'eseguibile
I vantaggi sono- minori dimensioni effettive dell'eseguibile su disco e in memoria- superiore o pari velocità di caricamento ed esecuzione- aggiornando con versioni più ottimizzate delle lib dinamiche si ottimizzano automaticamente tutti gli eseguibili
Programmazione Grafica aa2006/2007 6
Struttura di un programma C
Struttura
- Comandi di prepocessore
Serie di comandi che permettono di comandare il compilatore secondo le proprie esigenze.
- Definizione dei tipiAd ogni variabile deve essere associato un tipo, è una regola per ogni LDP.
- Prototipi (Dichiarazione)Dichiarazione dei tipi delle funzioni e delle variabili passate alle
funzioni
- Variabili (Globali)Variabili disponibili e allocate durante l’intera esecuzione del programma.
- Funzioni (Implementazione)
Implementazione del core delle funzioni
- Main FunctionIl Main la prima funzione che fa da start (init) ad un programma C.
Programmazione Grafica aa2006/2007 7
Primo programma
Step 1 :: Stesura del codice
Il vostro codice C dovrà contenere una e una sola funzione
main().
int main(void){
return 0;}
1
int main(void){
printf(“Corso di PG\n”);return(0);
} 2
Evolviamo il nostro codice sopra scritto in una forma poco più
complessa.
Programmazione Grafica aa2006/2007 8
Primo programma
Step 2 :: Compilazione
A seconda su quale SO volete creare il vostro codice sorgente, esistono una serie di compilatori per ogni
tipo di piattaforma. Noi useremo Visual C++. Ma in prima istanza impareremo come utilizzare il compilatore
da linea di comando in modo da capire la corretta sequenza delle operazioni che spesso nei grossi tool di
programmazione è trasparente all’utente.Nota: prima di iniziare dobbiamo settare le variabili di ambiente in modo tale da essere in grado di eseguire il nostro compilatore in qualunque posizione si trovi il nostro sorgente.- settare la variabile PATH con la dir C:\Programmi\Microsoft Visual Studio\VC98\Bin\ di Visual C++
Salviamo in un file “sorgente02.c” il nostro sorgente (codice 2) che abbiamo scritto in precedenza.
Apriamo un prompt di MS-Dos e andiamo nella directory in cui è contenuto il file appena salvato.Eseguiamo il seguente comando in dosc:\>...\ cl /c sorgente02.c
Dove cl.exe è il nostro compilatore visual c.
Il comando appena eseguito ha generato il sorgente assembler. Il passaggio successivo è la fase di linking.
Programmazione Grafica aa2006/2007 9
Primo programma
Step 3 :: Linking
Dal nostro file sorgente02.obj ora dobbiamo linkarlo.Useremo sempre il nostro compilatiore ma questa volta con il flag “/l” e l’elenco delle
librerie che ci servono.
Nel nostro caso nessuna libreria è stata usata quindi viene generato il sorgente senza nessun problema.
Per eseguire il programma basta scrivere nel prompt di MS-Dos il nome del file.
Eseguiamo il seguente comando in dosc:\>...\ cl sorgente02.c /l
Dopo il comando viene generato il file sorgente02.exe
Ottenete come output nella vostra console:Corso di PG
Con questi semplici passaggi abbiamo voluto far capire come deve essere la procedura di compilazione
per un programma C. La cosa importante da ricordare è che qualunque applicazione voi usiate per
compilare i vostri progetti essa eseguirà sempre queste procedure nella sequenza illustrata.
Programmazione Grafica aa2006/2007 10
Esempio
Parte 01 :: Codice sorgente
// Direttive per il preprocessore// #include <stdio.h> // Pre-processore#define COSTANTE_01 30 // Pre-processore#define COSTANTE_02 50 // Pre-processore// ..............................................
// Definizione dei tipi//typedef int intVector[3];// ......................
// dichiarazione funzioni//void printValues (intVector vec);int sumVectorValues (intVector vec, int times);// ............................................
Programmazione Grafica aa2006/2007 11
Esempio
Parte 02 :: typedef
// Definizione dei tipi//typedef int intVector[3];// ......................
La parola chiave typedef viene utilizzata per assegnare un alias ad un qualsiasi tipo fondamentale oppure derivato (introdotto dall'utente e derivato dai tipi fondamentali). Con typedef non si definisce un nuovotipo, ma si introduce un nome che corrisponde a un tipo definito. La sintassi è la seguente:
typedef nome_tipo nuovo_nome_tipo;
Programmazione Grafica aa2006/2007 12
Esempio
Parte 03 :: Codice sorgente
// implementazione funzioni//void printValues(intVector vec) {
... Implementazione ...}int sumVectorValues(intVector vec, int times) {
... Implementazione ...}// Main functionint main(int argc, char *argv[]){
intVector myVector;// variabile di tipo intVector
... Implementazione ...
return 0;// valore di ritorno
}
Programmazione Grafica aa2006/2007 13
EsempioParte 04 :: printValues
La funzione riceve in ingresso come parametro una variabile di tipo intVector.
L’unica cosa che succede all’interno della funzione è che i valori della funzione vengono stampati su
standard output tramite la funzione printf.
// implementazione funzioni//void printValues(intVector vec) {
// Funzione per la gestione dell'output su console
printf("Vec :: [%d,%d,%d]",vec[0],vec[1],vec[2]);}
Programmazione Grafica aa2006/2007 14
Esempio
La funzione stampa una serie di argomenti secondo uno schema di formattazione.
Format: Stringa che contiene del testo da essere stampatoHa il seguente prototipo: %[flags][width][.precision][modifiers]type
argument(s): Parametri opzionali che contengono i valori/dati da essere visualizzati secondo la formattazione prescelta.
Return Value:in caso di errore ritorna un numero negativo altrimenti ritorna il numero di caratteri stampati.
Es: printf ("Some different radixes: %d %x %o %#x %#o \n", 100, 100, 100, 100, 100);
printf ("floats: %4.2f %+.0e %E \n", 3.1416, 3.1416, 3.1416);
Some different radixes: 100 64 144 0x64 0144
floats: 3.14 +3e+000 3.141600E+000
Parte 05 :: printf
int printf ( const char * format [ , argument , ...] );
Programmazione Grafica aa2006/2007 15
EsempioParte 06 :: sumVectorValues
La funzione prende in ingresso 2 valori, un intVector e un intero.Viene eseguito un ciclo per i numero di volte (times).Nel ciclo sommo il valore delle componenti del vettore x , y, z a se stesso.Una volta uscito dal ciclo chiamo la funzione printValues a cui passo il mio vettore per
essere stampato.
In ultima istanza passo come valore di ritorno la somma delle tre componenti del vettore risultante dalle
operazioni precedenti.
int sumVectorValues(intVector vec, int times) {
int i; // local varfor(i=0;i<times;i++) // ciclo for{
vec[0] += vec[0]; // a = a + avec[1] += vec[1]; // a = a + avec[2] += vec[2]; // a = a + a
}printValues(vec); // richiamo la funzione printValuesreturn vec[0]+vec[1]+vec[2]; // valore di ritorno
}
Programmazione Grafica aa2006/2007 16
EsempioParte 07 :: main
La funzione main, è la prima funzione invocata in esecuzione.Istanzia una variabile “myVector”di tipo intVector.
La variabie myVector viene inizializzati ai valori 1,1,1.
Eseguo la funzione “sumVectorValues” e il suo valori di ritorno lo salvo nell variabile res.
Stampo il valore della variabile res.
// Main functionint main(int argc, char *argv[]){
intVector myVector; // variabile di tipo intVectorint res; // variabile intmyVector[0] = 1; // primo elemento dell'array myVector[1] = 1; // secondo elemento dell'array myVector[2] = 1; // terzo elemento dell'array
res = sumVectorValues (myVector, 4); // richiamo la funzione sumVectorValues
printf("\nRes :: %d",res); // gestione dell'output su console
return 0; // valore di ritorno}
Programmazione Grafica aa2006/2007 17
FunzioniPassaggio di parametri
Il linguaggio C utilizza il passaggio dei parametri alle funzioni per valore. Per ottenere il passaggio per riferimento occorre utilizzare i puntatori.
#include <stdio.h>
void function (value){ value++; // value = 4}
void main (void){ int value = 3; function (value); // value = 3}
Programmazione Grafica aa2006/2007 18
StructStruct
struct box cassetto;
Viene cosi definita una nuova struttura box e definita cassetto di tipo struct box. Una struttura puo' essere pre-inizializzata al momento della dichiarazione:
struct box cassetto={“sofa", 5, 40.50};
Per accedere ai membri (o campi) di una struttura il C fornisce l'operatore ".". Ad esempio:
cassetto.numeroBiro = 35;
struct box {
char name[36]; int numeroBiro; float profondita;
};
Programmazione Grafica aa2006/2007 19
Allineamento byte nelle struct
Occupazione di memoria di una struct: – non è data dalla somma delle occupazioni dei singoli campi– dipende dall’ordine in cui vengono definiti– si deve prestare attenzione all’allineamento dei byte dei campi
rispetto alla word (4 byte – 32 bit nel nostro caso).Esempio: struct con un intero e due char
– occupazione memoria del tipo char: 1 byte– occupazione memoria del tipo int: 4 byte– Sarebbe char + char + int = 6 byte ma……– …………cosa succede in questi due casi?
typedef struct _mia_struct1{
char c1;int n;char c2;
} mia_struct1;
typedef struct _mia_struct2{
int n;char c1;char c2;
} mia_struct2;
Programmazione Grafica aa2006/2007 20
4 8 12
Allineamento byte nelle struct
Una word viene riempita con uno o più dati.Se un dato non può essere contenuto esattamente in una word si salta alla
word successiva.
typedef struct _mia_struct1{
char c1;int n;char c2;
} mia_struct1;
typedef struct _mia_struct2{
int n;char c1;char c2;
} mia_struct2;
4 8 12
c1 c2 n Offset Offset
Occupazione totale : 12 byte (!)
n c1 c2 Offset
Occupazione totale : 8 byte (!)
Programmazione Grafica aa2006/2007 21
Strutture di controllo
for (expression1; expression2; expression3) statement;
while (expression) statement;
Condition Statement
Cicli
if (expression) statement1 else statement2 oexpression1 ? statement1 : statement2
switch (expression) { case item1: statement1;
break; case item2: statement2;
break; case default: statement;
break;}
Programmazione Grafica aa2006/2007 22
sprintf
La funzione scrive una serie di argomenti secondo uno schema di formattazione, in un buffer.
Buffer: Buffer dove vengono scritti i valori
Format: Stringa che contiene del testo da essere stampatoHa il seguente prototipo: %[flags][width][.precision][modifiers]type
argument(s): Parametri opzionali che contengono i valori/dati da essere visualizzati secondo la formattazione prescelta.
Return Value:in caso di errore ritorna un numero negativo altrimenti ritorna il numero di caratteri stampati.
int sprintf ( char * buffer, const char * format [ , argument , ...] );
sprintf
Programmazione Grafica aa2006/2007 23
sprintf
Output:[10 + 3 è 13] is a 11 chars string
sprintf
#include <stdio.h>
int main (void){
char buffer [100];int n, a=10, b=3;n = sprintf (buffer, "%d + %d è %d",
a, b, a+b);printf ("[%s] is a %d chars string\
n",buffer,n);return 0;
}
Programmazione Grafica aa2006/2007 24
Rand
La function "rand" consente di estrarre un numero pseudo-casuale.
n=rand();La sequenza di numeri ottenuta con la "rand" è però sempre la stessa, per avere una sequenza che sia imprevedibile è necessario fornire alla "rand" un seme di avvio diverso ogni volta che essa viene richiamata. Per tale scopo è necessario usare la "srand". Il parametro che deve essere passato alla "srand" è un "unsigned int".
srand(437U);Ogni valore diverso del seme dà inizio ad una diversa sequenza di numeri. Affinchè la sequenza possa essere imprevedibile è necessario passare alla "srand" un valore imprevedibile per il seme. Un modo di fare ciò è quello di usare il clock di macchina.
srand ((unsigned int) time((NULL));La funzione "time(NULL)" ritorna l'ora corrente; la costante "NULL" è definita nello header "<stdio.h>". Il valore restituito da tale function viene convertito tramite un "cast" al tipo "unsigned int".
Rand
int rand (void)
Programmazione Grafica aa2006/2007 25
Filefopen
I files sono l'esempio piu' comune di stream. Per aprire un puntatore al file si utilizza la funzione fopen().
Tale funzione ritorna un puntatore a FILE. La stringa "name" e' il nome del file su disco a cui vogliamo accedere; la stringa "mode" definisce il tipo di accesso. Se per una qualsiasi ragione il file risulta non accessibile, viene ritornato un puntatore nullo.
Le possibili modalita' di accesso ai files sono: * "r" (read), * "w" (write), * "a" (append).
e: *”t” (modalità ASCII - default), *”b” (modalità binaria).
Per aprire un file dobbiamo avere una stream (puntatore al file) che punta ad una struttura FILE.Ogni volta che apriamo un file dobbiamo SEMPRE chiuderlo.
FILE *stream;
stream = fopen ("myfile.dat","rb");
if ((stream = fopen ("myfile.dat","rb"))==NULL)
{ printf("Can't open %s \n", "myfile.dat");
exit(1); }fclose(stream);
FILE *fopen(char *name, char *mode)
Programmazione Grafica aa2006/2007 26
Lettura e scrittura da file ASCII
fprintf - fscanf
Le funzioni fprintf ed fscanf sono comunemente utilizzate per l'accesso ai files di testo.
Sono simili a printf e scanf, tranne per il fatto che i dati sono letti dalla stream, che deve essere aperta con fopen().
Il puntatore alla stream viene incrementato automaticamente con tutte le funzioni di lettura/scrittura su file, quindi non e' necessario preoccuparsi di farlo manualmente.
char *string[80] FILE *fp; if ((fp=fopen("file.dat","r")) != NULL) fscanf(fp,"%s",string); fclose(fp);
char *string[80]: FILE *stream, *fopen(); if ((stream=fopen(...)) != NULL) fscanf(stream,"%s",string);fclose(stream);
int fprintf(FILE *stream, char *format, args ...) int fscanf(FILE *stream, char *format, args ...)
Programmazione Grafica aa2006/2007 27
Lettura e scrittura da file binari
I comandi fread e fwrite ci permettono di leggere/scrivere con una sola
riga di codice una grande quantità di dati da file binari.
int fread(void *buffer, int size, int num, FILE *fp);
Il comando:• legge dal file puntato da fp• un numero num di dati• di grandezza size byte• e li mette nel buffer buffer• ritorna il num di elementi letti
int fwrite(void *buffer, int size, int num, FILE *fp);
Il comando:• scrive nel file puntato da fp• un numero num di dati• di grandezza size byte• Pigliandoli dal buffer buffer• ritorna il num di elementi scritti
Programmazione Grafica aa2006/2007 28
Organizzazione del progetto
Divisione del codice
Ci potrà essere molto utile ed anzi è caldamente consigliato organizzare su più file il progetto che si ha intenzione di affrontare.
Le ragioni sono tante tra le più importanti ci sono sicuramente:- maggiore comprensione del codice-un code reuse molto alto, spesso molte funzioni che scriviamo possono essere riutilizzate per altri scopi.
Al nostro programma aggiungiamo ora altre 2 semplicissime funzioni; vediamo la dichiarazione delle funzioni:
// dichiarazione funzioni//void printVector (intVector vec);void printInteger (int intVal);
int sumVectorValues (intVector vec, int times);int sumIntValues (int intVal, int times);// ............................................
Programmazione Grafica aa2006/2007 29
Organizzazione del progetto
Parte 02
Tutto il codice di “sorgente04” è contenuto in un unico file. Come possiamo vedere mano a mano che il codice cresce e si iniziano ad introdurre nuove funzioni la gestione del codice diviene più difficoltosa.L’ unica possibilità per rendere il tutto più chiaro è cercare di dividere il tutto su più files.
Come primo approccio bisogna individuare le funzioni che fanno delle cose comuni, nel nostro caso abbiamo due funzioni che stampano su standard output, e altre due che gestiscono dati e li elaborano, ed infine abbiamo il main.
Bene a questo punto dobbiamo cercare di avere non più 1 file solo ma almeno 3 file.
Il primo deve contenere la main function.Il secondo set di files conterrà le funzioni di print.il terzo set di files conterrà le funzioni di gestione dei dati.
Con “set di files” indico una coppia di file, header (*.h) e la sua implementazione (*.c).
Vediamo nel nostro caso come risulteranno i files e come li compileremo.
Programmazione Grafica aa2006/2007 30
Organizzazione del progetto
Parte 03 – sorgente05.c
// Direttive per il preprocessore// #include <stdio.h> // Pre-processore#include "OperationsFunctions.h"#include "PrintFunctions.h"
#define COSTANTE_01 30 // Pre-processore#define COSTANTE_02 50 // Pre-processore
int main(int argc, char *argv[]){
//implementazione}
La modifica evidente è la scomparsa delle funzioni e invece la comparsa di nuovi include i quali includono
le dichiarazioni delle funzioni precedentemente implementate nel codice unico (sorgente04.c).
Programmazione Grafica aa2006/2007 31
Organizzazione del progetto
Parte 04 – OperationsFunctions
typedef int intVector[3];
int sumVectorValues (intVector vec, int times);int sumIntValues (int intVal, int times);
OperationsFunctions.h
#include "OperationsFunctions.h"
int sumVectorValues(intVector vec, int times) {
//implementazione}int sumIntValues (int intVal, int times){
//implementazione}
OperationsFunctions.c
Programmazione Grafica aa2006/2007 32
Organizzazione del progetto
Parte 05 – PrintFunctions
typedef int intVector[3];
int sumVectorValues (intVector vec, int times);int sumIntValues (int intVal, int times);
PrintFunctions.h
#include “PrintFunctions.h"
int printValues(intVector vec, int times) {
//implementazione}int printInteger (int intVal, int times){
//implementazione}
PrintFunctions.c
Programmazione Grafica aa2006/2007 33
Organizzazione del progetto
Parte 06 - Compilazione
Prima di tutto devo compilare i file OperationsFunctions.c e PrintFunctions.c.
cl /c OperationsFunctions.ccl /c PrintFunctions.ccl /c sorgente05.c
E genero i file obj dove sono contenute le dipendenze per le funzioni.
Successivamente compilo il file sorgente05.c con le dipendenze di libreria (*.obj) generate prima.Adesso genero il sorgente con il seguente comando nel quale dovrò indicare le dipendenze dei 2 file esterni appena creati.
cl Sorgente05.c /l OperationsFunctions.obj PrintFunctions.obj
Il compilato è il medesimo della versione precedente.
Programmazione Grafica aa2006/2007 34
Puntatori
Utilizzati per:
- Allocazione dinamica della memoria- Passaggio di parametri per riferimento nelle funzioni- Puntatori a funzione per gestione callback
- Una variabile dichiarata come puntatore ad un tipo conterrà l’indirizzo di una locazione di memoria
int i, j, k;int *pi;
i = 5;j = 10;k = 15;pi = &i;
5
10
15
0x12ff70
…
i
j
k
pi
0x12ff70
0x12ff74
0x12ff78
0x12ff7C
0x12ff80
Programmazione Grafica aa2006/2007 35
Puntatori
Allocazione dinamica
int *a = NULL;int dim = 6;
a = (int*) malloc( dim * sizeof(int) );
for(i=0; i<dim; i++) a[i] = 1000 * ( i+1);
free(a);
0x12ff74 0x431870 a
…
0x43186C
0x431870 1000 a[0]
0x431874 2000 a[1]
0x431878 3000 a[2]
0x43187C 4000 a[3]
0x431880 5000 a[4]
0x431884 6000 a[5]
Programmazione Grafica aa2006/2007 36
Puntatori
Allocazione dinamica - caso bidimensionale
#define DIMX 8#define DIMY 6
…
int i, j;int **m;
m = (int **)malloc( DIMX * sizeof(int*) );
for(i=0; i<DIMX; i++){ m [i] = (int*) malloc( DIMY * sizeof(int) );}
m[0] m[0][0]
m[0][1]
…
m[1] m[1][0]
m[1][1]
m[2] …
m[3]
m[4]
m[5]
m[6]
m[7]
m