Ayer, alguien me mostró este código:
#include <stdio.h> int main(void) { unsigned long foo = 506097522914230528; for (int i = 0; i < sizeof(unsigned long); ++i) printf("%u ", *(((unsigned char *) &foo) + i)); putchar('\n'); return 0; }
Eso da como resultado:
0 1 2 3 4 5 6 7
Estoy muy confundido, principalmente con la línea en el ciclo for
. Por lo que puedo decir, parece que &foo
se está convirtiendo en un unsigned char *
y luego lo agrega i
. Creo que *(((unsigned char *) &foo) + i)
es una forma más detallada de escribir ((unsigned char *) &foo)[i]
, pero esto hace que parezca foo
, se está indexando un unsigned long
. Si es así, ¿por qué? El resto del bucle parece típico de imprimir todos los elementos de una matriz, por lo que todo parece indicar que esto es cierto. El elenco de unsigned char *
me confunde aún más. Intenté buscar sobre convertir tipos enteros a char *
específicamente en Google, pero mi investigación se atascó después de algunos resultados de búsqueda inútiles sobre convertir int
a char
, itoa()
, etc. 506097522914230528
imprime específicamente 0 1 2 3 4 5 6 7
, pero otros números parecen tener sus propios 8 números únicos que se muestran en la salida, y los números más grandes parecen llenar más ceros.
Como prefacio, este programa no necesariamente se ejecutará exactamente como lo hace en la pregunta, ya que exhibe un comportamiento definido por la implementación. Además de esto, ajustar ligeramente el programa también puede causar un comportamiento indefinido. Más información sobre esto al final.
La primera línea de la función main
define un unsigned long foo
como 506097522914230528
. Esto parece confuso al principio, pero en hexadecimal se ve así: 0x0706050403020100
.
Este número consta de los siguientes bytes: 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00
. A estas alturas, probablemente pueda ver su relación con la salida. Si todavía está confundido acerca de cómo se traduce esto en la salida, eche un vistazo al bucle for.
for (int i = 0; i < sizeof(unsigned long); ++i) printf("%u ", *(((unsigned char *) &foo) + i));
Suponiendo que un long
tiene 8 bytes, este ciclo se ejecuta ocho veces (recuerde, dos dígitos hexadecimales son suficientes para mostrar todos los valores posibles de un byte, y dado que hay 16 dígitos en el número hexadecimal, el resultado es 8, por lo que el ciclo for corre ocho veces). Ahora la parte realmente confusa es la segunda línea. Piénselo de esta manera: como mencioné anteriormente, dos dígitos hexadecimales pueden mostrar todos los valores posibles de un byte, ¿verdad? Entonces, si pudiéramos aislar los dos últimos dígitos de este número, ¡obtendríamos un valor de byte de siete! Ahora, suponga que long
es en realidad una matriz que se ve así:
{00, 01, 02, 03, 04, 05, 06, 07}
Obtenemos la dirección de foo
con &foo
, la convertimos en un unsigned char *
para aislar dos dígitos, luego usamos la aritmética de punteros para obtener básicamente foo[i]
si foo
es una matriz de ocho bytes. Como mencioné en mi pregunta, esto probablemente parece menos confuso como ((unsigned char *) &foo)[i]
.
Un poco de advertencia: este programa exhibe un comportamiento definido por la implementación . Esto significa que este programa no necesariamente funcionará de la misma manera/dará el mismo resultado para todas las implementaciones de C. No solo es un largo de 32 bits en algunas implementaciones, sino que cuando declaramos el unsigned long
, la forma/el orden en que almacena los bytes de 0x0706050403020100
(también conocido como endianness ) también están definidos por la implementación. Crédito a @philipxy por señalar primero el comportamiento definido por la implementación. Este tipo de juego de palabras causa otro problema que señaló @Ruslan, que es que, si el long
se convierte en algo que no sea un char *
/ unsigned char *
, entra en juego la estricta regla de alias de C y obtendrá un comportamiento indefinido (Crédito del el enlace también va a @Ruslan). Más detalles sobre estos dos puntos en la sección de comentarios.