Dichiarazione di una funzione.
Nel linguaggio C (ansi C99) ogni funzione implementata deve essere preventivamente dichiarata. La definizione di una funzione è anche detto “prototipo”, ed ha lo scopo definire essenzialmente 3 cose :
- Nome della funzione
- Elenco parametri di ingresso (nomi, tipo e lunghezza di ogni parametro previsto)
- Tipo della funzione (il tipo dato che ritorna: semplici, complesso, o anche assente nel caso delle funzioni di tipo void)
Faccio un esempio :
#include <stdio.h>
int printMessage ( char *msg, unsigned short int n ) ;
main()
{
printMessage ( "hello world!", 5) ;
}
int printMassage ( char *msg, unsigned short int n )
{
short int contatore = 0 ;
// esce restituendo un warning
if ( n = 0 || n > 100 )
return -1 ;
for ( contatore = 0 ; contatore < n ; contatore++ )
printf ( "[%u] messaggio : [%s]\n", contatore, msg );
return 0 ;
}
Compilando ed eseguendo example-1.c, questo stamperà per 5 volte il messaggio “hello world!”.
arcadia$ cc example-1.c -o example-1
arcadia$ ./example-1
[1] messaggio : [hello world!]
[2] messaggio : [hello world!]
[3] messaggio : [hello world!]
[4] messaggio : [hello world!]
[5] messaggio : [hello world!]
arcadia$
Se io provassi ad omettere la dichiarazione della funzione printMessage, ossia la riga 3 del sorgente, otterrei un errore da parte del compilatore
arcadia$ cat example-1.c | grep "\/\/ | grep printMessage
//int printMessage ( char *msg, unsigned short int n ) ;
arcadia$ cc example-1.c -o example
example-1.c:7:8: warning: call to undeclared function 'printMessage'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
7 | printMessage ( "hello world!", 5) ;
| ^
example-1.c:10:5: error: conflicting types for 'printMessage'
10 | int printMessage ( char *msg, unsigned short int n )
| ^
example-1.c:7:8: note: previous implicit declaration is here
7 | printMessage ( "hello world!", 5) ;
| ^
1 warning and 1 error generated.
Se io provassi a prototipare (dichiarare la funzione) senza uno dei 2 parametri o a cambiare il tipo, otterrei sempre un errore da parte del compilatore.
arcadia$ cat example-1.c | grep "int printMessage"
int printMessage ( char *msg ) ; // , unsigned short int n ) ;
int printMessage ( char *msg, unsigned short int n )
arcadia$ cc example-1 -o example-1
example-1.c:7:39: error: too many arguments to function call, expected single argument 'msg', have 2 arguments
7 | printMessage ( "hello world!", 5) ;
| ~~~~~~~~~~~~ ^
example-1.c:3:5: note: 'printMessage' declared here
3 | int printMessage ( char *msg ) ; // , unsigned short int n ) ;
| ^ ~~~~~~~~~
example-1.c:10:5: error: conflicting types for 'printMessage'
10 | int printMessage ( char *msg, unsigned short int n )
| ^
example-1.c:3:5: note: previous declaration is here
3 | int printMessage ( char *msg ) ; // , unsigned short int n ) ;
| ^
2 errors generated.
Il compilatore C è alquanto rigido su questo aspetto, e allora ci si potrebbe chiedere : è possibile implementare in C una funzione variadica, con una tale rigidità del compilatore ?
Funzione Variadica
Per variadica si intende una funzione che ha un numero di parametri variabile :
- Parametri noto, almeno uno.
- Parametri non noti (o anonimi), numero non definito.
Supponiamo di dover progettare una funzione che calcoli la media matematica delle temperature rilevate in più città di Italia ad una certa data e ora. Il numero di luogi rilevati nella giornata non è preventivabile, quindi ignoto.
Per una giornata potrebbero arrivare ad esempio 8 rilevazioni, ed il giorno dopo 4. Potrebbero esserci giornate in cui addirittura non arriva nessuna rilevazione, ed il giorno successivo 6.
media = calcolaMedia ( "01/10/2026 12:00", 12, 22, 11, 13, 15, 11, 32, 12 ) ;
media = calcolaMedia ( "02/10/2026 12:00", 10, 23, 11, 15 ) ;
media = calcolaMedia ( "03/10/2026 12:00", 11, 10, 13, 16, 16, 11 ) ;
media = calcolaMedia ( "04/10/2026 12:00", 15, 21, 33, 15, 15, 10, 10, 12, 11, 09, 12 ) ;
media = calcolaMedia ( "05/10/2026 12:00", 14, 30, 21, 17, 11, 10, 10 ) ;
media = calcolaMedia ( "06/10/2026 12:00" ) ;
media = calcolaMedia ( "07/10/2026 12:00", 10, 40, 11, 18, 18, 16 ) ;
La nostra funzione calcolaMedia dovrà essere definità in qualche modo come variadica, ma come?
Il linguaggio C, per definire un elenco indefinito di parametri anonimi, ha previsto i 3 punti […], quindi la funzione calcolaMedia può essere dichiarata in questo modo, senza alcuna alzata di ciglio da parte del compilatore, clang, gnu gcc o altro.
int calcolaMedia ( char *timestamp, ... )
Vediamo se è vero.
arcadia$ cat example-2.c
#include <stdio.h>
int calcolaMedia(unsigned int numeroValori, ...) ;
int main()
{
char *timestamp = "01/10/2026 12:00:00" ;
int numeroRilevazioni = 8 ;
/*
.-------------------.
| test calcolaMedia |
'-------------------' */
int mediaTemperatura = calcolaMedia ( numeroRilevazioni, 12, 22, 11, 13, 15, 11, 32, 12 ) ;
printf("rilevazione del [%s] | numeroRilevazioni [%d] | media temperatura [%d]",
timestamp, numeroRilevazioni, mediaTemperatura) ;
return 0;
}
int calcolaMedia(unsigned int numeroValori, ...)
{
int mediaAritmetica = 0 ;
/*
lettura dei lista valori anonimi
e calcolo della mediaTemperatura */
return mediaAritmetica ;
}
arcadia$ cc example-2.c -o example-2 -std c99
arcadia$
Una volta compreso come dichiarare una funzione variadica, è necessario capire come leggere l’elenco dei parametri anonimi, e per far questo bisogna introdurre la stdarg.h.
stdarg.h
stdarg.h è un header di libc (ansi c99) che non definisce alcuna funzione ma esclusivamente le 4 macro, che risultano essere indispensabili per implementare una funzione variadica. Vediamo il contenuto di stdarg.h per l’implementazione didattica della mia libc.
arcadia$ cat null0s/src/libc/include/ansi/stdarg.h
#ifndef _STDARG_H
#define _STDARG_H
typedef __builtin_va_list va_list;
#define va_start(ap, param) __builtin_va_start(ap, param)
#define va_arg(ap, type) __builtin_va_arg(ap, type)
#define va_end(ap) __builtin_va_end(ap)
#define va_copy(dest, src) __builtin_va_copy(dest, src)
#endif
Vediamo nel dettaglio ogni singila macro :
- va_list. Serve a definire la variabile che punterà agli argomenti anonimi della lista.
- va_start. Inizializza la variabile definita da va_list, in modo tale che questa punti al primo elemento della lista anonima.
- va_arg. restituisce il valore dell’elemento della lista anonima a cui la variabile (definita da va_list ed inizializzata da va_start) sta puntando, dovendone specificane preventivamente anche il tipo. Ogni lettura fa avanzare il puntatore.
- va_end. Per indicare al compilatore C che non si seguiranno altre letture (va_arg) della lista. Per poter nuovamente leggere la lista dopo una va_end, bisogna ripetere il va_start, che si posiziona nuovamente al primo elemento della lista anonima.
- va_copy. (introdotta con C99) permette di copiare lo stato di una lista di argomenti, magari per posizionarsi in un punto precedente, e ripartire la lettura da li.
Scriviamo un esempio completo di funzione variadica.
Proviamo a completare la nostra funzione calcolaMedia.
#include <stdio.h>
#include <stdarg.h>
int calcolaMedia(unsigned int numeroValori, ...) ;
int main()
{
char *timestamp = "01/10/2026 12:00:00" ;
int numeroRilevazioni = 8 ;
/*
.-------------------.
| test calcolaMedia |
'-------------------' */
int mediaTemperatura = calcolaMedia ( numeroRilevazioni, 12, 22, 11, 13, 15, 11, 32, 12 ) ;
printf("rilevazione del [%s] | numeroRilevazioni [%d] | media temperatura [%d]",
timestamp, numeroRilevazioni, mediaTemperatura) ;
return 0;
}
int calcolaMedia(unsigned int numeroValori, ...)
{
int valoreLetto = 0 ;
int sommaValori = 0 ;
int mediaAritmetica = 0 ;
/*
.------------------------------------------------------.
| legge la lista dei valori anonimi e calcola la media |
'------------------------------------------------------' */
// Dichiara il puntatore alla lista anonima
va_list listaValori ;
// Posiziona il puntatore al primo elemento della lista anonima
va_start(listaValori, numeroValori);
// Effettua la somma di tutti i valori della lista anonima
for (int i = 0; i < numeroValori; i++) {
valoreLetto = va_arg(listaValori, int);
printf("calcoloMedia|ValoreLetto[%d]\n",valoreLetto);
sommaValori += valoreLetto ;
}
// Calcola la media aritmentica dei valori
mediaAritmetica = sommaValori / numeroValori ;
// Dichiara di voler leggere piu da listaValori
va_end(listaValori);
// Ritorna al chiamante la media aritmerica dei valori ricevuti
return mediaAritmetica ;
}
arcadia$ cc example-2.c -o example-2 -std=c99
arcadia$ ./example-2
calcoloMedia|ValoreLetto[12]
calcoloMedia|ValoreLetto[22]
calcoloMedia|ValoreLetto[11]
calcoloMedia|ValoreLetto[13]
calcoloMedia|ValoreLetto[15]
calcoloMedia|ValoreLetto[11]
calcoloMedia|ValoreLetto[32]
calcoloMedia|ValoreLetto[12]
rilevazione del [01/10/2026 12:00:00] | numeroRilevazioni [8] | media temperatura [16]
Cosa avviene sotto al cofano?
Viene da chiedersi, come fa una serie di macro ad implementare delle vere e proprio funzionalità, non implementate direttamente all’interno di libc? Per rispondere a questa domanda è necessario ritornare all’header strarg.h. Innanzitutto l’effetto ad esempio, di
#define va_start(ap, param) __builtin_va_start(ap, param)
è quello, che il pre compilatore, ogni volta che incontrerà, ad esempio, una va_start, la andrà a sostituire con una _builtin_va_start, lasciando inalterati i parametri da noi impostati, pertanto va da se che i parametri della ‘define’ stessa, non vengono per nulla considerati. Nell’header strarg.h è quasi una convenzione chiamarli ‘ap‘ (anonimous parameters) e ‘param‘. Lo si fa perchè lo fanno anche le altre implementazioni di libc, e quindi suona quasi familiare, ma non è un obbligo imposto da qualche specifica o altro. A confermare quanto detto, è possibile eseguire del nostro example-2.c la sola pre compilazione, è verificare poi nell’output prodotto le sostituzioni applicate a va_list, va_start, va_arg e va_end. Vediamo.
arcadia$ cc -E -P example-2.c -o example.i -std c99
arcadia $ tail -n 16 example-2.i
int calcolaMedia(unsigned int numeroValori, ...)
{
int valoreLetto = 0 ;
int sommaValori = 0 ;
int mediaAritmetica = 0 ;
va_list listaValori ;
__builtin_va_start((listaValori), numeroValori);
for (int i = 0; i < numeroValori; i++) {
valoreLetto = __builtin_va_arg((listaValori), int);
printf("calcoloMedia|ValoreLetto[%d]\n",valoreLetto);
sommaValori += valoreLetto ;
}
mediaAritmetica = sommaValori / numeroValori ;
__builtin_va_end((listaValori));
return mediaAritmetica ;
}