c++ c assembly portability

c++ - tamaño de los punteros y la arquitectura



assembly portability (9)

Al realizar una prueba básica ejecutando un simple programa C ++ en una computadora de escritorio normal, parece plausible suponer que los tamaños de los punteros de cualquier tipo (incluidos los punteros a las funciones) son iguales a los bits de la arquitectura de destino.

Por ejemplo: en arquitecturas de 32 bits -> 4 bytes y en arquitecturas de 64 bits -> 8 bytes.

Sin embargo recuerdo haber leído eso, ¡no es así en general!

Así que me preguntaba ¿cuáles serían esas circunstancias?

  • Para la igualdad de tamaño de los punteros a los tipos de datos en comparación con el tamaño de los punteros a otros tipos de datos
  • Para la igualdad de tamaño de los punteros a los tipos de datos en comparación con el tamaño de los punteros a las funciones
  • Para la igualdad de tamaño de los punteros a la arquitectura de destino.

¿Es razonable suponer que, en general, los tamaños de los punteros de cualquier tipo (incluidos los punteros a las funciones) son iguales a los bits de la arquitectura de destino?

Puede ser razonable, pero no es confiable de manera correcta. Así que supongo que la respuesta es "no, excepto cuando ya sabes que la respuesta es sí (y no te preocupa la portabilidad)" .

Potencialmente:

  • Los sistemas pueden tener diferentes tamaños de registro y usar diferentes anchos subyacentes para los datos y el direccionamiento: no es evidente lo que "bits de arquitectura de destino" significa incluso para un sistema de este tipo, por lo que debe elegir un ABI específico (y una vez que lo haya hecho). Conoce la respuesta, para eso ABI).

  • los sistemas pueden admitir diferentes modelos de punteros, como los antiguos punteros near , far e huge ; en ese caso, necesita saber en qué modo se está compilando su código (y entonces usted sabe la respuesta, para ese modo)

  • los sistemas pueden admitir diferentes tamaños de punteros, como el X32 ABI ya mencionado, o cualquiera de los otros modelos de datos populares de 64 bits descritos here

Finalmente, no hay un beneficio obvio para esta suposición, ya que solo puede usar sizeof(T) directamente para cualquier T que le interese.

Si desea convertir entre enteros y punteros, use intptr_t . Si desea almacenar enteros y punteros en el mismo espacio, simplemente use una union .


¿Es razonable suponer que, en general, los tamaños de los punteros de cualquier tipo (incluidos los punteros a las funciones) son iguales a los bits de la arquitectura de destino?

Si observa todos los tipos de CPU (incluidos los microcontroladores) que se producen actualmente, diría que no.

Los contraejemplos extremos serían arquitecturas donde se utilizan dos tamaños de punteros diferentes en el mismo programa :

x86, 16 bits

En MS-DOS y Windows de 16 bits, un programa "normal" usaba punteros de 16 y 32 bits.

x86, 32 bits segmentados

Había solo unos pocos sistemas operativos menos conocidos que usaban este modelo de memoria.

Los programas normalmente utilizan punteros de 32 y 48 bits.

STM8A

Esta moderna CPU automotriz de 8 bits utiliza punteros de 16 y 24 bits. Ambos en el mismo programa, por supuesto.

AVR pequeña serie

La RAM se direcciona utilizando punteros de 8 bits, Flash se direcciona utilizando punteros de 16 bits.

(Sin embargo, AVR no puede programarse con C ++, por lo que sé).


Es razonable suponer que, en general, los tamaños de los punteros de cualquier tipo (incluidos los punteros a las funciones) son iguales a los bits de la arquitectura de destino

Depende. Si desea obtener una estimación rápida del consumo de memoria, puede ser lo suficientemente bueno.

(incluyendo punteros a funciones)

Pero aquí hay un comentario importante. Aunque la mayoría de los punteros tendrán el mismo tamaño, los punteros de función pueden diferir. No se garantiza que un void* pueda mantener un puntero de función. Al menos, esto es cierto para C. No sé sobre C ++.

Así que me preguntaba ¿cuáles serían esas circunstancias si las hubiera?

Puede haber toneladas de razones por las que difiere. Si la corrección de sus programas depende de este tamaño, NUNCA está bien hacer tal suposición. Compruébalo en su lugar. No debería ser difícil en absoluto.

Puede usar esta macro para verificar tales cosas en tiempo de compilación en C:

#include <assert.h> static_assert(sizeof(void*) == 4, "Pointers are assumed to be exactly 4 bytes");

Al compilar, esto da un mensaje de error:

$ gcc main.c In file included from main.c:1: main.c:2:1: error: static assertion failed: "Pointers are assumed to be exactly 4 bytes" static_assert(sizeof(void*) == 4, "Pointers are assumed to be exactly 4 bytes"); ^~~~~~~~~~~~~

Si está utilizando C ++, puede omitir #include <assert.h> porque static_assert es una palabra clave en C ++. (Y puede usar la palabra clave _Static_assert en C, pero se ve fea, así que use la inclusión y la macro en su lugar).

Ya que estas dos líneas son extremadamente fáciles de incluir en su código, NO hay excusa para no hacerlo si su programa no funcionaría correctamente con el tamaño de puntero incorrecto.


En general, los punteros serán de tamaño 2 en un sistema de 16 bits, 3 en un sistema de 24 bits, 4 en un sistema de 32 bits y 8 en un sistema de 64 bits. Depende de la implementación de ABI y C. AMD tiene modos largos y heredados , y hay diferencias entre AMD64 e Intel64 para los programadores en lenguaje ensamblador, pero están ocultos para los idiomas de nivel superior.

Cualquier problema con el código C / C ++ puede deberse a prácticas de programación deficientes e ignorar las advertencias del compilador. Consulte: " 20 problemas de portar código C ++ a la plataforma de 64 bits ".

Vea también: " ¿Los punteros pueden ser de diferentes tamaños? " Y la respuesta de LRiO :

... está preguntando acerca de C ++ y sus implementaciones compatibles, no de alguna máquina física específica. Tendría que citar todo el estándar para demostrarlo , pero el hecho simple es que no ofrece garantías sobre el resultado de sizeof (T *) para ninguna T, y (como corolario) no garantiza que sizeof (T1 *) == sizeof (T2 *) para cualquier T1 y T2).

Nota: Donde es respondido por JeremyP , C99 sección 6.3.2.3, subsección 8:

Un puntero a una función de un tipo puede convertirse en un puntero a una función de otro tipo y viceversa; El resultado se comparará igual al puntero original. Si se usa un puntero convertido para llamar a una función cuyo tipo no es compatible con el tipo apuntado a, el comportamiento es indefinido.

En GCC puede evitar suposiciones incorrectas mediante el uso de funciones integradas: " Comprobación de tamaño de objeto Funciones incorporadas ":

Función incorporada: size_t __builtin_object_size (const void * ptr, int type)

es una construcción incorporada que devuelve un número constante de bytes desde ptr hasta el final del objeto al que apunta el puntero ptr (si se conoce en el momento de la compilación). Para determinar los tamaños de los objetos asignados dinámicamente, la función se basa en las funciones de asignación llamadas para obtener el almacenamiento que se va a declarar con el atributo alloc_size (consulte Atributos de función comunes). __builtin_object_size nunca evalúa sus argumentos para efectos secundarios. Si hay efectos secundarios en ellos, devuelve (size_t) -1 para los tipos 0 o 1 y (size_t) 0 para los tipos 2 o 3. Si hay varios objetos a los que ptr puede apuntar y todos son conocidos en tiempo de compilación , el número devuelto es el máximo de recuentos de bytes restantes en esos objetos si tipo & 2 es 0 y mínimo si no es cero. Si no es posible determinar a qué objetos apunta ptr en tiempo de compilación, __builtin_object_size debería devolver (size_t) -1 para el tipo 0 o 1 y (size_t) 0 para el tipo 2 o 3.


Históricamente, en los microcomputadores y los microcontroladores, los punteros a menudo eran más amplios que los registros de propósito general, de modo que la CPU podría abordar suficiente memoria y aún encajar dentro del presupuesto del transistor. La mayoría de las CPU de 8 bits (como el 8080, Z80 o 6502) tenían direcciones de 16 bits.

Hoy en día, es más probable que se produzca una falta de coincidencia porque una aplicación no necesita varios gigabytes de datos, por lo que ahorrar cuatro bytes de memoria en cada puntero es una ganancia.

Tanto C como C ++ proporcionan tipos separados size_t , uintptr_t y off_t , que representan el mayor tamaño de objeto posible (que podría ser más pequeño que el tamaño de un puntero si el modelo de memoria no es plano), un tipo integral lo suficientemente amplio como para contener un puntero, y un desplazamiento de archivo (a menudo más ancho que el objeto más grande permitido en la memoria), respectivamente. Un size_t (sin signo) o ptrdiff_t (firmado) es la forma más portátil de obtener el tamaño de palabra nativo. Además, POSIX garantiza que el compilador del sistema tiene algún indicador que significa que un long puede contener cualquiera de estos, pero no siempre se puede asumir.


La arquitectura de destino "bits" dice sobre el tamaño de los registros. Ex. Intel 8051 es de 8 bits y opera en registros de 8 bits, pero se accede a la RAM (externa) y a la ROM (externa) con valores de 16 bits.


No es correcto, por ejemplo, los punteros de DOS (16 bits) pueden estar lejos (seg + ofs).

Sin embargo, para los objetivos habituales (Windows, OSX, Linux, Android, iOS) es correcto. Porque todos usan el modelo de programación plano que se basa en la paginación.

En teoría, también puede tener sistemas que usan solo los 32 bits más bajos cuando está en x64. Un ejemplo es un ejecutable de Windows vinculado sin LARGEADDRESSAWARE. Sin embargo, esto es para ayudar al programador a evitar errores al cambiar a x64. Los punteros se truncan a 32 bits, pero siguen siendo de 64 bits.

En los sistemas operativos x64, esta suposición siempre es cierta, porque el modo plano es el único válido. El modo largo en la CPU obliga a que las entradas GDT sean de 64 bits planas.

Uno también menciona un ABI x32, creo que se basa en la misma tecnología de paginación, lo que obliga a que todos los punteros se asignen a la 4gb inferior. Sin embargo, esto debe basarse en la misma teoría que en Windows. En x64 solo puedes tener modo plano.

En el modo protegido de 32 bits, puede tener punteros de hasta 48 bits. (Modo segmentado). También puedes tener callgates. Pero, ningún sistema operativo utiliza ese modo.


No, no es razonable suponer. Hacer esta suposición puede causar errores.

Los tamaños de los punteros (y de los tipos enteros) en C o C ++ se determinan en última instancia por la implementación de C o C ++. Las implementaciones normales de C o C ++ están muy influenciadas por las arquitecturas y los sistemas operativos a los que se dirigen, pero pueden elegir el tamaño de sus tipos por razones distintas a la velocidad de ejecución, como los objetivos de admitir un menor uso de memoria, un código de soporte que no se escribió. sea ​​totalmente portátil a cualquier tamaño de tipo, o permita un uso más fácil de los enteros grandes.

He visto un compilador destinado a un sistema de 64 bits pero que proporciona punteros de 32 bits, con el propósito de crear programas con un menor uso de memoria. (Se observó que los tamaños de los punteros eran un factor considerable en el consumo de memoria, debido al uso de muchas estructuras con muchas conexiones y referencias que usan punteros). El código fuente se creó con el supuesto de que el tamaño del puntero era igual al registro de 64 bits. el tamaño se rompería.


Para la corrección , no puedes asumir nada. Tienes que revisar y estar preparado para enfrentar situaciones extrañas.

Como regla general, es una suposición predeterminada razonable .

Aunque no es universalmente cierto. Vea el ABI de X32 , por ejemplo, que utiliza punteros de 32 bits en arquitecturas de 64 bits para ahorrar un poco de memoria y espacio de caché. Lo mismo para el ABI ILP32 en AArch64.

Por lo tanto, para estimar el uso de la memoria, puede utilizar su suposición y con frecuencia será correcta.