Argumentos variables en C/C++
Febrero 9th, 2005 - [Enlace local]
Frecuentemente me he preguntado cómo se las apañan algunas funciones como printf () o scanf () para permitir al programador realizar llamadas con una lista indeterminada de argumentos. Ayer aprendí el método en el que, al parecer, se basan para funcionar. Existe una librería de C llamada stdarg.h la cual contiene un pequeño conjunto de macros y un tipo de datos preparado para definir funciones que trabajen con listas de argumentos variables, donde nosotros en la/s llamada/s indicaremos el número de argumentos (parámetros actuales) que necesitemos. Las herramientas que stdarg.h nos aporta son:
va_list: es el tipo de dato utilizado para declarar la variable que será utilizada con las macros que ofrecestdarg.h. Define un puntero a la lista de argumentos variable y en entornos como Dev-C++ o el compilador GCC se trata de un alias de un puntero a caracter (typedef char * va_list;).void va_start (va_list ap, last);: macro que se encarga de inicializar una variablepa(parámetro de argumentos) para poder utilizarla con el par de macros restantes.lastes un argumento fijo que hay que pasar siempre a la función, como primer parámetro, ya que le sirve como referencia ava_startpara saber dónde localizar la lista de argumentos variables (estos argumentos estarán en posiciones de memoria adyacentes alast, de ahí a que se le deba pasar siempre un argumento “estático” a la función).type va_arg (va_list ap, type);: macro con la que recorreremos la lista de argumentos variable. Devuelve el contenido del siguiente de estos argumentos.void va_end (va_list ap);: macro encargada de finalizar el uso de la variableappreviamente inicializada conva_start.
Bien, el mecanismo para emplear estas macros sin obtener resultados inesperados es básicamente siempre el mismo: lo primero de todo es declarar correctamente una función que utilizará lista de argumentos variable. Para ello se emplea un parámetro fijo inicial pasado por valor y, seguidamente, el operador especial ... (”puntos suspensivos”) que indica la lista de argumentos variable. Algo así:
tipo_f foo (tipo_p bar, ...);
Hago especial hincapie en el paso del parámetro bar puesto que como dije éste servirá de referencia a va_start para saber dónde encontrar el inicio de la lista de argumentos variable. Normalmente este parámetro fijo contendrá el número de argumentos con que la función fue llamada o, también, puede ser un puntero a la primera posición de una cadena de caracteres (como es el caso de printf () o scanf ()).
Lo siguiente es declarar una variable que sea del tipo de dato valist. Seguidamente se debe emplear vastart para inicializar dicha variable y, al final, emplear va_end. En conjunto quedaría algo así:
tipo_f foo (tipo_p bar, ...)
{
va_list pa;
tipo_p param;
...
va_start (pa, bar);
... por aquí se podría usar va_arg -> param = va_arg (pa, tipo_p);
va_end (pa);
return;
}
Esta técnica, a priori, parece no tener demasiadas aplicaciones prácticas en la vida real. A mí al menos no se me ocurren demasiadas y, tal vez, como curiosidad está la mar de bien
Aunque lo cierto es que creo que la utilización de funciones con lista de argumentos variables podría venir muy bien para procesos de refactorizaación de código fuente; para según que casos / funciones concretas que tengamos hechas tal vez pueda valer.
Es un tema interesante y seguramente no haya quedado del todo claro con mis escuetas explicaciones. Os recomiendo la lectura de un artículo muy logrado sobre el tema que justamente ayer encontré en El Rincón del Programador y con el que he aprendido a utilizar un poco este juego de macros que trae stdarg.h. El documento que digo es [ éste ] Además también se puede leer información práctica en las páginas man, [ aquí ]
Y ahora de la teoría a la práctica. Esta mañana en clase he estado jugando un poco con lo dicho anteriormente, sobre todo para afianzar lo aprendido ayer y hacer algún ejercicio chorra de prueba. El siguiente programilla lee por línea de comandos de dos a cuatro caracteres e imprime por pantalla una concatenación de ellos. Para cada número de argumentos, se utiliza una función que trabaja con una lista de argumentos variable. El código está hecho en plan guarro y, obviamente, solo sirve para liar un poco la cosa y aportar algún ejemplo algo más extraño a lo que personalmente he podido encontrar buscando por ahí (aunque, a fin de cuentas, es lo mismo).
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char * get_mem (int);
char * concatena (int, ...);
int main (int argc, char **argv)
{
size_t i, error = 0;
char * cad;
switch (argc)
{
case 3 :
cad = get_mem (argc);
strcpy (cad, concatena (argc, argv [1], argv [2]));
break;
case 4 :
cad = get_mem (argc);
strcpy (cad, concatena (argc, argv [1], argv [2], argv [3]));
break;
case 5 :
cad = get_mem (argc);
strcpy (cad, concatena (argc, argv [1], argv [2], argv [3], argv [4]));
break;
default :
error = 1;
break;
}
if (!error)
{
for (i = 1; i < argc; i++)
fprintf (stdout, \"(%s) \", argv [i]);
fprintf (stdout, \"-> %s\", cad);
free (cad);
}
else
fprintf (stderr, \"uso: %s car1 car2 [car3 car4]\", *argv);
putchar ('\n');
return 0;
}
char * get_mem (int args)
{
char * result = (char *) malloc (sizeof (char) * (--args));
if (result == NULL)
exit (1);
return result;
}
char * concatena (int args, ...)
{
char * result;
va_list pa;
result = get_mem (--args);
va_start (pa, args);
*result = '\0';
while (args--)
strcat (result, va_arg (pa, char *));
va_end (pa);
return result;
}
Lo dicho, recomendado: