C Language
Variable Argumente
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 zuint
(undunsigned short
wird auch zuint
befördert, es sei dennsizeof(unsigned short) == sizeof(int)
. In diesem Fall wird es zuunsigned int
. -
float
wirddouble
. -
signed char
wird zuint
;unsigned char
wird ebenfalls zuint
befördert, es sei dennsizeof(unsigned char) == sizeof(int)
, was selten der Fall ist. -
char
wird normalerweise zuint
. - C99-Typen wie
uint8_t
oderint16_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);
}