2010-08-26 22 views
26

Cualquier programador C que ha estado trabajando durante más de una semana se ha encontrado con los accidentes que resultan de llamar printf con más especificadores de formato que argumentos reales, por ejemplo:pasar demasiados argumentos de printf

printf("Gonna %s and %s, %s!", "crash", "burn"); 

Sin embargo, ¿hay alguna cosas malas similares que pueden suceder cuando pasa demasiados argumentos para printf?

printf("Gonna %s and %s!", "crash", "burn", "dude"); 

Mi conocimiento de ensamblador x86/x64 me lleva a creer que esto es inofensivo, aunque no estoy convencido de que no hay una condición de borde que me falta, y no tengo ni idea acerca de otras arquitecturas. ¿Se garantiza que esta condición es inofensiva, o hay una trampa potencialmente inductora de colisiones aquí también?

+3

No es una respuesta a su pregunta, apilador de es correcta, pero para los accidentes. 'gcc' debería dar buenas advertencias sobre eso, así que realmente no hay excusa para pasarlo por alto ;-) –

+0

¿Cómo podría GCC dar advertencias al respecto? Tenga en cuenta que la cadena de formato no necesariamente tiene que ser una cadena constante. Puede ser cualquier 'char *' –

+1

GCC puede dar buenas advertencias cuando puede conocer la cadena de formato en tiempo de compilación. Como eso representa una gran franja de casos de uso racional para 'printf' y amigos, esas advertencias son valiosas y deben tenerse en cuenta. – RBerteig

Respuesta

12

Usted probablemente conoce el prototipo de la función printf como algo parecido a este

int printf(const char *format, ...); 

Una versión más completa de la que en realidad sería

int __cdecl printf(const char *format, ...); 

El __cdecl define la "convención de llamada", que, junto con otras cosas, describe cómo se manejan los argumentos. En este caso, significa que los argumentos se insertan en la pila y que la función que realiza la llamada limpia la pila.

Una alternativa a _cdecl es __stdcall, hay otros. Con __stdcall, la convención es que los argumentos se envían a la pila y se limpian con la función que se llama. Sin embargo, hasta donde yo sé, no es posible para una función __stdcall aceptar una cantidad variable de argumentos. Eso tiene sentido ya que no sabría cuánta pila limpiar.

Lo largo y lo corto es que en el caso de las funciones __cdecl es seguro pasar tantas args como desee, ya que la limpieza se realiza en el código que realiza la llamada. Si tuviera que pasar demasiados argumentos a una función __stdcall, daría lugar a una corrupción de la pila. Un ejemplo de dónde podría suceder esto es si tienes el prototipo incorrecto.

Más información sobre convenciones de llamadas se puede encontrar en Wikipedia here.

+6

__cdecl es un Win32ism, creado por el hecho de que algunos viejos compiladores de DOS admitían las convenciones de llamadas C y pascal. – ninjalj

+1

@ninjalj, '__cdecl' solo es compatible con los compiladores de MS, pero la nota general sobre las convenciones de llamada es válida para todos los sistemas operativos. –

+0

@JSBangs: __cdecl también fue compatible con los compiladores Borland IIRC. Además, en la mayoría de los demás sistemas operativos, C utiliza solo la convención de llamadas C (de derecha a izquierda, el que llama limpia la pila), posiblemente con variantes para ISR, compatibilidad con otros compiladores y/o guardando los argumentos en registros (regparm de GCC). AFAIK, Win32 es la única plataforma donde puede seleccionar una convención de llamadas que no admita funciones vararg. – ninjalj

3

Todos los argumentos se insertarán en la pila y se eliminarán si se elimina el marco de la pila. este comportamiento es independiente de un procesador específico. (Solo recuerdo un mainframe que no tenía stack, diseñado en los 70) Entonces, sí, el segundo ejemplo no fallará.

3

printf está diseñado para aceptar cualquier número de argumentos. printf lee el especificador de formato (primer argumento) y extrae los argumentos de la lista de argumentos según sea necesario. Esta es la razón por la que faltan muy pocos argumentos: el código simplemente comienza a usar argumentos inexistentes, accediendo a la memoria que no existe, o alguna otra cosa mala. Pero con demasiados argumentos, los argumentos adicionales simplemente serán ignorados. El especificador de formato utilizará menos argumentos que se han aprobado en

+0

Para agregar a esto, su compilador también puede eliminar los parámetros adicionales si puede detectar que no se utilizan. Tendría que mirar la salida del conjunto para saber si los parámetros adicionales realmente se pasan a 'printf' o si se optimizan. – bta

+0

Si el compilador sabe con certeza que está llamando a algo que utiliza el lenguaje de formato de printf (y GCC tiene un atributo que puede usarse para decorar sus propias funciones tipo printf) entonces es en principio seguro hacer esta optimización. Todavía tendría que actuar como si hubiera calculado todos los parámetros en caso de que alguno de los no utilizados tuviera efectos secundarios. – RBerteig

27

Online C Draft Standard (n1256), sección 7.19.6.1, párrafo 2:.

La función fprintf escribe salida al flujo de datos apuntado por flujo, bajo el control de la cadena de apuntado por el formato que especifica cómo los argumentos posteriores son convertidos para la salida.Si no hay suficientes argumentos para el formato, el comportamiento es indefinido. Si el formato se agota mientras los argumentos permanecen, los argumentos en exceso son evaluados (como siempre) pero de lo contrario se ignoran. La función fprintf devuelve cuando se encuentra el final de la cadena de formato.

El comportamiento de todas las demás funciones *printf() es el mismo exceso de argumentos wrt a excepción de vprintf() (obviamente).

+0

¿Esto se aplica a las funciones variadas escritas por el usuario? – immibis

+0

@immibis: La razón principal por la que podría no serlo es que algunas convenciones de llamadas tienen argumentos de llamada en la pila cuando regresa. Esta regla esencialmente requiere la implementación para apoyar el caso general, o tiene un montón de soporte de propósito especial para 'printf'. La mayoría de los sistemas con una convención de llamadas de callee-pops no lo usan para funciones variadas, por esta razón. (Y también que la instrucción 'ret imm16' de x86 requiere que la cantidad de bytes a pop sea una constante en tiempo de compilación). 'printf' es casi el peor de los casos para la capacidad del que recibe la llamada para detectar el número de args: polimórfico, sin centinela –

+1

¡Esta debería ser la respuesta aceptada! ¿A quién le importa llamar a las convenciones aquí? Eso es solo un detalle para implementar el estándar. – Xlea

-2

Comentario: ambas advertencias gcc y producir sonido metálico:

$ clang main.c 
main.c:4:29: warning: more '%' conversions than data arguments [-Wformat] 
    printf("Gonna %s and %s, %s!", "crash", "burn"); 
          ~^ 
main.c:5:47: warning: data argument not used by format string 
         [-Wformat-extra-args] 
    printf("Gonna %s and %s!", "crash", "burn", "dude"); 
     ~~~~~~~~~~~~~~~~~~     ^
2 warnings generated. 
+0

El problema es principalmente cuando genera su propia cadena de formato, que no se conoce en tiempo de compilación. – meneldal

Cuestiones relacionadas