Behandelt GCC einen Zeiger auf eine va_list falsch, die an eine Funktion übergeben wird?

Lesezeit: 8 Minuten

Benutzer-Avatar
Jonathan Leffler

Die Frage ‘Va_list oder Zeiger auf va_list übergeben?’ hat eine Antwort, die den Standard zitiert (ISO/IEC 9899:1999 – §7.15 ‘Variable arguments <stdarg.h>Fußnote 212) ausdrücklich gesagt:

Es ist erlaubt, einen Zeiger auf a zu erzeugen va_list und übergeben Sie diesen Zeiger an eine andere Funktion, in welchem ​​Fall die ursprüngliche Funktion die ursprüngliche Liste weiter verwenden kann, nachdem die andere Funktion zurückkehrt.

Ich kompiliere einen Code, der durch das Folgende veranschaulicht werden kann (der echte Code ist sehr viel komplexer, wobei die ursprünglichen Funktionen viel mehr Arbeit leisten als hier gezeigt).

vap.c

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

static void test_ptr(const char *fmt, va_list *argp)
{
    int x;
    x = va_arg(*argp, int);
    printf(fmt, x);
}

static void test_val(const char *fmt, va_list args)
{
    test_ptr(fmt, &args);
}

static void test(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);   /* First use */
    test_val(fmt, args);
    va_end(args);
    va_start(args, fmt);   /* Second use */
    test_ptr(fmt, &args);
    va_end(args);
}

int main(void)
{
    test("%d", 3);
    return 0;
}

Fehlermeldungen

Wenn ich es kompiliere (auf RHEL5 mit GCC 4.1.2 oder 4.5.1), erhalte ich die folgenden Fehlermeldungen. Beachten Sie, wie viel informativer die Fehlermeldung von 4.5.1 ist – dem GCC-Team muss zu der Verbesserung gratuliert werden!

$ gcc --version
gcc (GCC) 4.5.1
Copyright (C) 2010 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ /usr/bin/gcc --version
gcc (GCC) 4.1.2 20080704 (Red Hat 4.1.2-44)
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ gcc -c vap.c
vap.c: In function ‘test_val’:
vap.c:13:5: warning: passing argument 2 of ‘test_ptr’ from incompatible pointer type
vap.c:4:13: note: expected ‘struct __va_list_tag (*)[1]’ but argument is of type ‘struct __va_list_tag **’
$ /usr/bin/gcc -c vap.c
vap.c: In function ‘test_val’:
vap.c:13: warning: passing argument 2 of ‘test_ptr’ from incompatible pointer type
$ 

Ich erhalte die gleichen Meldungen unter MacOS X Lion mit GCC/LLVM 4.2.1 und mit GCC 4.6.1:

$ /usr/bin/gcc --version
i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)
Copyright (C) 2007 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ gcc --version
gcc (GCC) 4.6.1
Copyright (C) 2011 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$

Fragen

  • Kann jemand artikulieren, warum die test_val() Funktion kann die nicht passieren va_list als Argument an übergeben test_ptr()während die test() Funktion (die die erstellt hat va_list) kann?

  • Ist GCC richtig, sich über die indirekte Übergabe des Zeigers zu beschweren? test_val()?

In beiden Fällen kann ich eine Antwort verschwommen sehen, aber ich kann sie nicht prägnant beschreiben. Ich denke, dass der Code in test_val() missbraucht die va_list und es ist gut, dass der Code nicht kompiliert wird – aber ich möchte sicher sein, bevor ich ihn behebe.


Aktualisierung 30.03.2012

Ich habe mich diese Woche mit dem problematischen Code befasst. Bevor ich Änderungen vornahm, suchte ich heraus, wo die bösartigen Funktionen verwendet werden – und das sind sie nicht! Also habe ich mein Kompilierungsfehlerproblem gelöst, indem ich die Funktionen entfernt habe (4 extern sichtbare, aber nicht verwendete, plus 2 statische, die den problematischen Code enthielten). Das war viel einfacher, als herauszufinden, wie man mit dem Schlamassel umgeht. (Dies erklärt auch, warum es nie Hinweise auf ein durch den Code verursachtes Laufzeitproblem gab.)

Benutzer-Avatar
Christoph

Dies ist ein bekanntes Problem. Auf einigen Architekturen (insbesondere x86-64) va_list muss komplexer sein als ein einfacher Zeiger auf den Stapel, beispielsweise weil einige Argumente in Registern oder auf andere Weise außerhalb des Bandes übergeben werden können (siehe diese Antwort für die Definition von va_list auf x86-64).

Auf solche Architekturen ist es üblich zu machen va_list ein Array-Typ, so dass Parameter vom Typ va_list auf Pointer-Typen angepasst, und statt der gesamten Struktur muss nur ein einzelner Pointer übergeben werden.

Dies sollte nicht gegen die C-Norm verstoßen, die nur das sagt va_list muss ein vollständiger Objekttyp sein und berücksichtigt sogar ausdrücklich, dass das Übergeben von a va_list Das Argument klont möglicherweise nicht den erforderlichen Zustand: va_list Objekte haben einen unbestimmten Wert, wenn sie als Argumente übergeben und in der aufgerufenen Funktion verwendet werden.

Aber selbst wenn machen va_list Ein Array-Typ ist zulässig, führt jedoch immer noch zu den Problemen, die Sie erlebt haben: Als Parameter des Typs va_list den ‘falschen’ Typ haben, z struct __va_list_tag * Anstatt von struct __va_list_tag [1]wird es in Fällen explodieren, in denen der Unterschied zwischen Arrays und Zeigern eine Rolle spielt.

Das eigentliche Problem ist nicht der Typkonflikt, vor dem gcc warnt, sondern die By-Pointer statt By-Value-Argumente, die die Semantik übergeben: &args in test_val() zeigt auf die Zwischenzeigervariable statt auf die va_list Objekt; das Ignorieren der Warnung bedeutet, dass Sie aufrufen va_arg() in test_ptr() auf die Zeigervariable, die Garbage (oder segfault, wenn Sie Glück haben) zurückgeben und den Stack beschädigen sollte.

Eine Problemumgehung besteht darin, Ihre zu umschließen va_list in einer Struktur und geben Sie diese stattdessen herum. Eine andere Lösung Ich habe in freier Wildbahn gesehenauch hier auf SO, zu verwenden va_copy um eine lokale Kopie des Arguments zu erstellen und dann einen Zeiger darauf zu übergeben:

static void test_val(const char *fmt, va_list args)
{
    va_list args_copy;
    va_copy(args_copy, args);
    test_ptr(fmt, &args_copy);
    va_end(args_copy); 
}

Dies sollte in der Praxis funktionieren, aber technisch gesehen kann es je nach Interpretation des Standards ein undefiniertes Verhalten sein oder auch nicht:

Wenn va_copy() als Makro implementiert ist, werden keine Parameteranpassungen durchgeführt, und das könnte eine Rolle spielen args ist nicht typ va_list. Allerdings so wie es ist nicht spezifiziert ob va_copy() ein Makro oder eine Funktion ist, könnte man zumindest argumentieren könnte eine Funktion sein und Parametereinstellungen werden implizit in dem für das Makro angegebenen Prototyp vorausgesetzt. Es kann sinnvoll sein, die Behörden um Klärung zu bitten oder sogar eine Mängelanzeige zu erstatten.

Sie könnten auch Ihr Build-System verwenden, um das Problem zu lösen, indem Sie ein Konfigurations-Flag wie definieren HAVE_VA_LIST_AS_ARRAY damit Sie für Ihre jeweilige Architektur das Richtige tun können:

#ifdef HAVE_VA_LIST_AS_ARRAY
#define MAKE_POINTER_FROM_VA_LIST_ARG(arg) ((va_list *)(arg))
#else
#define MAKE_POINTER_FROM_VA_LIST_ARG(arg) (&(arg))
#endif

static void test_val(const char *fmt, va_list args)
{
    test_ptr(fmt, MAKE_POINTER_FROM_VA_LIST_ARG(args));
}

  • Ich würde denken, dass das sogar der Standard zulassen würde va_copy als Funktion auf Plattformen implementiert werden soll, wo dies zu einer korrekten Semantik führen würde, würde es implizit erfordern, dass es als Makro implementiert wird, wenn die korrekte Semantik auf andere Weise nicht erreicht werden könnte.

    – Superkatze

    28. August 2015 um 23:06 Uhr

Benutzer-Avatar
Dan Aloni

Das Problem ist nicht spezifisch für va_list. Der folgende Code führt zu einer ähnlichen Warnung:

typedef char *test[1];

void x(test *a)
{
}

void y(test o)
{
    x(&o);
}

Das Problem ergibt sich aus der Art und Weise, wie C Funktionsvariablen behandelt, die auch Arrays sind, wahrscheinlich aufgrund der Tatsache, dass Arrays als Referenz und nicht als Wert übergeben werden. Die Art von o ist nicht dasselbe wie der Typ einer lokalen Variablen vom Typ testin diesem Fall: char *** Anstatt von char *(*)[1].

Um auf das ursprüngliche Problem zurückzukommen, besteht die einfache Möglichkeit, es zu umgehen, darin, eine Containerstruktur zu verwenden:

struct va_list_wrapper {
    va_list v;
};

und es gäbe keine Tippprobleme beim Übergeben eines Punktes.

  • Der Satz, der mit „Die Art von o„sollte enden char ** vs char *[1]. Die tatsächlich angegebenen Typen sind die Typen von &o gegen a.

    – MM

    2. April 2020 um 0:06 Uhr

Benutzer-Avatar
Café

Wie andere angemerkt haben, manifestiert sich dieses Problem, wenn va_list ist ein Array-Typ. Das erlaubt die Norm, die nur das sagt va_list muss ein sein “Objekttyp”.

Sie können das beheben test_val() funktionieren so:

static void test_val(const char *fmt, va_list args)
{
    va_list args_copy;

    /* Note: This seemingly unnecessary copy is required in case va_list
     * is an array type. */
    va_copy(args_copy, args);
    test_ptr(fmt, &args_copy);
    va_end(args_copy);
}

Ich finde va_list muss als Array-Typ deklariert werden, der zu einem Zeigertyp „reduziert“, wenn er als Parameter einer Funktion deklariert wird. deshalb, die & angewendet auf die va_list eintippen test_val liefert einen Zeiger auf einen Zeigertyp, jedoch keinen Zeiger auf einen Array-Typ test_ptr -Funktion deklariert einen ihrer Parameter als Zeiger auf den Array-Typ, was tatsächlich in der bereitgestellt wird test Funktion.

1371360cookie-checkBehandelt GCC einen Zeiger auf eine va_list falsch, die an eine Funktion übergeben wird?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy