PDA

View Full Version : [C++][Risolto] cstdarg - Passare argomenti da una funzione all'altra


agente mm8
26-11-2009, 13:37
Salve a tutti.
Ho un piccolo problema con C++: ho una funzione che accetta più argomenti, e utilizzo la libreria cstdarg per prelevarli.
Ho quindi una seconda funzione, che esegue le stesse operazioni della prima con qualche aggiunta. Mi chiedevo quindi se era possibile lanciare la prima funzione, dalla prima.
Ho provato a passare gli argomenti in questo modo:
#include <cstdarg>
void funzioneUno(string pinco, ...){
// codice
}

void funzioneDue(string pallino, ...){
funzioneUno(pallino, ...);
//altro codice
}

Ma mi dà errore. Come faccio a passare la lista di argomenti dalla seconda funzione alla prima?

bottomap
26-11-2009, 13:53
Ciao,

Non mi sono mai trovato nella necessità di fare l'operazione che descrivi, ma forse potresti modificare la funzioneUno in modo che accetti due argomenti:
void funzioneUno(string pinco, va_list lista);

In modo da poteri richiamare la funzioneUno da dentro alla funzioneDue passando la lista di argomenti appropriata:
void funzioneDue(string pallino,...){
va_list vl;
va_start(vl,pallino);
funzioneUno(xxx,vl);
va_end();
}

Visto che altre funzioni (come la vsprintf) accettano una va_list come argomento, credo proprio che l'oggetto in questione sia trasferibile tra più funzioni, facendo attenzione alla sua apertura, all'uso dei va_args e alla chiusura.
NB:La cosa è tutta da verificare, non l'ho mai sperimentata come ho detto.

Ciaociao :)

agente mm8
26-11-2009, 13:58
Guarda, in effetti ci avevo pensato, lo stavo sperimentando giusto ora. Grazie mille del consiglio :p

Edit: tutto a posto, funge.

cionci
26-11-2009, 14:32
Però di fatto hai cambiato l'intestazione della funzione...e quando non lo puoi fare ?
Mettiamo caso che io voglia fare un layer aggintivo per la printf:
int myPrintf(string format, ...)
{
printf("%s\n", format.c_str());
printf(format.c_str(), ????);
}
Io sinceramente credo che non si possa fare.

bottomap
26-11-2009, 14:34
Ciao,

Beh, senza cambiare il prototipo la vedo un po'difficile anch'io... in quel caso concordo appieno: non è possibile.

Questo anche perchè i tre puntini di sospensione (...) sono un'espressione che può stare in un'intestazione o in un prototipo di funzione (in deroga alla normale sintassi <tipo> <nome variabile> per i parametri), ma non possono di certo essere passati come argomento a qualsivoglia altro metodo/funzione, né essere usati in qualsiasi espressione di qualsiasi tipo.
Non a caso per ricondurli ad un insieme di variabili è necessario, nel corpo della funzione, usare una va_list e le macro va_start, va_end e va_args.

Ciaociao :)

cionci
26-11-2009, 14:50
Questo anche perchè i tre puntini di sospensione (...) sono un'espressione che può stare in un'intestazione o in un prototipo di funzione (in deroga alla normale sintassi <tipo> <nome variabile> per i parametri), ma non possono di certo essere passati come argomento a qualsivoglia altro metodo/funzione, né essere usati in qualsiasi espressione di qualsiasi tipo
Su questo siamo d'accordo, ma avrebbero potuto prevedere una sintassi per identificare tutto quello passato come parametri variabili in modo da poterlo passare a sua volta ad una funzione che nell'intestazione ha i tre puntini.

wingman87
26-11-2009, 14:56
Penso si possa fare usando la libreria apposita: LINK (http://www.cplusplus.com/reference/clibrary/cstdarg/)
Ma non ci ho mai provato

cionci
26-11-2009, 14:58
Penso si possa fare usando la libreria apposita: LINK (http://www.cplusplus.com/reference/clibrary/cstdarg/)
Ma non ci ho mai provato
In quella libreria ci sono solo quelle 4 macro e nessuna fa la cosa di cui parliamo ;)

bottomap
26-11-2009, 15:02
Ciao,

Credo che i tre puntini e la varargs sia qualcosa di arcaico, nato ben prima dello standard ANSI C.
Un header varargs.h si dovrebbe rintracciare in POSIX e rappresenta ciò da cui poi è nato stdargs.h in UNIX... credo si parli della fine degli anni 80 ed inizi degli anni 90 (sicuramente esisteva solo il C stretto, e forse siamo prima dell'ISO C e del C89).

In effetti è rimasta di utilizzo piuttosto raro... personalmente credo di aver usato la cosa, per non perdere tempo nello sviluppo, una sola volta fino ad ora e di aver sentito di rado l'esigenza di un insieme indefinito di parametri - piuttosto ricorro a contenitori e tratto direttamente quelli (strada che è pienamente percorribile anche per il problema in discussione - soprattutto se gli elementi sono omogenei tra loro).

Edit: Quello in link è l'header apposito di sistema (stdarg.h o cstdarg sono la stessa cosa. Nel secondo caso quello che sta dentro è stato inserito nel namespace std, niente di più)... quello che usi (implicitamente o meno) quando dichiari va_args, va_list, va_start e va_end.


Ciaociao :)

cionci
26-11-2009, 16:25
Io credo che risalga alla prima stesura del C, visto che la printf sfrutta questo meccanismo.

wingman87
26-11-2009, 16:30
In quella libreria ci sono solo quelle 4 macro e nessuna fa la cosa di cui parliamo ;)

Hai ragione, non avendole mai usate avevo pensato che facessero cose che in realtà non fanno.

bottomap
26-11-2009, 16:40
Si, l'origine è indubbiamente arcaica...

Per finire di condire la discussione con qualche cenno prettamente storico, ecco scovato un gustoso articolo di wiki sulle orgini e storia della printf, che tra l'altro tocca di sfuggita anche i varargs (peccato che la wiki italiana non sia mai così fornita di materiale quanto quella inglese): http://en.wikipedia.org/wiki/Printf

Ciaociao :)

cionci
26-11-2009, 16:52
Allora si può !!!

Bisogna passare la struttura va_list come parametro alla funzione dove ci sono i tre punti.
#include <stdio.h>
#include <stdarg.h>


void funzioneDue(int num, ...)
{
int i;
int val;
printf ("int passed: ");
va_list vl;
va_start(vl,num);
for (i=0;i<num;i++)
{
val=va_arg(vl,int);
printf ("\t%d",val);
}
va_end(vl);
printf ("\n");
}

void funzioneUno(int num, ...)
{
va_list vl;
va_start(vl, num);
funzioneDue(num, vl);
va_end(vl);
}

int main()
{
funzioneUno(5, 1, 5, 2, 3, 4);
}
Compila, ma non mi funziona (stampa solo 2,3,4 in modo corretto). Se avete tempo dateci un'occhiata.

bottomap
26-11-2009, 17:48
Ciao,

No... ci avevo pensato anch'io, ma non ottieni la cosa voluta.

Compila correttamente solo perché a funzioneDue puoi passare quello che vuoi (in virtù appunto dei ...). vl sarà un argomento come gli altri (un int, un char)... certo lo puoi trattare come tale in funzioneDue se sai che ti arriva come secondo parametro, ma così non ci scostiamo dal problema originale. Nella va_list della funzioneDue avrai semplicemente un va_args che contiene una va_list (della funzioneUno), tutto qui. Poi forse ci si può giocare un po'sopra se si riesce ad individuare il tipo del dato e/o se si conosce la posizione esatta del dato nella seconda lista.

La discussione su vprintf e simili funzioni aveva tratto in inganno anche me. In realtà la frase "These functions offer the ability for programmers to essentially create their own printf variants" è un po'ambigua.

Significa che con una lista di argomenti variabili puoi ottenere la va_list e che, usando la vprintf/vsprintf/vfprintf puoi stampare a video/su stringa/su file (in virtù del fatto che vprintf ha una va_list come argomento - in sostanza quanto avevo proposto all'inizio nella modifica del prototipo di funzioneDue) e di conseguenza ottenere una "tua" versione della printf.

Edit:Ho fatto comunque qualche test a scanso di equivoci (MSVC .NET 2003 Standard) e ottengo risultati piuttosto randomici. Da debugger vedo anche che il primo elemento della va_list di funzioneDue è (come mi aspettavo) un puntatore alla va_list di funzioneUno.

Ciaociao :)

marco.r
26-11-2009, 17:54
Allora si può !!!

Bisogna passare la struttura va_list come parametro alla funzione dove ci sono i tre punti.
#include <stdio.h>
#include <stdarg.h>


void funzioneDue(int num, ...)
{
int i;
int val;
printf ("int passed: ");
va_list vl;
va_start(vl,num);
for (i=0;i<num;i++)
{
val=va_arg(vl,int);
printf ("\t%d",val);
}
va_end(vl);
printf ("\n");
}

void funzioneUno(int num, ...)
{
va_list vl;
va_start(vl, num);
funzioneDue(num, vl);
va_end(vl);
}

int main()
{
funzioneUno(5, 1, 5, 2, 3, 4);
}
Compila, ma non mi funziona (stampa solo 2,3,4 in modo corretto). Se avete tempo dateci un'occhiata.

Non funziona perche' in realta' passi semplicemente il puntatore alla lista come argomento. Per prima cosa dovresti trovare il modo di dereferenziarlo (perche' la va_list successiva andra' a prendere nuovamente il suo puntatore). Inoltre il compilatore pensa che tu abbia passato solo un parametro per cui si comporta di conseguenza.
Se a te e' venuto fuori 2 3 4 e' probabilmente dovuto al fatto che sfori e vai a pescare nello stack frame della chiamata precedente (ma e' solo una mia ipotesi).
L'unico modo portabile secondo me e' quello di contare il numero di argomenti e chiamarlo esplicitamente. In questo caso ovviamente bisogna mettere un limite al numero massimo di argomenti, ma con un po' di giochi di template si puo' probabilmente lasciarlo ragionevolmente alto senza dover diventar matti a scrivere tutto a mano.

bottomap
26-11-2009, 18:02
sfori e vai a pescare nello stack frame della chiamata precedente (ma e' solo una mia ipotesi)
Confermo l'ipotesi... anzi non è un'ipotesi, vai proprio a pescare nello stack.

Ciaociao :)

cionci
26-11-2009, 19:40
Quasi sicuramente...secondo me comunque qualcosa si può fare... Ci penso un po'.

cionci
26-11-2009, 19:59
Allora...lavorando in asm non ci dovrebbero essere grossi problemi.
Infatti si rifà nuovamente il push nello stack dei parametri, conoscendone la dimensione e il numero è semplicissimo.
In C non mi viene in mente una soluzione valida, questo perché bisognerebbe passare come parametro alla funzione una struttura dati con la parte dati allocata staticamente, in quanto se fosse allocata dinamicamente verrebbe passata per puntatore.
In C++ si potrebbe fare qualcosa con i template.

cionci
26-11-2009, 20:13
Giusto per farvi capire... Questo non funziona perché a viene passato per indirizzo.
#include <stdio.h>
#include <stdarg.h>

void funzioneDue(int num, ...)
{
va_list vl;
va_start(vl, num);

for(int i = 0; i < num; ++i)
{
printf("%d ", va_arg(vl, int));
}
va_end(vl);
printf("\n");
}

void funzioneUno(int num, ...)
{
int a[num];

va_list vl;
va_start(vl, num);

for(int i = 0; i < num; ++i)
{
a[i] = va_arg(vl, int);
}
va_end(vl);

funzioneDue(num, a);
}

int main()
{
funzioneUno(5, 12, 13, 154, 123, 222);

return 0;
}

bottomap
26-11-2009, 20:27
Ciao,

Mi pare che stiamo uscendo un po dal seminato... in asm la cosa è chiaramente fattibile, ma viste le prerogative del C non hai molte alternative a disposizione...

Se vuoi (e bisogna vedere quanto e se è fattibile) basterebbe qualcosa del genere, ma fa affidamento che il va_list sia il primo argomento passato, quando invece il placeholder "..." è assolutamente generico e non ti assicura niente sull'ordine dei parametri:
...
va_list v1;
va_start(v1,x);
va_list v2=va_arg(v1,va_list);
... e a questo punto girare v2 ...
va_end(v1);
In buona sostanza comunque una bruttura. Direi che il fatto che sia un arcaismo di poca utilità e poco utilizzato è, in fondo, un bene...

Ciaociao :)

cionci
26-11-2009, 20:30
Ma che c'entra, in questo modo noi bisogna modificare funzioneDue...
Dicevo che ci vorrebbe una struttura con dimensione definita a runtime, passata per valore. Basta quello per ottenere quello che vogliamo.

PS: prima ho scritto int a[5] invece dovevo scrivere int a[num]

wingman87
26-11-2009, 20:40
Hai ragione, infatti questo funziona:
#include <stdio.h>
#include <stdarg.h>

struct prova{
int v1;
int v2;
int v3;
int v4;
int v5;
};

void funzioneDue(int num, ...)
{
va_list vl;
va_start(vl, num);
int i;
for(i = 0; i < num; ++i)
{
printf("%d ", va_arg(vl, int));
}
va_end(vl);
printf("\n");
}

void funzioneUno(int num, ...)
{
int a[num];

va_list vl;
va_start(vl, num);
struct prova s;
s.v1=va_arg(vl,int);
s.v2=va_arg(vl,int);
s.v3=va_arg(vl,int);
s.v4=va_arg(vl,int);
s.v5=va_arg(vl,int);
/*for(int i = 0; i < num; ++i)
{
a[i] = va_arg(vl, int);
}*/
va_end(vl);

funzioneDue(num, s);
}

int main()
{
funzioneUno(5, 12, 13, 154, 123, 222);

return 0;
}

EDIT: il codice è ripulibile perché ho preso il tuo come base e non mi andava di cancellare le cose inutili

bottomap
26-11-2009, 20:43
Ciao,

Ma se anche passavi a per valore (a parte il problema di base che gli argomenti potrebbero avere un tipo eterogeneo - ad esempio int,char,string,int,int) il problema si riproponeva.
Passando a per puntatore ottieni di inserire un solo argomento (un puntatore) nella va_list della funzioneUno. Passando a per valore (ammettiamo sia fattibile) inserisci n argomenti nella va_list della funzioneUno.

Poi chiami la funzioneDue con cosa? Se passi un array passi un puntatore, se passi una va_list (quella della funzione uno) passi un puntatore. Non puoi passare altro in chiamata (non puoi passare in particolare un numero variabile di parametri per la sintassi stessa del linguaggio)... non hai qualcosa di simile ad una eval (ed è tutto sommato un bene).

Puoi al limite riempire lo stack via asm prima della chiamata con gli elementi della va_list (questo non te lo vieta nessuno) e poi fare la chiamata senza argomenti. Questo forse si, ma direi che alla fine il gioco non vale la candela...

Ciaociao :)

bottomap
26-11-2009, 20:45
Hai ragione, infatti questo funziona

Quel codice è specifico. Per ogni possibile conformazione di dati in ingresso devi dichiararti una struct apposita...

Certo se la tua funzione dovrà gestire sempre e solo 5 interi la cosa può andare bene, ma a quel punto non era più semplice passare un int a[5]; o una struttura con cinque interi? A che può servire una funzione generica che si basa su una conformazione specifica?

Il succo dell'utilità della notazione "..." è proprio poter gestire un qualsiasi numero di parametri ognuno con il suo tipo. Se poi la vuoi usare per gestire un solo caso preciso, perde di significato.

wingman87
26-11-2009, 20:51
Quel codice è specifico. Per ogni possibile conformazione di dati in ingresso devi dichiararti una struct apposita...
Certo se la tua funzione dovrà gestire sempre e solo 5 interi la cosa può andare bene, ma a quel punto non era più semplice passare un int a[5]; o una struttura con cinque interi? A che può servire una funzione generica che si basa su una conformazione specifica?
Sì, lo so che è specifico, ho solo scritto in codice quello che stava dicendo cionci, che mi pareva non si fosse capito. Comunque quando ricevi un numero di parametri variabile con ... in genere prima hai una stringa che ti dice quali sono i tipi dei parametri variabili. Ora se noi definiamo una struct abbastanza grossa possiamo simulare il passaggio di ... ad un'altra funzione che prende un argomento ... fino a un limite massimo di parametri variabili*.
Spero di essermi spiegato :)

EDIT: * dipendente anche dal tipo delle variabili passate

bottomap
26-11-2009, 21:02
Ciao,

Certamente (suonavo troppo aggressivo? In caso chiedo scusa), mi pare però un compito sostanzialmente inutile... supponi queste due ipotesi:

- La seconda funzione è una printf... accetta un numero di parametri variabili (ed eterogenei) quindi è compresa nell'ipotesi iniziale. Supponiamo che nella funzioneUno tu abbia una stringa, poi la stringa di formato e poi la va_list con i parametri e che voglia passare alla printf i parametri dal secondo in poi. Chiaramente la cosa non è fattibile perché non puoi passare una struct alla printf (o meglio puoi, ma ovviamente non avremo gli effetti desiderati)

- La seconda funzione è una tua versione custom della printf, che si basa su quanto detto e scrive a video (chiaramente con primitive che stampano solo stringhe semplici). Diciamo che ricevi la struct e sei pronto ad utilizzarla, ma qui nascono i problemi legati alla specificità:
-- il numero di parametri può variare (e qui l'ipotesi di allocare una struct "abbastanza grande" può ancora essere valida, ma quelli non utilizzati e/o da non utilizzare? Nella struttura nella migliore delle ipotesi saranno azzerati, il che può essere un valore numerico valido - devi aver modo di definire quale sia il range di valori da usare... al limite puoi ipotizzare una notazione pascal-like in cui il primo parametro è il numero di parametri)
-- i tipi dei parametri non è detto che siano omogenei (li potresti casomai desumere da un'analisi della stringa di formato), il che può avere il suo peso a seconda dei casi.

Poi la cosa effettivamente ha un suo senso, ma come detto mi sembra molto più semplice che la tua funzione customizzata accetti direttamente la struct in input o i parametri specifici. Non c'è alcuna necessità che l'insieme di parametri sia variabile se sai sempre a priori quanti e di che tipo sono.

Ciaociao :)

wingman87
26-11-2009, 21:12
Ciao,

Certamente (suonavo troppo aggressivo? In caso chiedo scusa)
No, scusami tu, in effetti sembrava che me la fossi presa :)

- La seconda funzione è una printf... accetta un numero di parametri variabili (ed eterogenei) quindi è compresa nell'ipotesi iniziale. Supponiamo che nella funzioneUno tu abbia una stringa, poi la stringa di formato e poi la va_list con i parametri e che voglia passare alla printf i parametri dal secondo in poi. Chiaramente la cosa non è fattibile perché non puoi passare una struct alla printf (o meglio puoi, ma chiaramente non avremo gli effetti desiderati)


E' qui che sbagli (se ho capito bene). Se voglio fare una printf customizzata che richiama la printf originale, supponendo di avere una struct sufficientemente grande a disposizione potrei procedere come segue:
-dichiarare una variabile s del tipo struct moltoGrande
-ricavare l'indirizzo di questa variabile
-copiarci con i metodi appositi tipo memcpy i vari parametri che mi sono stati passati
-passarlo alla printf originale
E funzionerebbe (come dimostra l'esempio sopra) se restassi entro i limiti di struct moltoGrande.

EDIT: aggiungerei: fa schifo? Sì. Ma sto solo teorizzando :P

bottomap
26-11-2009, 21:36
Ciao,

Si, hai pienamente ragione... pensavo erroneamente che una scritta come printf("%d %d %d %d %d",s) con una struct del tipo che hai descritto più sotto fosse interpretata in altra maniera, ma ho appena verificato ed è proprio come hai ipotizzato. Il fatto che la struct venga passata per valore e non per puntatore toglie di mezzo buona parte degli appunti che avevo fatto finora.
In compilazione ottieni qualche warning (C4313), ma in buona sostanza la cosa funziona.

Sul "fa schifo? Si" concordo, ma anch'io sto teorizzando :D :D

Ciaociao :)

cionci
26-11-2009, 22:05
Sì, lo so che è specifico, ho solo scritto in codice quello che stava dicendo cionci, che mi pareva non si fosse capito. Comunque quando ricevi un numero di parametri variabile con ... in genere prima hai una stringa che ti dice quali sono i tipi dei parametri variabili. Ora se noi definiamo una struct abbastanza grossa possiamo simulare il passaggio di ... ad un'altra funzione che prende un argomento ... fino a un limite massimo di parametri variabili*.
Spero di essermi spiegato :)

EDIT: * dipendente anche dal tipo delle variabili passate
Esattamente ;)
Utilizzando la struttura come un buffer di byte ed andandoci a scrivere oculatamente (rispettando l'endianess) si può passare un numero di parametri variabile con l'unico vincolo della dimensione della struttura. Ecco perché se si potesse definire una struttura dinamica, ma che venisse passata per valore si potrebbero passare quanti argomenti si vuole alla seconda funzione.
Ora il problema con C++ credo che sia il costruttore di copia...viene richiamato nel caso di passaggio di un oggetto ad una funzione che prende un numero variabile di argomenti ? Se viene chiamato, e suppongo di sì, allora il problema è risolto in C++.
Faccio qualche prova...

bottomap
27-11-2009, 07:42
Ciao,

Si, se finisce sullo stack viene richiamato (per farne appunto una copia sullo stack).

Se lo piazzi in una struct di bytes con una memcpy (e poi passi la struct) chiaramente non verrà richiamato... per passare n oggetti (magari di diverso tipo) alla funzioneDue il meccanismo con la struct funziona ma non viene invocato il costruttore di copia (bisognerebbe che la struct contenesse un Oggetto del tipo indicato - ma si ritorna al problema originale).

Ciaociao :)