C
Zarc schrieb:
Hi,
ich verwende Funktionen mit variablen Parameterlisten nach folgendem Prinzip:
static int getIntValue(const int min,const int max,va_list *vl)
{
int listValue;
listValue=va_arg(*vl,int);
if (listValue<min) listValue=min;
else if (listValue>max) listValue=max;
return listValue;
}
unsigned long init_if_list(int *var,va_list vl)
{
char *listTag;
listTag=va_arg(vl,char*);
if (!strcmp(listTag,INIT_SHOWUI)) initValues.uiFlags=getIntValue(INT_MIN,INT_MAX,&vl);
Das compiliert und funktioniert problemlos mit VisualStudio 201x und GCC 4.7.x oder älter. Mit dem GCC 4.8.x unter Ubuntu/64 erhalte ich folgende Fehlermeldung:
error: cannot convert '__va_list_tag**' to '__va_list_tag (*)[1]' for argument '3' to 'int getIntValue(int, int, __va_list_tag (*)[1])'
if (!strcmp(listTag,INIT_SHOWUI)) initValues.uiFlags=getIntValue(INT_MIN,INT_MAX,&vl);
Der Grund ist etwas obskur. Wie im verlinkten Standardtext in Fußnote 253 erwähnt, ist es zulässig, einer Funktion einen Zeiger auf va_list zu übergeben damit diese mit dem gleichen va_list-Objekt weiterarbeiten kann.
Der Standard legt allerdings NICHt fest, welche Art Typ va_list hat, insbesondere darf es sich auch um einen Arraytyp handeln.
Das hat aber zur Folge, dass ein Funktionsparamter, der mit dem Typ va_list deklariert wurde, dann nicht tatsächlich den Typ va-list hat, wenn va_list ein Arraytyp ist. Die erwähnte Fußnote ist daher keine Lizenz dafür, ein va-list Funktionsargument per Zeiger an eine andere Funktion weiterzuleiten. Garantiert ist das nur, wenn das va_list-Objekt keinem Decay unterliegt, weil es etwa als lokales Objekt oder als Teil einer Struktur definiert wurde.
Eine einfach Möglichkeit, den Code zu reparieren ohne an den Funktionsdeklarationen etwas ändern zu müssen wäre z.B. wie folgt:
unsigned long init_if_list(int *var,va_list vl_param)
{
va_list vl;
char *listTag;
va_copy(vl, vl_param);
listTag=va_arg(vl,char*);
if (!strcmp(listTag,INIT_SHOWUI)) initValues.uiFlags=getIntValue(INT_MIN,INT_MAX,&vl);
Warum macht gcc 4.8 nun so etwas komisches? Der Grund hat tatsächlich nichts mit der Compilerversion zu tun (kanns zwar nicht testen aber vermutlich zeigt 4.7 das gleiche Verhalten unter x86_64) sondern mit der Zielarchitektur, für die übersetzt wird. Übersetze ich folgenden Code
#include <stdarg.h>
void foo(va_list*);
void bar(va_list ap)
{
foo(&ap);
}
mit gcc 4.9.3 64-bit erhalte ich den gleichen Fehler, mit -m32 verschwindet er hingegen. Der Grund ist darin zu suchen, das in der 64-bit ABI selbst Ellipsenargumente unter Umstämnden per Register übergeben werden, und die nat. nicht einfach per Zeiger erreicht werden können. Also ist hier zusätzliche Trickserei vom Compiler erforderlich. s.a. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=50581#c7
Zarc schrieb:
Ändere ich meinen Code jetzt dahingehend ab, das va_list nicht mehr als Pointer übergeben wird:
static int getIntValue(const int min,const int max,va_list vl)
{
int listValue;
listValue=va_arg(vl,int);
if (listValue<min) listValue=min;
else if (listValue>max) listValue=max;
return listValue;
}
unsigned long init_if_list(int *var,va_list vl)
{
char *listTag;
listTag=va_arg(vl,char*);
if (!strcmp(listTag,INIT_SHOWUI)) initValues.uiFlags=getIntValue(INT_MIN,INT_MAX,vl);
schmiert mir der Krempel mit VisualStudio und GCC 4.7 Code ab.
Nicht überraschend. Vermutlich wird vl in init_if_list im nicht gezeigten Teil mit va_arg weiterverwendet, was undefiniert ist (s.o.).