Un esempio di uso di vararg in C

Qui ho trovato un esempio di come le vararg possono essere usate in C.

#include  double average(int count, ...) { va_list ap; int j; double tot = 0; va_start(ap, count); //Requires the last fixed parameter (to get the address) for(j=0; j<count; j++) tot+=va_arg(ap, double); //Requires the type to cast to. Increments ap to the next argument. va_end(ap); return tot/count; } 

Posso capire questo esempio solo in una certa misura.

  1. Non mi è chiaro il motivo per cui utilizziamo va_start(ap, count); . Per quanto ho capito, in questo modo impostiamo l’iteratore sul suo primo elemento. Ma perché non è impostato all’inizio per impostazione predefinita?

  2. Non mi è chiaro il motivo per cui dobbiamo dare il count come argomento. Can not C determina automaticamente il numero degli argomenti?

  3. Non mi è chiaro il motivo per cui utilizziamo va_end(ap) . Cosa cambia? Imposta l’iteratore alla fine dell’elenco? Ma non è impostato alla fine della lista dal ciclo? Inoltre, perché ne abbiamo bisogno? Non usiamo più l’ ap ; perché vogliamo cambiarlo?

Ricorda che gli argomenti sono passati in pila. La funzione va_start contiene il codice “magico” per inizializzare la va_list con il puntatore dello stack corretto. Deve essere passato l’ultimo argomento con nome nella dichiarazione della funzione o non funzionerà.

Cosa va_arg fa è utilizzare questo puntatore dello stack salvato ed estrarre la quantità corretta di byte per il tipo fornito, quindi modificare ap modo che punti al successivo argomento nello stack.


In realtà queste funzioni ( va_start , va_arg e va_end ) non sono in realtà funzioni, ma implementate come macro del preprocessore. L’implementazione effettiva dipende anche dal compilatore, poiché i diversi compilatori possono avere un layout diverso dello stack e il modo in cui spinge gli argomenti nello stack.

Ma perché non è impostato all’inizio per impostazione predefinita?

Forse a causa di ragioni storiche da quando i compilatori non erano abbastanza intelligenti. Forse perché potresti avere un prototipo di funzione varargs che in realtà non si preoccupa delle vararg e impostare varargs è costoso su quel particolare sistema. Forse a causa di operazioni più complesse in cui fai va_copy o forse vuoi riavviare il lavoro con gli argomenti più volte e chiamare va_start più volte.

La versione breve è: perché lo standard della lingua lo dice.

In secondo luogo, non mi è chiaro il motivo per cui dobbiamo dare il conteggio come argomento. Can not C ++ può determinare automaticamente il numero degli argomenti?

Questo non è ciò che count . È l’ultimo argomento con nome alla funzione. va_start bisogno di capire dove sono le vararg. Molto probabilmente questo è per ragioni storiche su vecchi compilatori. Non riesco a capire perché non possa essere implementato diversamente oggi.

Come seconda parte della tua domanda: no, il compilatore non sa quanti argomenti sono stati inviati alla funzione. Potrebbe non essere nemmeno nella stessa unità di compilazione o anche nello stesso programma e il compilatore non sa come verrà chiamata la funzione. Immagina una libreria con una funzione vararg come printf . Quando compili la tua libc il compilatore non sa quando e come i programmi chiameranno printf . Sulla maggior parte delle ABI (ABI sono le convenzioni per come vengono chiamate le funzioni, come vengono passati gli argomenti, ecc.) Non c’è modo di scoprire quanti argomenti ha ricevuto una funzione. È inutile includere tali informazioni in una chiamata di funzione e non è quasi mai necessario. Quindi è necessario avere un modo per dire alla funzione varargs quanti argomenti ha ottenuto. L’accesso a va_arg oltre il numero di argomenti effettivamente passati è un comportamento non definito.

Quindi non mi è chiaro perché utilizziamo va_end (ap). Cosa cambia?

Sulla maggior parte delle architetture va_end non fa nulla di rilevante. Ma ci sono alcune architetture con argomenti complessi che passano la semantica e va_start potrebbe anche potenzialmente memoria malloc, quindi avresti bisogno di va_end per liberare quella memoria.

Anche la versione breve è qui: perché lo standard della lingua lo dice.

va_start inserisce l’elenco degli argomenti variabili. Si passa sempre l’ultimo argomento di funzione denominato come secondo parametro. È perché è necessario fornire informazioni sulla posizione nello stack, dove iniziano gli argomenti variabili, poiché gli argomenti vengono inseriti nello stack e il compilatore non può sapere dove c’è un inizio dell’elenco di argomenti variabili (non c’è differenziazione).

Per quanto riguarda va_end, viene utilizzato per liberare risorse allocate per l’elenco di argomenti variabili durante la chiamata va_start.

Sono macrosi di C. va_start imposta il puntatore interno all’indirizzo del primo elemento. va_end cleanup va_list . Se c’è va_start nel codice e non c’è va_end – è UB.

Le restrizioni che ISO C inserisce nel secondo parametro nella macro va_start () nell’intestazione sono diverse in questo standard internazionale. Il parametro parmN è l’identificativo del parametro più a destra nella lista dei parametri delle variabili della definizione della funzione (quella appena prima del …). Se il parametro parmN viene dichiarato con una funzione, array o tipo di riferimento o con un tipo che non è compatibile con il tipo che risulta quando si passa un argomento per il quale non esiste alcun parametro, il comportamento non è definito.