El estándar C especifica:
Un puntero a void tendrá los mismos requisitos de representación y alineación que un puntero a un tipo de carácter. De manera similar, los punteros a versiones cualificadas o no cualificadas de tipos compatibles tendrán los mismos requisitos de representación y alineación. Todos los punteros a tipos de estructuras tendrán los mismos requisitos de representación y alineación que los demás. Todos los punteros a tipos de unión tendrán los mismos requisitos de representación y alineación que los demás. No es necesario que los punteros a otros tipos tengan los mismos requisitos de representación o alineación.
es decir sizeof(int*) no es necesariamente igual a sizeof(char*) - pero sizeof(struct A*) es necesariamente igual a sizeof(struct B*) .
¿Cuál es la razón detrás de este requisito? Según tengo entendido, la razón detrás de los diferentes tamaños para los tipos básicos es admitir casos de uso como punteros cercanos/lejos/enormes ( editar : como se señaló en los comentarios y en la respuesta aceptada, esta no es la razón), pero no lo hace ¿Esta misma razón se aplica a struct s en diferentes ubicaciones en la memoria?
La respuesta es muy simple: los tipos de struct y union se pueden declarar como tipos opacos, es decir, sin una definición real de la struct o los detalles de la union . Si la representación de los punteros fuera diferente según los detalles de las estructuras, ¿cómo determinaría el compilador qué representación usar para los punteros opacos que aparecen como argumentos, valores devueltos o incluso simplemente leerlos o almacenarlos en la memoria?
La consecuencia natural de la capacidad de manipular tipos de punteros opacos es que todos estos punteros deben tener la misma representación. Sin embargo, tenga en cuenta que los punteros a struct y los punteros a union pueden tener una representación diferente, así como los punteros a tipos básicos como char , int , double ...
Otra distinción con respecto a la representación de punteros es entre punteros a datos y punteros a funciones, que pueden tener un tamaño diferente. Esta diferencia es más común en las arquitecturas actuales, aunque sigue siendo rara fuera del espacio del controlador de dispositivo y del sistema operativo. Los punteros de función de 64 bits parecen un desperdicio, ya que 4 GB deberían ser suficientes para el espacio del código, pero las arquitecturas modernas aprovechan este espacio adicional para almacenar firmas de punteros para proteger el código contra ataques maliciosos. Otro uso es aprovechar el hardware que ignora algunos de los bits del puntero (p. ej., x86_64 ignora los 16 bits superiores) para almacenar información de tipo o utilizar valores de NaN sin modificar como punteros.
Además, los atributos de puntero cercano/lejano/enorme del código heredado de 16 bits no se abordaron correctamente en esta observación en el estándar C, ya que todos los punteros podrían ser cercanos , lejanos o enormes . Sin embargo, la distinción entre punteros de código y punteros de datos en el código de modelo mixto se cubrió y parece que todavía está vigente en algunos sistemas operativos.
Finalmente, Posix exige que todos los punteros tengan el mismo tamaño y representación, por lo que el código de modelo mixto debería convertirse rápidamente en una curiosidad histórica.
Es discutible que las arquitecturas donde la representación es diferente para diferentes tipos de datos son cada vez más raras hoy en día y ya es hora de limpiar el estándar y eliminar esta opción. La principal objeción es la compatibilidad con arquitecturas en las que las unidades direccionables son palabras grandes y los bytes de 8 bits se direccionan con información adicional, lo que hace que char * y void * sean más grandes que los punteros normales. Sin embargo, tales arquitecturas hacen que la aritmética de punteros sea muy engorrosa y también son bastante raras (personalmente, nunca he visto una).
En el lenguaje C inventado por Dennis Ritchie, cuando un compilador de C encontró una definición para struct foo *p; no tendría necesidad de preocuparse por si se definió la estructura o cómo se definió a menos que o hasta que un programa usara aritmética de punteros o el operador -> . De lo contrario, podría simplemente registrar que p era un puntero a una estructura con la etiqueta foo sin tener que saber o preocuparse si, dónde o cómo podría definirse dicha estructura. El Estándar agrega un pequeño detalle extraño que a veces hace que los punteros de estructura con etiquetas coincidentes sean incompatibles, pero el problema sigue siendo que un compilador debe poder procesar una declaración de un tipo de puntero a estructura, así como las asignaciones básicas entre dichos punteros, en casos en los que podría no conocer el contenido de una estructura.
Tenga en cuenta que en las plataformas donde los punteros a objetos con alineación arbitraria pueden ser más grandes que los punteros a objetos que se sabe que tienen alineación int , un compilador podría especificar con sensatez que todas las estructuras tienen alineación int incluso si solo contienen miembros de caracteres. Además, los compiladores de tales plataformas podrían decidir procesar punteros a uniones de tal manera que permitan que un puntero a cualquier objeto, incluso un carácter, se convierta en un puntero a cualquier unión que contenga dicho objeto, y se use para acceder ese objeto dentro del sindicato. Esto puede requerir que los punteros a los objetos de unión tengan el tamaño de un puntero de byte, en lugar de un puntero int más pequeño.
Tenga en cuenta que en los compiladores anteriores al estándar, si dos estructuras contenían miembros coincidentes, se esperaba que una función que aceptara un void* y lo convirtiera en un tipo de estructura se pudiera utilizar para operar en ambos tipos de manera intercambiable. Desafortunadamente, el Estándar permite a los compiladores asumir que el código nunca hará tal cosa, y no proporciona ningún medio para que los programadores indiquen cuándo dos estructuras deberían poder usarse indistintamente.