2011-03-11 13 views
7

¿Cómo pueden las funciones variadas como printf averiguar la cantidad de argumentos que obtuvieron?¿Cómo encuentran las funciones vararg el número de argumentos en el código máquina?

La cantidad de argumentos, obviamente, no se pasa como un parámetro (oculto) (ver a call to printf in asm example here).

¿Cuál es el truco?

+0

Para 'printf' * et al *, usted sabe cuántos argumentos adicionales esperar dependiendo de la cadena de formato. –

+0

Esperaría que se empuje una cantidad similar a la cantidad de argumentos después de todos los argumentos, pero ese no es el caso en las listas a las que se vincula. – sharptooth

Respuesta

10

El truco es que les dices de alguna manera. Para printf, debe proporcionar una cadena de formato que incluso contiene información de tipo (aunque puede ser incorrecta). La forma de suministrar esta información es principalmente por contrato de usuario y, a menudo, propensa a errores.

En cuanto a las convenciones de llamadas: por lo general, los argumentos se colocan en la pila de izquierda a derecha y, a continuación, en la dirección de backjump. La rutina de llamada borra la pila. Por lo tanto, no hay necesidad técnica de que la rutina llamada conozca la cantidad de parámetros.

EDITAR: En C++ 0x hay una manera segura (incluso segura) de invocar funciones variadas!

+0

Ah, ese es el truco, simplemente asume que la cadena de formato es correcta. ¿Qué es ese tipo de manera segura? – masterxilo

+1

http://en.wikipedia.org/wiki/C%2B%2B0x#Variadic_templates @masterxilo – coldfix

+0

@OP C++ también es "críptico" (más difícil de desmontar). Es por eso que amo este lenguaje. –

9

Implícitamente, a partir de la cadena de formato. Tenga en cuenta que stdarg.h no contiene ninguna macro para recuperar el número total de "variables" de argumentos pasados. Esta es también una de las razones por las que la convención de llamadas C requiere que la persona que llama limpie la pila, aunque esto aumenta el tamaño del código.

7

Esta es la razón por la cual los argumentos son empujados en orden inverso en la convención de llamada C, por ejemplo:

Si llama:

printf("%s %s", foo, bar); 

La pila termina como:

... 
+-------------------+ 
| bar    | 
+-------------------+ 
| foo    | 
+-------------------+ 
| "%s %s"   | 
+-------------------+ 
| return address | 
+-------------------+ 
| old frame pointer | <- frame pointer 
+-------------------+ 
    ... 

Los argumentos se acceden indirectamente utilizando su desplazamiento desde el puntero de marco (el puntero de marco puede omitirse mediante compiladores inteligentes que saben cómo calcular cosas desde el puntero de pila). El primer argumento siempre se encuentra en una dirección conocida en este esquema, la función accede a tantos argumentos como le indiquen sus primeros argumentos.

intente lo siguiente:

printf("%x %x %x %x %x %x\n"); 

Esto volcar parte de la pila.

+0

En la convención de llamadas SystemV x86-64, los primeros 6 args de entero/puntero se pasan en registros, por lo que solo el último '% x' obtendrá printf para tomar un valor de la memoria de la pila. –

3
  • El AMD64 System V ABI (Linux, Mac OS X) ¿Se pasa el vector número (VER/AVX) varargs en rax, a diferencia de IA-32. Ver también: Why is %eax zeroed before a call to printf?

    TODO ¿por qué es necesario? Creo que es solo por motivos de rendimiento, para evitar guardar registros SSE innecesarios en el "Área Guardar registro" mencionado en "3.5.7 Listas de argumentos de variables".

  • En el nivel C, también hay otras técnicas además de analizar la cadena de formato como se mencionó por otros. Usted podría también:

    • pasar un centinela (void *)0 para indicar el último argumento como execl hace.

      Usted tendrá que usar el atributo sentinel función para ayudar a GCC hacer cumplir que en tiempo de compilación: C warning Missing sentinel in function call

    • pase como un argumento entero extra con el número de varargs

    • utilizar el atributo format función para ayudar a hacer cumplir GCC cadenas de formato de los tipos conocidos como printf o strftime

+0

Permite a la persona que llama pasar 'dobles' /' __m128'/'__m256' args en la pila incluso si aún no se utilizan todos los' xmm0-xmm7'. No sé por qué es útil. Tal vez sea útil para algunas funciones de contenedor saber si hay alguna regla de xmm para preservar. Sin embargo, no creo que le permita agregar un FP arg a una lista varag antes de pasarlo a otra función (como por ejemplo 'foo (char * fmt, ...) {va_stuff; fprintf (stderr, fmt, 2.0, varargs)); '), porque no sabes en qué parte de la pila poner' xmm7' después de mover todo para liberar 'xmm0' para la primera FP arg. –

+0

gcc/clang actualmente solo verifica si 'al' es cero o no, y si no es cero, vuelcan todos los 8' xmm' registros a la pila. (En lugar de usar un ciclo con conteo regresivo de "al"). –

Cuestiones relacionadas