Printf con formato y parámetros incomparables.

Estoy tratando de entender la función printf.

Después de haber leído sobre esta función, sé que el comstackdor c convierte automáticamente todos los parámetros que son más pequeños que int, como chars y short a int. También sé que long int largo (8 bytes) no se lanza y se empuja a la stack como está.

así que escribí este código c simple:

#include  int main() { long long int a = 0x4444444443434343LL; // note that 0x44444444 is 4 times 0x44 which is D in ascii. // and 0x43434343 is 4 times 0x43 which is C in ascii. printf("%c %c\n", a); return 0; } 

que crea una variable cuyo tamaño es de 8 bytes y la empuja a la stack. También sé que el printf se desplaza a través de la cadena de formato y cuando ve% c incrementará el puntero en 4 (porque sabe que un char se convirtió a int – ejemplo a continuación)

algo como: char c = (char) va_arg (list, int) ->

(* (int *) ((puntero + = sizeof (int)) – sizeof (int)))

como puede ver, obtiene los 4 bytes cuando el puntero apunta, y lo incrementa en 4

Mi pregunta es:

en mi lógica, debería imprimirse en el CD de little endian machines, esto no es lo que sucede y me pregunto por qué. Estoy seguro de que algunos de ustedes saben más que yo acerca de la implementación y por eso le hago esta pregunta.

EDITAR: el resultado real es C con algunos caracteres de basura que lo siguen.

Sé que algunos podrían decir que es un comportamiento indefinido: realmente depende de la implementación y solo quiero saber la lógica de la implementación.

Su lógica habría explicado el comportamiento de los comstackdores de C tempranos en los años 70 y 80. Las ABI más nuevas utilizan una variedad de métodos para pasar argumentos a funciones, incluidas las funciones de argumentos variables. Debe estudiar el ABI de su sistema para comprender cómo se pasan los parámetros en su caso, deducir de las construcciones que tienen un comportamiento explícito indefinido no ayuda.

Por cierto, los tipos más cortos que int no se lanzan o lanzan, sino que se promueven a int . Tenga en cuenta que los valores float se convierten al double cuando se pasan a funciones de argumento variable. Los tipos no enteros y los tipos enteros más grandes que int se pasan de acuerdo con el ABI, lo que significa que pueden pasarse en registros regulares o incluso en registros especiales, no necesariamente en la stack .

printf basa en las macros definidas en para ocultar estos detalles de implementación y, por lo tanto, se pueden escribir de manera portátil para architectures con diferentes ABI y diferentes tipos de tamaño estándar.

Hay un malentendido fundamental aquí, como lo revela el comentario.

De acuerdo con la cadena de formato aquí, el comstackdor debe saber que se insertaron 4 bytes, convertir 4 bytes a char e imprimirlo …

Pero el problema es que no hay una regla que diga que C usa una sola stack direccionada por bytes para todo .

Diferentes architectures de procesadores pueden, y lo hacen, usar una variedad de técnicas para pasar argumentos a las funciones. Algunos argumentos pueden pasarse a una stack convencional, pero otros pueden pasarse en registros o mediante otras técnicas. Argumentos de diferentes tipos pueden pasarse en diferentes tipos de registros (32 vs. 64 bit, entero vs. coma flotante, etc.).

Obviamente, un comstackdor de C debe saber cómo pasar correctamente los argumentos de la plataforma para la que se está comstackndo. Obviamente, una función variadic como printf debe escribirse con cuidado para obtener sus argumentos variables correctamente, según la plataforma en la que se esté utilizando. Pero un especificador de formato como %d no lo repite, simplemente significa “saca 4 bytes de la stack y los trata como un int “. De manera similar, %c no significa “resaltar 4 bytes e imprimir el entero resultante como un carácter”. Cuando printf encuentra el especificador de formato %c o %d , debe organizar la búsqueda del siguiente argumento del tipo int , lo que sea necesario para hacerlo . Y si, de hecho, el siguiente argumento pasado por el código de llamada no fue del tipo int , por ejemplo si, como aquí, el siguiente argumento fue en realidad el tipo long long int , no hay manera de saber en general qué podría suceder.

Específicamente, cuando printf acaba de ver un especificador %d o %c , lo que hace internamente es el equivalente a llamar

 va_arg(argp, int) 

Y esto dice literalmente, “buscar el siguiente argumento del tipo int “. Y luego depende del autor de va_arg (y el rest de las funciones y macros declaradas en ) saber exactamente lo que se necesita para obtener el siguiente argumento del tipo int en esta plataforma en particular.

Claramente, es posible saber qué sucederá realmente en una plataforma en particular. (Obviamente, ¡el autor de va_arg tenía que saberlo!) Pero no lo descubrirás basándose en el lenguaje C en sí mismo, o haciendo suposiciones sobre lo que crees que debería suceder. Tendrá que leer acerca de ABI, la interfaz binaria de la aplicación, que especifica los detalles de las convenciones de llamada de funciones en su plataforma. Estos detalles pueden ser difíciles de encontrar, porque muy pocos progtwigdores realmente se preocupan por ellos.

Dije que ” printf debe escribirse con cuidado para obtener sus argumentos variables correctamente”, pero en realidad me he equivocado, porque como dije más adelante, “en realidad depende del autor de va_arg saber exactamente lo que se necesita”. Tienes razón, es posible escribir una implementación razonablemente portátil de printf . Hay un ejemplo en la lista de C Preguntas frecuentes .

Si desea saber más acerca de las convenciones de llamada de funciones, otro tema interesante para leer es sobre Interfaces de funciones extranjeras o FFI. (Por ejemplo, hay otra biblioteca libffi que te ayuda a, de manera portátil, realizar algunas tareas más exóticas involucradas en la manipulación de argumentos de funciones).

Simplemente hay demasiados tipos de notas

C especifica 11 tipos enteros con signed char , char , … unsigned long long como tipos distintos. Aparte de que char debe coincidir con signed char o unsigned char , estos podrían implementarse como 10 codificaciones diferentes o solo 2 (Use 64 bits firmados o sin firmar para todos). La biblioteca estándar tiene un especificador printf() para cada uno de esos 11. (Debido a promociones sub- int , hay preocupaciones adicionales).

Hasta ahora no hay problemas reales.

Sin embargo, C tiene muchos otros tipos con especificadores printf() :

 ju uintmax_t jd intmax_t zu size_t td ptrdiff_t PRIdLEASTN int_leastN_t where N is 8, 16, 32, 64 PRIuLEASTN uint_leastN_t PRIdN intN_ PRIuN uintN_t Many others 

En general, estos tipos adicionales podrían ser distintos o compatibles con los 11 anteriores.

Siempre que el código utilice estos otros tipos en un printf() , surgirá el problema distinto / compatible y evitará que muchos comstackdores detecten / proporcionen el mejor especificador de impresión coincidente sugerido.

1 Existen varias condiciones / limitaciones.