Iniziamo con il codice più classico che esista e cerchiamo di compilare il programma:

#include <stdio.h>
int main (void)
{
  printf ("Hello, world!\n");
  return 0;
}

Se assumiamo che il programma sia memorizzato in un file denominato hello.c, il comando per compilarlo sarà il seguente:

gcc -Wall hello.c -o hello

Il comando precedente ha come effetto la compilazione del file hello.c e la produzione di un file eseguibile denominato "hello". Il nome del file oggetto viene specificato tramite l'opzione -o; questa opzione è generalmente l'ultima della riga di comando e se omessa ha come consequenza la generazione di un file oggetto con il nome predefinito "a.out". L'opzione -Wall abilita la visualizzazione di tutti i messaggi di warning. Per eseguire il programma è necessario digitare il nome del file eseguibile preceduto dal suo percorso; in questo caso:

$ ./hello

Nel caso di programmi di grandi dimensioni, per semplificare l'editazione e la comprensione del codice, è comune dividere il sorgente su più file; questa situazione comporta la compilazione separata di ciascun file del progetto. Supponiamo, ad esempio, che il progetto sia composto da file1.c e file2.c, allora per compilare e linkare l'applicazione in un unico file eseguibile di nome test, è sufficiente digitare il comando:

$ gcc -Wall file1.c file2.c -o test

Notare che eventuali file header inclusi nei sorgenti non devono essere specificati nel comando di compilazione.

Supponiamo adesso di aver modificato una funzione implementata nel file file2.c e di voler generare il nuovo eseguibile, in questo caso per evitare inutili perdite di tempo, è possibile ricompilare esclusivamente il file sorgente file2.c, tramite il comando:

$ gcc -Wall -c file2.c

Successivamente per linkare assieme il vecchio file1.c con il nuovo file2.c, si può scrivere:

$ gcc file1.o file2.o -o test

 


I puntatori a funzione sono variabili che possono essere inizializzate con l'indirizzo di una funzione; questo genere di puntatori sono molto utili, perché consentono di richiamare all'interno dello stesso blocco di codice funzioni diverse (anche se tutte con la stessa signature), modificando a tempo di esecuzione e sulla base di altri dati del programma, il comportamento del codice. Questo processo, in cui gli indirizzi delle funzioni da chiamare non sono risolti al momento della compilazione, ma in fase di esecuzione si chiama late binding.

Per dichiarare un puntatore a funzione basta iniziare scrivendo il prototipo della funzione, poi sostituire il nome_funzione con (*nome_puntatore) ed infine eliminare i nomi dei parametri; a questo punto nome_funzione è il puntatore da utilizzare. Ad esempio supponiamo di voler dichiarare un puntatore alla funzione:

void func(int value);

basta scrivere:

void (*pFunc)(int);

pFunc è il puntatore da utilizzare, adesso non resta che inizializzarlo:

pFunc = func;

E' bene notare che la riga precedente è equivalente a scrivere:

pFunc = &func;

A volte la presenza di più di un asterisco può rendere difficoltoso l'interpretazione del prototipo della funzione, ma la regola è sempre la stessa: isolare il nome della funzione con il suo asterisco ed interpretare il resto come una funzione "normale". Ad esempio:

void* (*pFunc)(int *);

L'invocazione di una funzione tramite un puntatore non è dissimile dall'invocazione della funzione tramite il suo nome:

pFunc(10);

La riga precedente è equivalente a scrivere:

(*pFunc)(10);

Vediamo desso come sia possibile passare una funzione come parametro ad un'altra funzione sfruttando i puntatori.

void func (int v)
{
  printf("valore: %d\n", v);
}

void call_func(int v, void (*f1)(int))
{
  printf("valore: %d\n", v);
  f1(v + 1);
}

int main_funptr(int argc, char *argv[])
{
   call_func(3, func);
   return 0;
}

Come si nota dal codice precente, la funzione call_func riceve come parametri un intero ed una funzione con la signature void f1(int).


Un vettore è una collezione di dati, tutti dello tipo e disposti nella memoria uno di seguito all'altro.  Ad esempio In C un vettore di 10 interi si dichiara in questo modo:

 

int num[10];

per accedere ai singoli elementi che compongono l'array si utilizza l'operatore []; ad esempio per impostare il terzo elemento si può scrivere:

num[2] = 5;

Dopo questa breve introduzione passiamo all'argomento di questo post; e supponiamo di voler scrivere una funzione che accetti un array di interi come parametro ed inizializzi tutti gli elementi a zero. Una possibile implementazione potrebbe essere la seguente.

void init_array(int a[], int count)
{
  int i;
  /* Imposta a 0 ogni elemento del vettore */
  for(i = 0; i < count; i++)
    a[i] = 0;
}

Il primo parametro è definito come un array di interi mentre il secondo è un intero che contiene la dimensione dell'array. Come si può notare dal codice precedente, l'array all'interno della funzione viene sempre acceduto tramite l'operatore [].

La funzione può essere utilizzata in questo modo:

int main(void)
{
  int num[10];
  init_array(num, 10);  /* Notare la mancanza dell'op. [] */
  return 0;
}

A questo punto è bene precisare che quando si passa un array, in realtà viene passato un puntatore alla prima locazione e che le modifiche agli elementi del vettore fatte all'interno della funzione,  rimangono visibili anche al suo esterno: di fatto gli array sono passati sempre per riferimento e non per valore. Inoltre in C un vettore non ha conoscenza della sua dimensione e, per evitare di andare a leggere o scrivere all'esterno dei suoi confini, è necessario passare anche la sua dimensione massima tramite un ulteriore parametro intero.


Calendario

<<  febbraio 2012  >>
lumamegivesado
303112345
6789101112
13141516171819
20212223242526
2728291234
567891011

View posts in large calendar

Archivio

Licenza d'uso
Eccetto dove diversamente specificato, i contenuti di questo sito sono rilasciati mediante:

Licenza Creative Common