Ven! Te invito a aprender algo acerca de los Argumentos Variables en C y el uso de la función alloca()...
Muy buen día estimados amigos lectores. Hoy regresamos con todos los ánimos del mundo para conversarles aspectos importantes en relación a la forma de utilizar funciones que admiten un número de argumentos variables en el lenguaje C, así como el uso de la función alloca y el impacto que tiene el uso de ésta en el stack frame.
Según lo que investigamos no es algo tan trivial el uso de argumentos variables en C, pero también no es cosa del otro mundo, solo que tenemos que seguir algunas reglas para realizar su programación.
Para que puedan llegar a comprender mejor como se realiza este proceso, los vamos a hacer revisando el siguiente ejemplo.
En primer lugar, vamos a utilizar un archivo de cabecera llamado stdarg.h, en el cual se encuentran unas macros que nos serán de mucha utilidad en todo este proceso. La sentencia que tendremos que escribir en el área de nuestros includes es la siguiente:
#include
A continuación explicamos en que consiste cada macro:
void va_start(va_list pa, last): hace que pa apunte al último argumento justo antes de que comience la lista de argumentos variables.
tipo va_arg(va_list pa, tipo): devuelve el valor del siguiente argumento apuntado por pa y de tipo tipo.
void va_end(va_list pa): manipula un retorno normal de la función cuya lista de argumentos variables fue inicializada por va_start.
El argumento last, el cual se le envía a la macro o función va_start, es el ultimo argumento que conoce la función, es decir, el que esta exactamente antes del primer argumento en la lista de argumentos variables.
Además es importante decir en que consiste el tipo de dato va_list, éste es un tipo de dato que define un puntero a la lista de argumentos variables.
Para tener una idea más exacta, a continuación mostraremos una porción de pseudo código que nos dirá cómo utilizar cada una de estas macros con el fin de programar una función que acepte argumentos variables.
tipo funcionVariable(last,...) {
va_list pa;
tipo_X argumento_de_tipo_X;
va_start(pa,last);
while (quedanArgumentos)
argumento_de_tipo_X = va_arg(pa, tipo_X);
va_end(pa);
}
Ahora explicaremos brevemente el pseudo código anterior:
Primero declaramos el apuntador a la lista de argumentos variables, llamada pa. Seguidamente, declaramos variables de diferentes tipos de datos, los cuales son los distintos tipos que podrá aceptar nuestra función. Ahora, procedemos a hacer que pa apunte al último argumento, ubicado justo antes del primer argumento en la lista de argumentos variables. Con esto, ya tendremos una ubicación exacta de donde comienzan los argumentos que no conocemos. Esto lo hacemos mediante la macro va_start.
Posteriormente en un ciclo while, recorreremos la lista de argumentos variables, con la ayuda de nuestra macro va_arg. Y para culminar, siempre colocamos el llamado a la macro va_end, quien se encarga de que el retorno de la función sea procesado sin problemas.
Existen diferentes formas de implementación de esta clase de argumentos en C. Por ejemplo, utilizando una variable que nos indique el numero de argumentos que recibirá la función, o lo que es similar, una cadena de formateo, al puro estilo de la función printf, la cual nos dirá cuántos argumentos espera la función y el orden de los mismos.
Y para llevar un poco de lo visto anteriormente a la práctica, enseguida mostramos una función con argumentos variables y su respectiva invocación, codificada en lenguaje C.
Esta función, como ustedes verán mas adelante, es una imitación de la función printf de C, en donde la variable formato es una cadena que nos especificará el orden en que se imprimirán los argumentos variables. En dicha función, los caracteres ?s representan una cadena y ?d un numero tipo entero. Pero sin más rodeos, veamos mejor el código:
void pprintf(char *formato, ...) {
char *p;
va_list pa;
va_start(pa, formato);
for (p = formato; *p; p++) {
if (*p != '?') {
putchar (*p);
continue;
}
switch (*++p) {
case 'd':
printf("(%d)", va_arg(pa, int));
break;
case 's':
printf("(%s)", va_arg(pa, char *));
break;
default:
putchar(*p);
break;
}
}
va_end(pa);
}
int main (void) {
pprintf("Probemos a poner paréntesis a una ?s y a un ?d \n", "cadena", 25);
return 0;
}
El retorno de la función pprintf es el siguiente:
”Probemos a poner paréntesis a una (cadena) y a un (25)”.
Y con esto finalizamos la explicación de cómo programar funciones con argumentos variables en C.
Por otra parte, con respecto a la función alloca() del lenguaje C, queremos decirles que ésta reserva memoria en el marco de pila de las funciones, la cual es implícitamente liberada al retornar.
Es decir, que alloca() reserva un espacio determinado en el stack frame a la hora en que se invoca una función, y una vez que la función termina de hacer su trabajo, o sea, que se produce el return, el espacio lo libera automáticamente.
La ventaja de usar esta función es el rendimiento, ya que es muy eficiente. Además, es oportuno mencionar que a esta función se le envía de parámetro el tamaño del área a reservar en el stack frame.
En adición, es importante citar, que cuando se use alloca() el espacio que se vaya a reservar no sea mayor al espacio libre en el stack frame, ya que si esto pasa, entonces se levanta una excepción llamada “core dump”.
Otro aspecto importante, es que para utilizar la función alloca() se debe de incluir el archivo de cabecera alloca.h en nuestro programa, ya que si este archivo no se incluye, se pierde una gran cantidad de rendimiento al ejecutar la función.
Y por ultimo, el espacio liberado por alloca() al termino de una función, puede ser reutilizado perfectamente para alojar otra función o para cualquier actividad similar.
Bueno amigos, esto es todo en cuanto a los argumentos variables y a la función alloca() del lenguaje C, pero también, quisiera comentarles algo con respecto al avance en la construcción de nuestro compilador micro-C.
En los últimos días, hemos estado realizando toda clase de validaciones en relación al análisis semántico, ya que a partir de este próximo lunes comenzamos a ver el tema de código Intermedio en la clase, por lo que para realizar la traducción a código intermedio es un requerimiento esencial la culminación de toda la parte semántica del compilador.
Hasta pronto!