C Language
Variabele argumenten
Zoeken…
Invoering
Variabele argumenten worden gebruikt door functies in de printf-familie ( printf
, fprintf
, enz.) En anderen om een functie toe te staan met een ander aantal argumenten elke keer, vandaar de naam varargs .
Gebruik #include <stdarg.h>
om functies te implementeren met de functie variabele argumenten.
Om functies aan te roepen die een variabel aantal argumenten aannemen, moet u ervoor zorgen dat er een volledig prototype is met de volgende ellips in scope: void err_exit(const char *format, ...);
bijvoorbeeld.
Syntaxis
- void va_start (va_list ap, laatste ); / * Start variadische argumentverwerking; laatste is de laatste functieparameter vóór de ellips ("...") * /
- type va_arg (va_list ap, type ); / * Krijg volgende variadisch argument in lijst; zorg ervoor dat u het juiste gepromoveerde type * /
- void va_end (va_list ap); / * Einde argumentverwerking * /
- void va_copy (va_list dst, va_list src); / * C99 of hoger: kopieer argumentenlijst, dwz huidige positie in argumentverwerking, naar een andere lijst (bijv. Om argumenten meerdere keren over te slaan) * /
parameters
Parameter | Details |
---|---|
va_list ap | argument pointer, huidige positie in de lijst met variadische argumenten |
laatste | naam van het laatste niet-variadische functieargument, zodat de compiler de juiste plaats vindt om te beginnen met het verwerken van variadische argumenten; mag niet worden gedeclareerd als een register , een functie of een arraytype |
type | gepromoveerd type variadisch argument om te lezen (bijvoorbeeld int voor een short int argument) |
va_list src | huidige argumentwijzer om te kopiëren |
va_list dst | nieuwe argumentlijst die moet worden ingevuld |
Opmerkingen
De va_start
, va_arg
, va_end
en va_copy
zijn eigenlijk macro's.
Zorg ervoor dat u altijd eerst va_start
, en slechts eenmaal, en dat u va_end
laatste en slechts eenmaal va_end
, en op elk exitpunt van de functie. Als u dit niet doet, kan dit op uw systeem werken, maar het is zeker niet draagbaar en nodigt dus uit tot bugs.
Zorg ervoor dat u uw functie correct aangeeft, dwz met een prototype, en let op de beperkingen op het laatste niet-variadische argument (geen register
, geen functie of arraytype). Het is niet mogelijk om een functie te declareren die alleen variadische argumenten gebruikt, omdat er ten minste één niet-variadisch argument nodig is om de argumentverwerking te kunnen starten.
Wanneer u va_arg
, moet u het gepromoveerde argumenttype aanvragen, dat wil zeggen:
-
short
wordt gepromoveerd totint
(enunsigned short
wordt ook gepromoveerd totint
tenzijsizeof(unsigned short) == sizeof(int)
, in welk geval het wordt gepromoveerd totunsigned int
). -
float
wordt gepromoveerd totdouble
. -
signed char
wordt gepromoveerd totint
;unsigned char
wordt ook gepromoveerd totint
tenzijsizeof(unsigned char) == sizeof(int)
, wat zelden het geval is. -
char
wordt meestal gepromoveerd totint
. - C99-typen zoals
uint8_t
ofint16_t
worden op dezelfde manier gepromoot.
Historische (dwz K&R) variadische argumentverwerking wordt gedeclareerd in <varargs.h>
maar mag niet worden gebruikt omdat deze verouderd is. Standaard variadische argumentverwerking (die hier beschreven en verklaard in <stdarg.h>
) werd geïntroduceerd in C89; de va_copy
macro werd geïntroduceerd in C99 maar werd daarvoor door veel compilers geleverd.
Gebruik een expliciet telargument om de lengte van de va_list te bepalen
Bij elke variadische functie moet de functie weten hoe de lijst met variabele argumenten moet worden geïnterpreteerd. Met de functies printf()
of scanf()
geeft de opmaakreeks de functie aan wat te verwachten.
De eenvoudigste techniek is om een expliciete telling van de andere argumenten (die normaal allemaal van hetzelfde type zijn) door te geven. Dit wordt aangetoond in de variadische functie in de onderstaande code die de som van een reeks gehele getallen berekent, waarbij er een willekeurig aantal gehele getallen kan zijn, maar dat aantal wordt opgegeven als een argument voorafgaand aan de lijst met variabele argumenten.
#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;
}
Terminatorwaarden gebruiken om het einde van va_list te bepalen
Bij elke variadische functie moet de functie weten hoe de lijst met variabele argumenten moet worden geïnterpreteerd. De "traditionele" benadering (geïllustreerd door printf
) is het vooraf opgeven van het aantal argumenten. Dit is echter niet altijd een goed 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 */
Soms is het robuuster om een expliciete terminator toe te voegen, geïllustreerd door de functie POSIX execlp()
. Hier is een andere functie om de som van een reeks double
getallen te berekenen:
#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));
}
Goede terminatorwaarden:
- geheel getal (verondersteld allemaal positief of niet-negatief te zijn) -
0
of-1
- drijvende komma types -
NAN
- pointer types -
NULL
- typen tellers - enige speciale waarde
Implementeren van functies met een `printf ()` -achtige interface
Een veelgebruikt gebruik van argumentlijsten met variabele lengte is om functies te implementeren die een dunne wikkel zijn rond de printf()
van functies. Een voorbeeld hiervan is een set functies voor foutrapportage.
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
Dit is een kaal voorbeeld; dergelijke pakketten kunnen veel ingewikkeld zijn. Normaal gesproken gebruiken programmeurs errmsg()
of warnmsg()
, die zelf verrmsg()
intern gebruiken. Als iemand echter meer moet doen, is de zichtbare verrmsg()
-functie nuttig. Je kon niet bloot te stellen totdat je een behoefte aan is ( YAGNI - u niet zult het nodig hebben ), maar de behoefte zal uiteindelijk ontstaan (je zult het nodig hebben - YAGNI).
errmsg.c
Deze code hoeft alleen de vfprintf()
argumenten door te sturen naar de vfprintf()
-functie voor uitvoer naar standaardfout. Het rapporteert ook het systeemfoutbericht dat overeenkomt met het systeemfoutnummer ( errno
) dat aan de functies is doorgegeven.
#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);
}
errmsg.h
Nu kunt u die functies als volgt gebruiken:
#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;
}
Als de open()
of read()
systeemaanroepen mislukken, wordt de fout naar standaardfout geschreven en wordt het programma afgesloten met exitcode 1. Als de systeemaanroep close()
mislukt, wordt de fout alleen afgedrukt als een waarschuwingsbericht en het programma gaat verder.
Controle van het juiste gebruik van printf()
formaten
Als u GCC gebruikt (de GNU C Compiler, die deel uitmaakt van de GNU Compiler Collection), of Clang gebruikt, kunt u de compiler laten controleren of de argumenten die u doorgeeft aan de foutberichtfuncties overeenkomen met wat printf()
verwacht. Aangezien niet alle compilers de extensie ondersteunen, moet deze voorwaardelijk worden gecompileerd, wat een beetje lastig is. De bescherming die het biedt, is echter de moeite waard.
Eerst moeten we weten hoe we kunnen detecteren dat de compiler GCC is of Clang GCC emuleert. Het antwoord is dat GCC __GNUC__
definieert om dat aan te geven.
Zie gemeenschappelijke functie attributen voor meer informatie over de attributen - in het bijzonder de format
attribuut.
errmsg.h
#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
Als u nu een fout maakt, zoals:
errmsg(EXIT_FAILURE, errno, "Failed to open file '%d' for reading", filename);
(waar de %d
%s
moet zijn), dan zal de compiler klagen:
$ 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
$
Een opmaakreeks gebruiken
Het gebruik van een opmaakreeks geeft informatie over het verwachte aantal en type van de volgende variadische argumenten op een zodanige manier dat de noodzaak van een expliciet telargument of een terminatorwaarde wordt vermeden.
Het onderstaande voorbeeld toont een functie die de standaardfunctie printf()
omhult, waarbij alleen variadische argumenten van het type char
, int
en double
(in decimaal drijvend puntformaat). Hier, net als bij printf()
, is het eerste argument voor de omloopfunctie de opmaakreeks. Aangezien de opmaakreeks wordt ontleed, kan de functie bepalen of er een ander variadisch argument wordt verwacht en wat het type moet zijn.
#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);
}