Arbeiten mit variablen langen Argumentlisten C C++ C/C++ Arbeiten mit variablen langen Argumentlisten stdarg.h Ellipse va_list va_start va_arg va_end Arbeiten mit variablen langen Argumentlisten stdarg.h Ellipse va_list va_start va_arg va_end Kapitel 21: Arbeiten mit variablen langen Argumentlisten <stdarg.h>

In diesem Kapitel lernen Sie Möglichkeiten kennen, wie Sie Funktionen mit einer variablen Anzahl von Argumenten auf-rufen können.

Dass in C Funktionen mit variablen langen Argumentlisten aufgerufen werden können, dürfte Ihnen an Funktionen wie printf() oder scanf() bereits aufgefallen sein. Die Deklaration von printf() sieht wie folgt aus:

int printf(const char*format, ...);  

Anhand dieser Deklaration erkennen Sie auch, dass printf() mindestens ein festes Argument (const char *format) und eine variable Anzahl optionaler Argumente erwartet. Die variable Anzahl optionaler Argumente erkennen Sie an den drei Punkten, dem zweiten Parameter von printf(). Diese drei Punkte werden Ellipse genannt. Die Verwendung von printf() ohne weitere Argumente kennen Sie ja:

printf("ohne weitere Argumente\n"); 

Jetzt dasselbe mit zwei Argumenten:

printf("Ich habe %d %s\n",argument2,argument1);  

Anhand der beiden Formatzeichen %d und %s ist jetzt bekannt, dass dort zwei Argumente folgen.

Makros in <stdarg.h> - va_list, va_arg, va_start und va_end            zurück  Ein Kapitel tiefer  zum Inhaltsverzeichnis

Um eigene Funktionen mit einer variablen Argumentliste zu schreiben, sind in der Headerdatei folgende vier Makros deklariert:

Makro Syntax Bedeutung
va_list va_list name; abstrakter Datentyp, mit dem die Liste der Parameter definiert wird
va_start va_start(va_list name,lastarg); Argumentliste wird initialisiert
va_arg typ=va_arg(va_list name,typ); liest den nächsten Parameter in der Argumentliste
va_end void va_end(va_list name); schließt die Argumentliste

Tabelle 21.1: Makros der Headerdatei stdarg.h

21.2. Argumentliste am Anfang oder Ende kennzeichnen            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Diese vier Makros werden Sie jetzt anwenden. Es soll eine Funktion geschrieben werden, welche eine variable Anzahl von Argumenten erhält. Die Argumente (Ganzzahlen) werden dabei alle zu einer Summe addiert. Hier das Listing:

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

int add(int zahlen, ...)
{
   va_list zeiger;
   int zahl;

   va_start(zeiger,zahlen);

   do{
         zahl = va_arg(zeiger,int);
         zahlen += zahl;
      }while(zahl != 0);

   va_end(zeiger);
   return zahlen;
}

int main()
{
   int erg;
   printf("%d\n",add(11,12,13,0));
   printf("%d\n",add(99,66,33,22,11,0));
   erg = add(10, 13, 11, 0) + add(9, 8, 11, 0);
   printf("%d\n",erg);
   return 0;
}

Zuerst der Aufruf der Funktion mit:

printf("%d\n",summe(11,12,13,0)); 

So wird die Funktion mit den Argumenten 11, 12, 13 und 0 aufgerufen. Die Zahl 0 am Ende stellt die Abbruchbedingung da. Dies alles nun aber etwas genauer: Zuerst wird mit

va_list zeiger;

ein abstrakter Datentyp deklariert, welcher für die Verarbeitung der weiteren Funktion benötigt wird. Mit

va_start(zeiger,zahlen);

wird die Liste initialisiert. Man könnte auch sagen, der Datentyp zeiger verweist auf die Anfangsadresse der ersten Zahl der Argumentliste. Anschließend wird in der do while-Schleife mit

zahl=va_arg(zeiger,int);

der nächste Wert in der Argumentliste an die Variable zahl übergeben. In diesem Beispiel ist dies die Zahl 12. Diese Zahl wird zu dem ersten Wert von zah-len (11) addiert.

Ist die Bedingung der Schleife wahr (zahl != 0), fährt das Programm mit zahl=va_arg(zeiger,int) und dem nächsten Wert (13) fort und addiert diesen wieder mit zahlen. Beim nächsten Durchgang ist die while-Bedingung unwahr (zahl==0), und die Liste wird beendet mit

va_end(zeiger);

Anhand des ersten Funktionsaufrufs können Sie sich dies grafisch so vorstellen:

Abbildung 21.1: Zugriff einzelner Werte bei einer variablen Argumentliste
Abbildung 21.1: Zugriff einzelner Werte bei einer variablen Argumentliste

Jetzt soll das Programm so umgeschrieben werden, dass mit einer bestimmten Anzahl von Argumenten gearbeitet wird:

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

void print_zahlen(int anzahl, ...)
{
   va_list zeiger;
   int zahl,ergebnis=0;
   int i;
   printf("Es werden %d Zahlen ausgegeben\n",anzahl);
   va_start(zeiger,anzahl);
   for(i=1; i<=anzahl; i++)
      {
         zahl=va_arg(zeiger,int);
         printf("%d\t",zahl);
      }
   printf("\n");
   va_end(zeiger);
}

int main()
{
   print_zahlen(4,3,2,4,7);
   print_zahlen(6,11,22,33,44,55,66);
   return 0;
}

Das Programm ist ähnlich aufgebaut wie im Beispiel zuvor, nur dass hier als Abbruchbedingung das erste Argument verwendet wurde. Mit dem Funktionsaufruf

print_zahlen(6, 11, 22, 33, 44, 55, 66);

wird durch das erste Argument gekennzeichnet, dass die Funktion mit 6 Argumenten vom Typ int aufgerufen wird. Dies ist auch die Abbruchbedingung für die for-Schleife in der Funktion print_zahlen().

Diese Makros sind natürlich noch deutlich vielseitiger, als Beispiel sei die Funktion strcat() zum Anhängen eines Strings an einen anderen genannt. Häufig würden Sie sicherlich gern mehrere Strings auf einmal an einen anderen hängen. Dabei mussten Sie bislang immer mehrere strcat()-Aufrufe ausführen. Mit dem eben gezeigten Beispiel kann dies jetzt in einem Schritt realisiert werden:

strxcat(3, string, string1, string2);

Hiermit werden die beiden Strings string1 und string2 an den String string gehängt. Die Anzahl der Strings wird am Anfang der Argumentliste gekennzeichnet. Hierzu das Listing:

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#define MAX 50

void strxcat(int n_strings, ...)
{
   va_list zeiger;
   char *quelle, *ziel, *p;
   int counter=0;

   va_start(zeiger,n_strings);

   /*Nun auf den Zielstring*/
   ziel = va_arg(zeiger,char *);
   p = ziel;
   /*Am Ende vom Zielstring*/
   ziel+=strlen(ziel);

   if( (ziel-p) > MAX)
      {
         printf("!!!Max. Anzahl Zeichen Überschritten!!!\n");
         return;
      }

   while(--n_strings > 0)
      {
         /*Quelle einlesen*/
         quelle=va_arg(zeiger, char *);
         /*Jetzt Zeichen für Zeichen an ziel*/
         while(*quelle)
            {
               *ziel++ = *quelle++;
               if( (ziel-p) > MAX)
                  {
                     printf("!Max. Zeichen ueberschritten!\n");
                     exit(0);
                  }
            }
      }
   *ziel = '\0';
}

int main()
{
   char string[MAX] = "Test : ";
   char string2[] = " Und";

   strxcat(3, string, "hallo " , "welt");
   printf("%s\n",string);

   strxcat(5, string, string2, " noch", " ein", " Test");
   printf("%s\n",string);

   /*Und nun ein Fehler mit Absicht*/
   strxcat(4, string , " Ueberlauf", " von", " MAX");
   printf("%s\n",string);
   return 0;
}

Abbildung 21.2: Mehrere Strings auf einmal aneinander hängen
Abbildung 21.2: Mehrere Strings auf einmal aneinander hängen

Hier wurde auch eine Sicherung eingebaut, um sich vor so genannten Pufferüberläufen zu schützen.

21.3. vprintf, vsprintf, vfsprintf            zurück  Ein Kapitel höher  zum Inhaltsverzeichnis

Mit dem Makro va_arg werden die variablen Parameter einzeln verarbeitet. Mit den beiden Funktionen vprintf() und vfprintf() kann die ganze Liste in einem Stück übernommen werden. Dazu wird der Makroname va_arg nicht mehr benötigt. Hier die Syntax von vprintf():

int vprintf(char *format, va_list name); 

Jetzt soll hiermit die Funktion printf() nachgebildet werden. Der Code:

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

static void myprintf(char *string, ...)
{
   va_list argzeiger;
   va_start(argzeiger,string);
   vprintf(string,argzeiger);
   va_end(argzeiger);
}

int main()
{
   char hallo[] = "Hallo vprintf\n";
   myprintf("Hier ein Beispiel von vprintf....");
   myprintf("\n");
   myprintf("%d * %d = %d\n",10,10,10*10);
   myprintf("%s",hallo);
   return 0;
}

Der einzige Unterschied zu den vorigen Beispielen ist, dass hier anstatt va_arg() die Funktion vprintf() benutzt wird. Diese Funktion übernimmt den ganzen String in einem Stück. Natürlich macht dieses Programm wenig Sinn. vprintf() eignet sich sehr gut, um eigene Fehlermeldungsroutinen zu schreiben. Ein Beispiel:

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

#define MAXWERT 8192

enum{ WARN, ERROR, EXIT, MISC };

/* Stringtabelle mit Fehlerausgaben */
const char *error[] = {
         "Fehlerhafte Eingabe\n",
         "Maximaler Wertebereich ueberschritten\n",
         "Nagativer Wert wurde eingegeben\n"
                       };

void fehler(int kennung, const char *format, ...)
{
   va_list vargzeiger;
   va_start(vargzeiger,format);

   switch(kennung)
      {
         case 0 : printf("\nAchtung: ");
                  vprintf(format,vargzeiger);
                  break;
         case 1 : printf("\nFehler : ");
                  vprintf(format,vargzeiger);
                  break;
         case 2 : printf("\nProgrammabbruch : ");
                  vprintf(format,vargzeiger);
                  exit(0);
         case 3 : vprintf(format,vargzeiger);
                  break;
        default : printf("\nFalscher Funktionsaufruf\n");
      }
   va_end(vargzeiger);
}

int main()
{
   int zahl, ret;
   printf("Eine Zahl zwischen 0-8192: ");
   ret=scanf("%d",&zahl);
   /* Fehlerhafte Eingabe vorgenommen */
   if(ret == 0)
      fehler(EXIT, error[0]);
   /* Zahl größer als Maximalwert */
   else if(zahl > MAXWERT)
      fehler(WARN, error[1]);
   /* Negative Zahl */
   else if(zahl < 0)
      fehler(ERROR, error[2]);
   /* Alles in bester Ordnung */
   else
      fehler(MISC, "Eingabe ist in Ordnung\n");
   return 0;
}

Damit kann jederzeit mit dem Funktionsaufruf

fehler(kennungsnummer,"Fehler - Unbekannter Fehler"); 

eine bestimmte Fehlermeldung auf dem Bildschirm ausgegeben werden. Je nachdem, welche Kennungsnummer an die Funktion fehler() übergeben wurde. In diesem Beispiel wurden die Fehlernummern in enum-Variablen gekleidet und die entsprechende Fehlerausgabe in eine Stringtabelle.

Das dateiorientierte Gegenstück zu vprintf() ist die Funktion vfprintf() mit folgender Syntax:

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

int vfprintf(FILE *f, const char *puffer, va_list arg);

Diese Funktion ist gleichbedeutend mit der Funktion vprintf(), nur dass Sie dabei noch formatiert in einem Stream oder aber auch auf die Standardausgabe schreiben können mit:

vfprintf(stdout, format, vargzeiger);
/* gleich */
vprintf(format, vargzeiger);

Zudem existiert noch die Funktion vsprintf(), die ähnlich funktioniert wie sprintf(). Die Syntax:

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

int vsprintf(const char *puffer,const char *format,
             va_list argzeiger);

Damit kann eine variabel lange Argumentliste formatiert in einen String geschrieben werden. Ein kurzes Beispiel:

#include <stdio.h>
#include <stdarg.h>
/*  Bei Linux Bibliothek math.h extra hinzulinken (-lm)
 *  gcc -o programm programm.c -lm
 */
#include <math.h>

static void float2string(char *string, char *dezimal, ...)
{
   va_list argzeiger;
   va_start(argzeiger,dezimal);
   vsprintf(string,dezimal,argzeiger);
   va_end(argzeiger);
}

int main()
{
   char string[100];
   float zahl = 20.0;
   float2string(&string[0],"string-> %.6f <-string",sqrt(zahl));
   printf("%s\n",string);
   return 0;
}

Weiter mit Kapitel 22: Zeitroutinen            zum Inhaltsverzeichnis