mario::konrad
programming / C++ / sailing / nerd stuff
Variable Parameterlist
© 2002 / Mario Konrad

Einleitung

Dieses Tutorial zeigt, wie mit variablen Parameterlisten gearbeitet werden kann. Es ist ein mächtiges aber gefährliches Werkzeug. Deshalb findet es relativ selten Verwendung.

Trotzdem wollen wir uns in diesem Tutorial damit beschäftigen und anhand eines Beispiels zeigen wie dieses Werkzeug zu verwenden ist und welche Falles es bietet.

Für dieses Tutorial werden grundlegende Kenntnisse in der Programmiersprache C vorausgesetzt.

Die variablen Parameter

Wozu sind die gut?

Eines der Paradebeispiele für die Verwendung variabler Parameterlisten ist der Befehl printf:

int printf(char *, ...);

Wir kennen ihn in vielerlei Situationen. Hier sind einige Aufgeführt:

printf("hello world\n");
printf("Der Zaehler: %d\n", i);
printf("Ihr Name ist %s und Ihr Alter ist %d\n", name, age);

Gut zu erkennen ist hier, dass die Anzahl der Parameter nicht immer glich ist. Allerdings kann man sagen, dass immer mindestens ein Parameter angegeben werden muss. Diese Regel entspricht ja auch der oben aufgeführten Deklaration von printf.

Selbst anwenden

Werfen wir nun einen Blick auf ein kleines Programm und analysieren wir Schritt für Schritt die Verwendung und die Konsequenzen der variablen Parameterliste.

Als grundsätzliche Regel gilt: eine Funktion mit einer variablen Parameterliste, muss immer mindestens ein Parameter übernehmen.

Als erstes brauchen wir verschiedene headerfiles:

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

Gehen wir nun daran eine Funktion zu definieren, die eine variable Parameterliste verwendet. Als demo wollen wir ein printf ähnliche Funktion implementieren.

int trace(char * s, ...)
{

Es folgen ein paar Konstanten- und Variablendeklarationen die dann später gebraucht werden. Im Wesentlichen uninteressant, bis auf die Variable %%va_list ap%%:

    static const char DEC[] = "0123456789\0";
    static const char HEX[] = "0123456789abcdef\0";

    va_list ap;
    char * ptr;
    int i;
    int j;
    int k;
    char buf[16];
    int len = strlen(s);

Die Variable va_list ap wird für die variable Parameterliste verwendet und stellt die Liste selbst dar. Diese müss allerdings initialisiert werden:

    va_start(ap, s);

Obwohl der Parameter s nicht in der Parameterliste ap erscheint, wird er dennoch bei dessen Initialisierung verwendet (um die Speicherplätze der andern Parameter zu holen).

Gehen wir nun daran den übergebenen String s zu durchlaufen und, wie bei printf, die Platzhalter (gekennzeichnet mit %) entsprechend zu behandeln.

    for (i = 0; i < len; i++)
    {
        if (s[i] == '%' && i < len-1)
        {
            i++;
            switch (s[i])
            {
                case '%': /* % */
                    putchar('%');
                    break;

Soweit nichts Spektakuläres. Auch die variable Parameterliste ist bis jetzt noch nicht in Aktion getreten. Dies wird sich bei der Verarbeitung der folgenden Platzhalter (s: string, d: decimal, c: character, x: hexadecimal) ändern. Bei decimal und hexadecimal darf man sich nich in die irre führen lassen durch die Verarbeitung durch die Schleife. Die Idee dieser Funktion ist printf (oder Ähnliche wie sprintf, fprintf, etc.) unter keinen Umständen zu verwenden (sonst macht's ja gar keinen Sinn).

Das für uns relevante Statement ist va_arg. Mit dessen Hilfe wird aus der Liste das nächste Element geholt und der Listenzeiger wird um eine Stelle erhöt. Es ist also nicht möglich in der Liste zurückzugehen. Als Parameter übernimmt die Funktion va_arg die Parameterliste und einen Datentyp. Das geholte Element wird auf diesen Typ gewandelt und als Rückgabewert geliefert (eigtlich ist va_arg ein Makro, hat aber für die Anwendung keine Bedeutung). Beispiele:

Werfen wir nun einen Blick die Verwendung von %%va_arg%%:

                case 's': /* string */
                    ptr = va_arg(ap, char *);
                    if (!ptr) break;
                    while (*ptr) putchar(*ptr++);
                    break;
                case 'd': /* decimal, pos. and neg. */
                    j = va_arg(ap, int);
                    if (j < 0) putchar('-');
                    for (k = 15; k && j; --k)
                    {
                        buf[k] = DEC[j % 10];
                        j /= 10;
                    }
                    for (; k < 15; putchar(buf[++k]));
                    break;
                case 'c': /* character */
                    putchar(va_arg(ap, char));
                    break;
                case 'x': /* hexadecimal */
                    j = va_arg(ap, int);
                    for (k = 15; k && j; --k)
                    {
                        buf[k] = DEC[j % 16];
                        j /= 16;
                    }
                    for (; k < 15; putchar(buf[++k]));
                    break;

Der Rest der Verarbeitung ist trivial.

                default: /* UNKNOWN */
                    putchar(s[i]);
                    break;
            }
        }
        else putchar(s[i]);
    }

Im Prinzip ist es nicht unbedingt nötig, wird aber allgemein empfohlen: das Schliessen der Parameterliste mit va_end(ap).

    va_end(ap);
    return 0;
}

Nun fehlt noch die Funktion main, die den Aufruf unserer obiger Funktion enthält.

int main(int argc, char ** argv)
{
    trace("hello world [%s] %d %c 0x%x %%\n", "hallo", 10, '@', 85);
    return 0;
}

Zusammenfassung

Für die Verwendung einer variablen Parameterliste sind fünf Dinge zu verwenden:

Ferner sollte immer im Gedächnis bleiben, dass zwingend immer ein fixer Parameter vorhanden sein muss.

Download Source Code

Alle source code Files die auf dieser Seite publiziert werden, sind frei zu kopieren, modifizieren, weiterverbreiten und zu verwenden. Das copyright des Tutorials hat Mario Konrad.