valores una todas tipos retornan que parametros llamar lenguaje las funciones funcion ejemplos con como c gcc variadic-functions calling-convention

una - tipos de funciones en lenguaje c



¿Cómo se implementan los argumentos variables en gcc? (4)

A medida que se pasan los argumentos a la pila, las "funciones" va_ (son la mayoría de las veces implementadas como macros) simplemente manipulan un puntero de pila privado. Este puntero de pila privada se almacena desde el argumento pasado a va_start , y luego va_arg "saca" los argumentos de la "pila" mientras itera los parámetros.

Digamos que llama a la función max con tres parámetros, como este:

max(a, b, c);

Dentro de la función max , la pila básicamente se ve así:

+-----+ | c | | b | | a | | ret | SP -> +-----+

SP es el puntero de pila real, y en realidad no es a , c que está en la pila, sino sus valores. ret es la dirección de retorno, donde saltar cuando se realiza la función.

Lo que va_start(ap, n) toma es la dirección del argumento ( n en su prototipo de función) y, a partir de eso, calcula la posición del siguiente argumento, de modo que obtenemos un nuevo indicador de pila privada:

+-----+ | c | ap -> | b | | a | | ret | SP -> +-----+

Cuando usa va_arg(ap, int) , devuelve a lo que apunta el puntero de la pila privada, y luego lo "abre" cambiando el puntero de la pila privada para que apunte al siguiente argumento. La pila ahora se ve así:

+-----+ ap -> | c | | b | | a | | ret | SP -> +-----+

Esta descripción es, por supuesto, simplificada, pero muestra el principio.

int max(int n, ...)

Estoy usando la convención de llamadas cdecl en la que la persona que llama limpia la variable después de que la persona que llama regresa.

Me interesa saber cómo funcionan las macros va_end , va_start y va_arg ?

¿La persona que llama pasa la dirección de la matriz de argumentos como el segundo argumento de max?


De manera general, cómo trueno target.def, cuando se declara un prototipo de función con (, ...) el compilador configura un árbol de análisis marcado con un indicador de varargs y hace referencia a los tipos de los argumentos nombrados. Para una estricta conformidad con C, cada argumento con nombre debe obtener la información adicional necesaria para configurar una lista_contraída cuando ese parámetro es el campo nombrado de va_start y como un posible retorno a va_arg (), pero la mayoría de los compiladores simplemente generan esta información para el último argumento nombrado . Cuando se define la función, su generador de prólogo toma nota de que el indicador de varargs se estableció y agrega el código necesario para configurar los campos ocultos que agrega al marco que tiene las compensaciones conocidas a las que puede hacer referencia la macro va_start.

Cuando encuentra una referencia a esa función, crea árboles de generación de código y análisis adicionales para cada argumento que representa el ..., que puede introducir campos ocultos adicionales de información de tipo de tiempo de ejecución, como los límites de la matriz, que se adjunta a la configuración de los campos para va_start y va_arg para los argumentos nombrados. Este árbol combinado determina qué código se genera para copiar los valores de los parámetros en el marco, el prólogo configura lo que es necesario para que va_start cree un va_list a partir de un último parámetro arbitrario o nombrado, y cada invocación de va_arg () genera un código en línea que hace referencia a cualquier campo oculto específico de un parámetro utilizado para validar en el momento de la compilación la devolución esperada es compatible con el uso de la expresión que se está compilando, y realizar las promociones / coerciones de argumentos requeridas. La suma de los tamaños de los valores de los campos nombrados y los tamaños de los campos ocultos determina qué valor se compila después de la llamada, o en el epílogo de la función para los modelos de limpieza del destinatario, para ajustar el marco al regresar.

Cada uno de estos pasos tiene dependencias de procesador y convención de llamada, encapsuladas en los archivos config / proc / proc.c y proc.h, que anulan las definiciones predeterminadas simplistas de va_start () y va_arg () que asumen que cada argumento tiene un tamaño fijo asignado a cierta distancia por encima del primer argumento nombrado en una pila. Para algunas plataformas o lenguajes, los marcos de parámetros implementados como malloc () separados son más deseables que una pila de tamaño fijo. También tenga en cuenta que estos usos no son seguros para subprocesos; no es seguro pasar una referencia va_list a otro subproceso sin medios no especificados para garantizar que el marco del parámetro no se invalide debido al retorno de la función o al aborto del subproceso.


Si observa la forma en que el lenguaje C almacena los parámetros en la pila, la forma en que funcionan las macros debería quedar clara:

Higher memory address Last parameter Penultimate parameter .... Second parameter Lower memory address First parameter StackPointer -> Return address

(tenga en cuenta que, dependiendo del hardware, el puntero de la pila puede ser una línea hacia abajo y la parte superior e inferior pueden cambiarse)

Los argumentos siempre se almacenan como este 1 , incluso sin el tipo de parámetro ...

La macro va_start solo establece un puntero al primer parámetro de función, por ejemplo:

void func (int a, ...) { // va_start char *p = (char *) &a + sizeof a; }

lo que hace que p apunte al segundo parámetro. La macro va_arg hace esto:

void func (int a, ...) { // va_start char *p = (char *) &a + sizeof a; // va_arg int i1 = *((int *)p); p += sizeof (int); // va_arg int i2 = *((int *)p); p += sizeof (int); // va_arg long i2 = *((long *)p); p += sizeof (long); }

La macro va_end solo establece el valor p en NULL .

NOTAS:

  1. La optimización de los compiladores y algunas CPU RISC almacenan los parámetros en registros en lugar de usar la pila. La presencia del parámetro ... apagaría esta habilidad y el compilador usaría la pila.

int max(int n, const char *msg,...) { va_list args; char buffer[1024]; va_start(args, msg); nb_char_written = vsnprintf(buffer, 1024, msg, args); va_end(args); printf("(%d):%s/n",n,buffer); }