C Language
Argumentos variables
Buscar..
Introducción
Los argumentos variables son utilizados por las funciones de la familia printf ( printf
, fprintf
, etc.) y otras para permitir que se llame a una función con un número diferente de argumentos cada vez, de ahí el nombre de varargs .
Para implementar funciones usando la característica de argumentos variables, use #include <stdarg.h>
.
Para llamar a funciones que toman un número variable de argumentos, asegúrese de que haya un prototipo completo con los puntos suspensivos finales en el alcance: void err_exit(const char *format, ...);
por ejemplo.
Sintaxis
- void va_start (va_list ap, última ); / * Iniciar el procesamiento de argumentos variadic; el último es el último parámetro de la función antes de los puntos suspensivos ("...") * /
- escriba va_arg (va_list ap, type ); / * Obtener el siguiente argumento variad en la lista; asegúrese de pasar el tipo correcto promovido * /
- void va_end (va_list ap); / * Procesamiento de argumento final * /
- void va_copy (va_list dst, va_list src); / * C99 o posterior: copie la lista de argumentos, es decir, la posición actual en el procesamiento de argumentos, en otra lista (por ejemplo, para pasar los argumentos varias veces) * /
Parámetros
Parámetro | Detalles |
---|---|
va_list ap | indicador de argumento, posición actual en la lista de argumentos variadic |
último | nombre del último argumento de función no variada, por lo que el compilador encuentra el lugar correcto para comenzar a procesar los argumentos variadic; no se puede declarar como una variable de register , una función o un tipo de matriz |
tipo | tipo promovido del argumento variadic para leer (por ejemplo, int para un short int argumento short int ) |
va_list src | argumento actual para copiar |
va_list dst | nueva lista de argumentos para completar |
Observaciones
Las va_start
, va_arg
, va_end
y va_copy
son en realidad macros.
Asegúrese de llamar siempre a va_start
primero, y solo una vez, y para llamar a va_end
último, y solo una vez, y en cada punto de salida de la función. No hacerlo puede funcionar en su sistema, pero seguramente no es portátil y, por lo tanto, invita a errores.
Tenga cuidado de declarar su función correctamente, es decir, con un prototipo, y tenga en cuenta las restricciones del último argumento no variado (no register
, no es una función o tipo de matriz). No es posible declarar una función que solo tome argumentos variables, ya que se necesita al menos un argumento no variable para poder iniciar el procesamiento de argumentos.
Al llamar a va_arg
, debe solicitar el tipo de argumento promovido , es decir:
-
short
se promueve aint
(yunsigned short
también se promueve aint
menos quesizeof(unsigned short) == sizeof(int)
, en cuyo caso se promueve aunsigned int
). - Se promueve
float
aldouble
. -
signed char
se promueve aint
;unsigned char
también se promueve aint
menos quesizeof(unsigned char) == sizeof(int)
, que rara vez es el caso. -
char
se suele ascendido aint
. - Los tipos C99 como
uint8_t
oint16_t
se promueven de manera similar.
El procesamiento del argumento variad histórico (es decir, K&R) se declara en <varargs.h>
pero no se debe usar porque está obsoleto. El procesamiento de argumentos variadic estándar (el descrito aquí y declarado en <stdarg.h>
) se introdujo en C89; la macro va_copy
se introdujo en C99 pero fue proporcionada por muchos compiladores antes de eso.
Usando un argumento de conteo explícito para determinar la longitud de la lista va
Con cualquier función variable, la función debe saber cómo interpretar la lista de argumentos variables. Con las funciones printf()
o scanf()
, la cadena de formato le dice a la función qué esperar.
La técnica más simple es pasar un conteo explícito de los otros argumentos (que normalmente son todos del mismo tipo). Esto se demuestra en la función variadic en el código siguiente, que calcula la suma de una serie de enteros, donde puede haber cualquier número de enteros, pero ese recuento se especifica como un argumento antes de la lista de argumentos variables.
#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;
}
Usando valores terminadores para determinar el final de va_list
Con cualquier función variable, la función debe saber cómo interpretar la lista de argumentos variables. El enfoque "tradicional" (ejemplificado por printf
) es especificar el número de argumentos por adelantado. Sin embargo, esto no siempre es una buena idea:
/* 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 */
A veces es más robusto agregar un terminador explícito, ejemplificado por la función execlp()
POSIX. Aquí hay otra función para calcular la suma de una serie de números double
:
#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));
}
Buenos valores de terminador:
- entero (se supone que es todo positivo o no negativo) -
0
o-1
- tipos de punto flotante -
NAN
- tipos de punteros -
NULL
- tipos de enumerador - algún valor especial
Implementando funciones con una interfaz similar a `printf ()`
Un uso común de las listas de argumentos de longitud variable es implementar funciones que son un envoltorio delgado alrededor de la familia de funciones printf()
. Un ejemplo de ello es un conjunto de funciones de informe de errores.
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
Este es un ejemplo escueto; Tales paquetes pueden ser mucho más elaborados. Normalmente, los programadores usarán errmsg()
o warnmsg()
, que a su vez usan verrmsg()
internamente. Sin embargo, si alguien tiene la necesidad de hacer más, entonces la función verrmsg()
expuesta será útil. Se podría evitar la exposición hasta que haya una necesidad de ella ( YAGNI - yagni ), pero la necesidad va a surgir con el tiempo (que está a necesitarlo - YAGNI).
errmsg.c
Este código solo necesita reenviar los argumentos variadic a la función vfprintf()
para generar un error estándar. También informa el mensaje de error del sistema correspondiente al número de error del sistema ( errno
) pasado a las funciones.
#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);
}
Utilizando errmsg.h
Ahora puedes usar esas funciones de la siguiente manera:
#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;
}
Si las llamadas del sistema open()
o read()
fallan, el error se escribe en el error estándar y el programa sale con el código de salida 1. Si falla la llamada al sistema close()
, el error simplemente se imprime como un mensaje de advertencia, y el programa continúa
Comprobando el uso correcto de los formatos printf()
Si está utilizando GCC (el compilador GNU C, que forma parte de la colección del compilador GNU), o está usando Clang, puede hacer que el compilador verifique que los argumentos que pasa a las funciones de mensaje de error coinciden con lo que espera printf()
. Dado que no todos los compiladores admiten la extensión, se debe compilar de forma condicional, lo cual es un poco complicado. Sin embargo, la protección que da merece el esfuerzo.
Primero, necesitamos saber cómo detectar que el compilador es GCC o Clang emulando GCC. La respuesta es que GCC define __GNUC__
para indicar eso.
Consulte los atributos de funciones comunes para obtener información sobre los atributos, específicamente el atributo de format
.
Reescrito 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
Ahora, si cometes un error como:
errmsg(EXIT_FAILURE, errno, "Failed to open file '%d' for reading", filename);
(donde %d
debería ser %s
), entonces el compilador se quejará:
$ 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
$
Usando una cadena de formato
El uso de una cadena de formato proporciona información sobre el número esperado y el tipo de los argumentos variables posteriores para evitar la necesidad de un argumento de recuento explícito o un valor de terminación.
El siguiente ejemplo muestra una función que envuelve la printf()
estándar printf()
, solo permite el uso de argumentos variables del tipo char
, int
y double
(en formato de coma flotante decimal). Aquí, como con printf()
, el primer argumento de la función de ajuste es la cadena de formato. A medida que se analiza la cadena de formato, la función puede determinar si se espera otro argumento variadic y cuál debería ser su tipo.
#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);
}