Suche…


Einführung

Variablenargumente werden von Funktionen der printf-Familie ( printf , fprintf usw.) und anderen verwendet, um zu ermöglichen, dass eine Funktion jedes Mal mit einer anderen Anzahl von Argumenten aufgerufen wird, daher der Name varargs .

Um Funktionen mithilfe der Variablenargumente zu implementieren, verwenden Sie #include <stdarg.h> .

Um Funktionen mit einer variablen Anzahl von Argumenten aufzurufen, stellen Sie sicher, dass es einen vollständigen Prototyp mit nachlaufenden Ellipsen im Gültigkeitsbereich gibt: void err_exit(const char *format, ...); zum Beispiel.

Syntax

  • void va_start (va_list ap, last ); / * Variadic Argumentverarbeitung starten; last ist der letzte Funktionsparameter vor den Ellipsen (“...”) * /
  • Typ va_arg (va_list ap, type); / * Nächstes variadisches Argument in der Liste erhalten; Vergewissern Sie sich, dass Sie den richtigen beworbenen Typ angeben * /
  • void va_end (va_list ap); / * Argumentverarbeitung beenden * /
  • va_copy ungültig (va_list dst, va_list src); / * C99 oder höher: Argumentliste, dh aktuelle Position in der Argumentverarbeitung, in eine andere Liste kopieren (zB um Argumente mehrfach zu übergeben) * /

Parameter

Parameter Einzelheiten
va_list ap Argumentzeiger, aktuelle Position in der Liste der variadischen Argumente
zuletzt Name des letzten nicht-variadischen Funktionsarguments, damit der Compiler den richtigen Ort für die Verarbeitung von variadischen Argumenten findet; darf nicht als register , Funktion oder Array-Typ deklariert werden
Art gefördert Typ des Arguments variadische (zB zum Lesen int für ein short int Argument)
va_list src aktueller zu kopierender Argumentzeiger
va_list dst neue Argumentliste, die ausgefüllt werden soll

Bemerkungen

Die Funktionen va_start , va_arg , va_end und va_copy sind eigentlich Makros.

va_start Sie sicher, dass Sie va_start immer zuerst und nur einmal aufrufen und va_end zuletzt und nur einmal sowie an jedem Exit-Punkt der Funktion aufrufen. Wenn Sie dies nicht tun, funktioniert dies möglicherweise auf Ihrem System, ist aber sicherlich nicht portierbar und führt daher zu Fehlern.

Achten Sie darauf, Ihre Funktion korrekt zu deklarieren, dh mit einem Prototyp, und beachten Sie die Einschränkungen für das letzte nicht-variadische Argument (nicht register , keine Funktion oder einen Array-Typ). Es ist nicht möglich, eine Funktion zu deklarieren, die nur variadische Argumente verwendet, da mindestens ein nicht-variadisches Argument erforderlich ist, um die Argumentverarbeitung starten zu können.

Wenn Sie va_arg aufrufen, müssen Sie den beförderten Argumenttyp anfordern, d. va_arg

  • short wird zu int (und unsigned short wird auch zu int befördert, es sei denn sizeof(unsigned short) == sizeof(int) . In diesem Fall wird es zu unsigned int .
  • float wird double .
  • signed char wird zu int ; unsigned char wird ebenfalls zu int befördert, es sei denn sizeof(unsigned char) == sizeof(int) , was selten der Fall ist.
  • char wird normalerweise zu int .
  • C99-Typen wie uint8_t oder int16_t werden ähnlich befördert.

Die Verarbeitung historischer (dh K & R) variadischer Argumente wird in <varargs.h> , sollte aber nicht verwendet werden, da sie veraltet ist. Die Verarbeitung variabischer Standardargumente (die hier beschriebene und in <stdarg.h> ) wurde in C89 eingeführt. va_copy Makro va_copy wurde in C99 eingeführt, wurde jedoch zuvor von vielen Compilern bereitgestellt.

Verwenden eines expliziten count-Arguments, um die Länge der va_list zu bestimmen

Bei jeder variadischen Funktion muss die Funktion wissen, wie sie die Liste der Variablenargumente interpretiert. Bei den Funktionen printf() oder scanf() teilt die scanf() der Funktion mit, was zu erwarten ist.

Die einfachste Technik besteht darin, eine explizite Anzahl der anderen Argumente zu übergeben (die normalerweise alle vom gleichen Typ sind). Dies wird in der variadischen Funktion im folgenden Code demonstriert, der die Summe einer Reihe von Ganzzahlen berechnet, wobei eine beliebige Anzahl von Ganzzahlen vorhanden sein kann, die Anzahl jedoch als Argument vor der Liste der Variablenargumente angegeben wird.

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

/* first arg is the number of following int args to sum. */
int sum(int n, ...) {
    int sum = 0;
    va_list it; /* hold information about the variadic argument list. */

    va_start(it, n); /* start variadic argument processing */
    while (n--)
      sum += va_arg(it, int); /* get and sum the next variadic argument */
    va_end(it); /* end variadic argument processing */

    return sum;
}

int main(void)
{
    printf("%d\n", sum(5, 1, 2, 3, 4, 5)); /* prints 15 */
    printf("%d\n", sum(10, 5, 9, 2, 5, 111, 6666, 42, 1, 43, -6218)); /* prints 666 */
    return 0;
}

Terminatorwerte verwenden, um das Ende von va_list zu bestimmen

Bei jeder variadischen Funktion muss die Funktion wissen, wie sie die Liste der Variablenargumente interpretiert. Der "traditionelle" Ansatz (am Beispiel von printf ) besteht darin, die Anzahl der Argumente im Vorfeld anzugeben. Dies ist jedoch nicht immer eine gute Idee:

/* First argument specifies the number of parameters; the remainder are also int */
extern int sum(int n, ...);

/* But it's far from obvious from the code. */
sum(5, 2, 1, 4, 3, 6)

/* What happens if i.e. one argument is removed later on? */
sum(5, 2, 1, 3, 6)  /* Disaster */

Manchmal ist es robuster, einen expliziten Terminator hinzuzufügen, wie beispielsweise die POSIX-Funktion execlp() . Hier ist eine weitere Funktion zum Berechnen der Summe einer Reihe von double Zahlen:

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

/* Sums args up until the terminator NAN */
double sum (double x, ...) {
  double sum = 0;
  va_list va;

  va_start(va, x);
  for (; !isnan(x); x = va_arg(va, double)) {
    sum += x;
  }
  va_end(va);

  return sum;
}

int main (void) {
  printf("%g\n", sum(5., 2., 1., 4., 3., 6., NAN));
  printf("%g\n", sum(1, 0.5, 0.25, 0.125, 0.0625, 0.03125, NAN));
}

Gute Terminatorwerte:

  • Ganzzahl (soll alle positiv oder nicht negativ sein) - 0 oder -1
  • Fließkommatypen - NAN
  • Zeigertypen - NULL
  • Aufzählertypen - ein besonderer Wert

Funktionen mit einer `printf ()` -ähnlichen Oberfläche implementieren

Argumentlisten mit variabler Länge werden häufig verwendet, um Funktionen zu implementieren, die einen dünnen Wrapper um die printf() Funktionsfamilie bilden. Ein solches Beispiel ist eine Reihe von Fehlerberichterstattungsfunktionen.

errmsg.h

#ifndef ERRMSG_H_INCLUDED
#define ERRMSG_H_INCLUDED

#include <stdarg.h>
#include <stdnoreturn.h>    // C11

void verrmsg(int errnum, const char *fmt, va_list ap);
noreturn void errmsg(int exitcode, int errnum, const char *fmt, ...);
void warnmsg(int errnum, const char *fmt, ...);

#endif

Dies ist ein nacktes Beispiel. Solche Pakete können sehr aufwendig sein. Normalerweise verwenden Programmierer entweder errmsg() oder warnmsg() , die selbst intern verrmsg() . Wenn jedoch jemand die Notwendigkeit hat, mehr zu tun, ist die exponierte verrmsg() Funktion nützlich. Sie könnten aussetzt es vermeiden , bis Sie eine Notwendigkeit für sie ( YAGNI - Yagni ), aber die Notwendigkeit entstehen schließlich (Sie sind gonna brauchen es - YAGNI).

errmsg.c

Dieser Code muss nur die variadischen Argumente an die Funktion vfprintf() um sie in einen Standardfehler vfprintf() . Sie meldet auch die Systemfehlermeldung, die der an die Funktionen übergebenen Systemfehlernummer ( errno ) entspricht.

#include "errmsg.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void
verrmsg(int errnum, const char *fmt, va_list ap)
{
    if (fmt)
        vfprintf(stderr, fmt, ap);
    if (errnum != 0)
        fprintf(stderr, ": %s", strerror(errnum));
    putc('\n', stderr);
}

void
errmsg(int exitcode, int errnum, const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    verrmsg(errnum, fmt, ap);
    va_end(ap);
    exit(exitcode);
}

void
warnmsg(int errnum, const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    verrmsg(errnum, fmt, ap);
    va_end(ap);
}

Mit errmsg.h

Jetzt können Sie diese Funktionen wie folgt verwenden:

#include "errmsg.h"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    char buffer[BUFSIZ];
    int fd;
    if (argc != 2)
    {
        fprintf(stderr, "Usage: %s filename\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    const char *filename = argv[1];

    if ((fd = open(filename, O_RDONLY)) == -1)
        errmsg(EXIT_FAILURE, errno, "cannot open %s", filename);
    if (read(fd, buffer, sizeof(buffer)) != sizeof(buffer))
        errmsg(EXIT_FAILURE, errno, "cannot read %zu bytes from %s", sizeof(buffer), filename);
    if (close(fd) == -1)
        warnmsg(errno, "cannot close %s", filename);
    /* continue the program */
    return 0;
}

Wenn entweder der Systemaufruf open() oder read() fehlschlägt, wird der Fehler in den Standardfehler geschrieben und das Programm wird mit dem Exit-Code 1 beendet. Wenn der Systemaufruf close() fehlschlägt, wird der Fehler lediglich als Warnmeldung ausgegeben und Das Programm wird fortgesetzt.

Überprüfen der korrekten Verwendung von printf() -Formaten

Wenn Sie GCC (den GNU C-Compiler, der Teil der GNU-Compiler-Collection ist) oder Clang verwenden, können Sie den Compiler überprüfen lassen, ob die Argumente, die Sie an die Fehlermeldungsfunktionen übergeben, mit dem übereinstimmen, was printf() erwartet. Da nicht alle Compiler die Erweiterung unterstützen, muss sie bedingt kompiliert werden, was etwas fummelig ist. Der Schutz lohnt sich jedoch.

Zunächst müssen wir wissen, wie der Compiler GCC oder Clang ist, der GCC emuliert. Die Antwort ist, dass GCC __GNUC__ definiert, um __GNUC__ anzuzeigen.

Informationen zu den Attributen finden Sie unter Allgemeine Funktionsattribute - insbesondere das format .

errmsg.h umgeschrieben

#ifndef ERRMSG_H_INCLUDED
#define ERRMSG_H_INCLUDED

#include <stdarg.h>
#include <stdnoreturn.h>    // C11

#if !defined(PRINTFLIKE)
#if defined(__GNUC__)
#define PRINTFLIKE(n,m) __attribute__((format(printf,n,m)))
#else
#define PRINTFLIKE(n,m) /* If only */
#endif /* __GNUC__ */
#endif /* PRINTFLIKE */

void verrmsg(int errnum, const char *fmt, va_list ap);
void noreturn errmsg(int exitcode, int errnum, const char *fmt, ...)
        PRINTFLIKE(3, 4);
void warnmsg(int errnum, const char *fmt, ...)
        PRINTFLIKE(2, 3);

#endif

Nun, wenn Sie einen Fehler machen wie:

errmsg(EXIT_FAILURE, errno, "Failed to open file '%d' for reading", filename);

(Wenn %d %s ), wird der Compiler Folgendes bemängeln:

$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes \
>     -Wold-style-definition -c erruse.c
erruse.c: In function ‘main’:
erruse.c:20:64: error: format ‘%d’ expects argument of type ‘int’, but argument 4 has type ‘const char *’ [-Werror=format=]
         errmsg(EXIT_FAILURE, errno, "Failed to open file '%d' for reading", filename);
                                                           ~^
                                                           %s
cc1: all warnings being treated as errors
$

Verwenden einer Formatzeichenfolge

Die Verwendung einer Formatzeichenfolge enthält Informationen über die erwartete Anzahl und den Typ der nachfolgenden variadischen Argumente, sodass ein explizites count-Argument oder ein Terminatorwert nicht erforderlich ist.

Das folgende Beispiel zeigt eine Funktion, die die Standardfunktion printf() umschließt und nur die Verwendung von variadischen Argumenten des Typs char , int und double (im dezimalen Fließkommaformat) zulässt. Wie bei printf() ist hier das erste Argument der Wrapping-Funktion der Formatstring. Da die Formatzeichenfolge analysiert wird, kann die Funktion feststellen, ob ein anderes variadisches Argument erwartet wird und welchen Typ es haben soll.

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

int simple_printf(const char *format, ...)
{
    va_list ap; /* hold information about the variadic argument list. */
    int printed = 0; /* count of printed characters */

    va_start(ap, format); /* start variadic argument processing */

    while (*format != '\0') /* read format string until string terminator */
    {
        int f = 0;

        if (*format == '%')
        {
            ++format;
            switch(*format)
            {
                case 'c' :
                    f = printf("%d", va_arg(ap, int)); /* print next variadic argument, note type promotion from char to int */
                    break;
                case 'd' :
                    f = printf("%d", va_arg(ap, int)); /* print next variadic argument */
                    break;

                case 'f' :
                    f = printf("%f", va_arg(ap, double)); /* print next variadic argument */
                    break;
                default :
                    f = -1; /* invalid format specifier */
                    break;
            }
        }
        else
        {
            f = printf("%c", *format); /* print any other characters */
        }

        if (f < 0) /* check for errors */
        {
            printed = f;
            break;
        }
        else
        {
            printed += f;
        }
        ++format; /* move on to next character in string */
    }

    va_end(ap); /* end variadic argument processing */

    return printed;
}

int main (int argc, char *argv[])
{
    int x = 40;
    int y = 0;

    y = simple_printf("There are %d characters in this sentence", x);
    simple_printf("\n%d were printed\n", y);
}


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow