Funzioni con un Numero Variabile di Argomenti in Linguaggio C

Numero di Argomenti Variabile

Arrivati fino a questo punto, abbiamo visto come dichiarare e definire funzioni che accettano (o meno) un certo numero di parametri di ingresso.

Tuttavia, al lettore più attento non sarà sfuggito che esistono delle funzioni, come la funzione di libreria printf, che può essere invocata con un numero differente di argomenti a seconda dei casi.

Consideriamo il seguente stralcio di codice:

// Invochiamo la funzione printf con un solo argomento
printf("Ciao\n");

// Invochiamo la funzione printf con due argomenti
printf("Il risultato è %d\n", 42);

// Invochiamo la funzione printf con tre argomenti
printf("Il risultato è %d e il resto è %d\n", 42, 1);

Nell'esempio di sopra abbiamo invocato la funzione printf prima con un solo argomento, poi con due e infine con tre argomenti! Il che farebbe pensare che la funzione printf sia quasi un'eccezione alla regola.

In realtà, non è così. Infatti, in C è possibile definire funzioni che accettano un numero variabile di argomenti attraverso le funzionalità messe a disposizione dalla libreria standard. Vediamo come fare.

Funzione che accetta un numero variabile di argomenti

Per prima cosa, per poter definire una funzione che accetta un numero variabile di argomenti, dobbiamo adoperare un operatore speciale, ..., chiamato ellissi.

Questo operatore deve essere posizionato alla fine della lista dei parametri formali della funzione. Ad esempio, la seguente dichiarazione di funzione è corretta:

void funzione(int a, int b, ...);

In questo caso, la funzione funzione accetta due parametri di tipo int e un numero variabile di argomenti di qualsiasi tipo.

Tuttavia, non è possibile definire una funzione che accetta un numero variabile di argomenti senza specificare almeno un parametro formale. Infatti, la seguente dichiarazione di funzione non è corretta:

// Errore:
// almeno un parametro formale deve essere specificato
void funzione(...);

Inoltre le ellissi non possono essere utilizzate in altri punti della lista dei parametri formali. Ad esempio, le seguenti dichiarazioni di funzione non sono corrette:

// Errore:
// L'ellissi non può essere posta all'inizio della lista dei parametri formali
void funzione(..., int a);

// Errore:
// L'ellissi non può essere posta in mezzo alla lista dei parametri formali
void funzione(int a, ..., int b);
Definizione

Operatore Ellissi

L'operatore ellissi ... è un operatore speciale in C che consente di definire funzioni con un numero variabile di argomenti. Deve essere posizionato alla fine della lista dei parametri formali e deve essere preceduto da almeno un parametro formale. Non può essere utilizzato in altri punti della lista dei parametri formali.

La sintassi corretta per definire una funzione con un numero variabile di argomenti è la seguente:

tipo_ritorno funzione(tipo1 a, tipo2 b, ...);

Una volta che abbiamo definito una funzione con un numero variabile di argomenti, possiamo invocarla normalmente facendo attenzione però a rispettare alcune condizioni:

  1. Gli argomenti corrispondenti ai parametri formali espliciti devono essere forniti in ordine e con il tipo corretto.
  2. Gli argomenti corrispondenti agli argomenti variabili possono essere forniti in qualsiasi ordine e con qualsiasi tipo, ma devono essere forniti in numero sufficiente affinché la funzione possa funzionare correttamente.

Ad esempio, supponiamo di voler dichiarare una funzione somma che effettui la somma di un certo numero di interi. La dichiarazione della funzione potrebbe essere la seguente:

int somma(int n, ...);

In questo caso, n rappresenta il numero di argomenti che vogliamo sommare. La funzione somma accetterà quindi un numero variabile di argomenti di tipo int.

A questo punto, possiamo invocare la funzione somma in questo modo:

int risultato = somma(3, 1, 2, 3);

Con il primo argomento indichiamo il numero di argomenti che vogliamo sommare, 3, e con i successivi tre argomenti indichiamo i numeri da sommare, 1, 2 e 3.

Adesso che abbiamo visto come dichiarare e invocare una funzione con un numero variabile di argomenti, vediamo come possiamo implementarla.

Libreria stdarg.h

Per implementare una funzione con un numero variabile di argomenti, dobbiamo utilizzare alcune macro (vedremo le macro in seguito) definite nel file di intestazione stdarg.h. Questo file di intestazione fa parte della libreria standard del linguaggio C e fornisce le funzionalità necessarie per gestire gli argomenti variabili.

Le principali macro definite in stdarg.h sono:

  • va_start: inizializza un oggetto di tipo va_list per accedere agli argomenti variabili.
  • va_arg: recupera il valore del prossimo argomento variabile di un tipo specificato.
  • va_end: termina l'accesso agli argomenti variabili.

Vediamo come adoperare queste macro per implementare la funzione somma che abbiamo dichiarato in precedenza.

#include <stdio.h>
#include <stdarg.h>

int somma(int n, ...) {
    int risultato = 0;

    // Inizializza un oggetto di tipo va_list
    va_list args;
    va_start(args, n);

    // Somma gli argomenti variabili
    for (int i = 0; i < n; i++) {
        risultato += va_arg(args, int);
    }

    // Termina l'accesso agli argomenti variabili
    va_end(args);

    return risultato;
}

int main() {
    int risultato = somma(3, 1, 2, 3);
    printf("La somma è: %d\n", risultato);
    return 0;
}

Come si può vedere dall'esempio, l'implementazione della funzione somma consiste in vari passaggi:

  1. Dichiariamo una variabile di tipo va_list chiamata args per accedere agli argomenti variabili. Il tipo va_list è un cosiddetto tipo opaco, il che significa che non importa sapere esattamente come è fatto, ma solo come usarlo. Infatti, tutte le implementazioni del linguaggio C possono implementare va_list in modo differente.
  2. Inizializziamo args con la macro va_start, passando come secondo argomento il numero di argomenti formali che precede l'ellissi. In questo caso, il numero di argomenti formali è n. Adesso risulta chiaro perché non possiamo definire una funzione con un numero variabile di argomenti senza specificare almeno un parametro formale: non sapremmo da dove cominciare a contare gli argomenti variabili!
  3. Recuperiamo il valore del prossimo argomento variabile con la macro va_arg, specificando il tipo dell'argomento che vogliamo recuperare. In questo caso, vogliamo sommare solo argomenti di tipo int, quindi passiamo int come secondo argomento a va_arg.
  4. Infine, chiudiamo l'accesso agli argomenti variabili con la macro va_end.

Esistono altre macro definite in stdarg.h, ma quelle che abbiamo visto sono le più comuni e quelle che ci servono per implementare una funzione con un numero variabile di argomenti.

Ricapitolando:

Definizione

Macro per Gestire Argomenti Variabili

Le macro principali definite in stdarg.h per gestire gli argomenti variabili sono:

  • va_start: inizializza un oggetto di tipo va_list per accedere agli argomenti variabili:

    va_list args;
    va_start(args, numero_argomenti);
    
  • va_arg: recupera il valore del prossimo argomento variabile di un tipo specificato:

    tipo argomento = va_arg(args, tipo);
    
  • va_end: termina l'accesso agli argomenti variabili:

    va_end(args);
    

In Sintesi

In questa lezione abbiamo introdotto un meccanismo molto potente fornito dal linguaggio C per definire funzioni con un numero variabile di argomenti.

In particolare abbiamo visto che:

  • Per definire una funzione con un numero variabile di argomenti, dobbiamo utilizzare l'operatore ellissi ... alla fine della lista dei parametri formali.
  • Oltre all'operatore ellissi, dobbiamo dichiarare almeno un parametro formale.
  • Per implementare una funzione con un numero variabile di argomenti, dobbiamo utilizzare le macro definite nel file di intestazione stdarg.h, in particolare va_start, va_arg e va_end.
  • La macro va_start inizializza un oggetto di tipo va_list per accedere agli argomenti variabili.
  • La macro va_arg recupera il valore del prossimo argomento variabile di un tipo specificato.
  • La macro va_end termina l'accesso agli argomenti variabili.
  • Le macro va_start, va_arg e va_end devono essere utilizzate in questo ordine per garantire un corretto accesso agli argomenti variabili.