tipos sintaxis rangos longitud funcion array c++ c struct sizeof c++-faq

c++ - sintaxis - ¿Por qué no es sizeof para una estructura igual a la suma de sizeof de cada miembro?



sizeof(int) (11)

¿Por qué el operador sizeof devuelve un tamaño más grande para una estructura que el tamaño total de los miembros de la estructura?


Además de las otras respuestas, una estructura puede (pero generalmente no) tener funciones virtuales, en cuyo caso el tamaño de la estructura también incluirá el espacio para el vtbl.


El tamaño de una estructura es mayor que la suma de sus partes debido a lo que se denomina empaquetamiento. Un procesador particular tiene un tamaño de datos preferido con el que trabaja. El tamaño preferido de los procesadores más modernos es de 32 bits (4 bytes). Acceder a la memoria cuando los datos se encuentran en este tipo de límite es más eficiente que las cosas que se ubican en ese límite de tamaño.

Por ejemplo. Considere la estructura simple:

struct myStruct { int a; char b; int c; } data;

Si la máquina es una máquina de 32 bits y los datos se alinean en un límite de 32 bits, vemos un problema inmediato (suponiendo que no hay alineación de la estructura). En este ejemplo, supongamos que los datos de la estructura comienzan en la dirección 1024 (0x400: tenga en cuenta que los 2 bits más bajos son cero, por lo que los datos se alinean con un límite de 32 bits). El acceso a data.a funcionará bien porque comienza en un límite: 0x400. El acceso a data.b también funcionará bien, ya que se encuentra en la dirección 0x404, otro límite de 32 bits. Pero una estructura no alineada pondría data.c en la dirección 0x405. Los 4 bytes de data.c están en 0x405, 0x406, 0x407, 0x408. En una máquina de 32 bits, el sistema leería data.c durante un ciclo de memoria, pero solo obtendría 3 de los 4 bytes (el 4to byte está en el siguiente límite). Entonces, el sistema tendría que hacer un segundo acceso de memoria para obtener el 4to byte,

Ahora, si en lugar de poner data.c en la dirección 0x405, el compilador completó la estructura con 3 bytes y colocó data.c en la dirección 0x408, entonces el sistema solo necesitaría 1 ciclo para leer los datos, reduciendo el tiempo de acceso a ese elemento de datos en un 50%. El relleno intercambia la eficiencia de la memoria para la eficiencia de procesamiento Dado que las computadoras pueden tener grandes cantidades de memoria (muchos gigabytes), los compiladores consideran que el intercambio (velocidad sobre el tamaño) es razonable.

Desafortunadamente, este problema se convierte en un asesino cuando intentas enviar estructuras a través de una red o incluso escribir los datos binarios en un archivo binario. El relleno insertado entre elementos de una estructura o clase puede interrumpir los datos enviados al archivo o red. Para escribir código portátil (uno que irá a varios compiladores diferentes), es probable que tenga que acceder a cada elemento de la estructura por separado para garantizar el "empaquetado" adecuado.

Por otro lado, diferentes compiladores tienen diferentes habilidades para gestionar el empaquetado de la estructura de datos. Por ejemplo, en Visual C / C ++ el compilador soporta el comando #pragma pack. Esto le permitirá ajustar el empaquetamiento de datos y la alineación.

Por ejemplo:

#pragma pack 1 struct MyStruct { int a; char b; int c; short d; } myData; I = sizeof(myData);

Ahora debería tener la longitud de 11. Sin el pragma, podría tener entre 11 y 14 (y para algunos sistemas, hasta 32), dependiendo del empaquetado predeterminado del compilador.


Empaquetado y alineación de bytes, como se describe en las Preguntas frecuentes de C here :

Es para la alineación. Muchos procesadores no pueden acceder a cantidades de 2 y 4 bytes (por ejemplo, ints y largas intenciones) si están abarrotados en todos los sentidos.

Supongamos que tiene esta estructura:

struct { char a[3]; short int b; long int c; char d[3]; };

Ahora, podrías pensar que debería ser posible empaquetar esta estructura en la memoria de esta manera:

+-------+-------+-------+-------+ | a | b | +-------+-------+-------+-------+ | b | c | +-------+-------+-------+-------+ | c | d | +-------+-------+-------+-------+

Pero es mucho más fácil para el procesador si el compilador lo organiza así:

+-------+-------+-------+ | a | +-------+-------+-------+ | b | +-------+-------+-------+-------+ | c | +-------+-------+-------+-------+ | d | +-------+-------+-------+

En la versión empaquetada, observe cómo es al menos un poco difícil para usted y para mí ver cómo se envuelven los campos byc. En pocas palabras, también es difícil para el procesador. Por lo tanto, la mayoría de los compiladores rellenarán la estructura (como si tuviera campos extra invisibles) de esta manera:

+-------+-------+-------+-------+ | a | pad1 | +-------+-------+-------+-------+ | b | pad2 | +-------+-------+-------+-------+ | c | +-------+-------+-------+-------+ | d | pad3 | +-------+-------+-------+-------+


Esto puede deberse a la alineación de bytes y al relleno para que la estructura salga a un número par de bytes (o palabras) en su plataforma. Por ejemplo, en C en Linux, las siguientes 3 estructuras:

#include "stdio.h" struct oneInt { int x; }; struct twoInts { int x; int y; }; struct someBits { int x:2; int y:6; }; int main (int argc, char** argv) { printf("oneInt=%zu/n",sizeof(struct oneInt)); printf("twoInts=%zu/n",sizeof(struct twoInts)); printf("someBits=%zu/n",sizeof(struct someBits)); return 0; }

Los miembros cuyos tamaños (en bytes) tienen 4 bytes (32 bits), 8 bytes (2x 32 bits) y 1 byte (2 + 6 bits) respectivamente. El programa anterior (en Linux con gcc) imprime los tamaños como 4, 8 y 4, donde la última estructura se rellena de modo que sea una sola palabra (4 bytes de 8 bits en mi plataforma de 32 bits).

oneInt=4 twoInts=8 someBits=4


Esto se debe al relleno agregado para satisfacer las restricciones de alineación. La alineación de la estructura de datos afecta tanto el rendimiento como la corrección de los programas:

  • El acceso mal alineado puede ser un error difícil (a menudo SIGBUS ).
  • El acceso mal alineado puede ser un error suave.
    • O bien corregido en hardware, para una modesta degradación del rendimiento.
    • O corregido por emulación en software, para una severa degradación del rendimiento.
    • Además, la atomicidad y otras garantías de concurrencia podrían romperse, dando lugar a errores sutiles.

Aquí hay un ejemplo que usa la configuración típica para un procesador x86 (todos usados ​​en los modos de 32 y 64 bits):

struct X { short s; /* 2 bytes */ /* 2 padding bytes */ int i; /* 4 bytes */ char c; /* 1 byte */ /* 3 padding bytes */ }; struct Y { int i; /* 4 bytes */ char c; /* 1 byte */ /* 1 padding byte */ short s; /* 2 bytes */ }; struct Z { int i; /* 4 bytes */ short s; /* 2 bytes */ char c; /* 1 byte */ /* 1 padding byte */ }; const int sizeX = sizeof(struct X); /* = 12 */ const int sizeY = sizeof(struct Y); /* = 8 */ const int sizeZ = sizeof(struct Z); /* = 8 */

Se puede minimizar el tamaño de las estructuras al ordenar los miembros por alineación (la clasificación por tamaño es suficiente para eso en los tipos básicos) (como la estructura Z en el ejemplo anterior).

NOTA IMPORTANTE: los estándares C y C ++ establecen que la alineación de la estructura está definida por la implementación. Por lo tanto, cada compilador puede elegir alinear los datos de manera diferente, lo que resulta en diseños de datos diferentes e incompatibles. Por esta razón, cuando se trata de bibliotecas que serán utilizadas por compiladores diferentes, es importante entender cómo los compiladores alinean los datos. Algunos compiladores tienen configuraciones de línea de comandos y / o declaraciones especiales de #pragma para cambiar las configuraciones de alineación de la estructura.


La idea es que, por razones de velocidad y caché, los operandos deben leerse desde direcciones alineadas a su tamaño natural. Para que esto suceda, el compilador rellena los miembros de la estructura para que el siguiente miembro o la siguiente estructura se alineen.

struct pixel { unsigned char red; // 0 unsigned char green; // 1 unsigned int alpha; // 4 (gotta skip to an aligned offset) unsigned char blue; // 8 (then skip 9 10 11) }; // next offset: 12

La arquitectura x86 siempre ha sido capaz de recuperar direcciones desalineadas. Sin embargo, es más lento y, cuando la desalineación se superpone a dos líneas de caché diferentes, desaloja dos líneas de caché cuando un acceso alineado solo desaloja a una.

Algunas arquitecturas realmente tienen que interceptarse en las lecturas y escrituras desalineadas, y las primeras versiones de la arquitectura ARM (la que evolucionó en todas las CPU móviles de hoy) ... bueno, en realidad solo devolvieron datos erróneos. (Ignoraron los bits de orden inferior).

Finalmente, tenga en cuenta que las líneas de caché pueden ser arbitrariamente grandes, y el compilador no intenta adivinarlas o hacer un intercambio de espacio-velocidad-velocidad. En cambio, las decisiones de alineación son parte de la ABI y representan la alineación mínima que eventualmente llenará una línea de caché.

TL; DR: la alineación es importante.


Puede hacerlo si ha establecido implícita o explícitamente la alineación de la estructura. Una estructura alineada 4 siempre será un múltiplo de 4 bytes, incluso si el tamaño de sus miembros sea algo que no sea un múltiplo de 4 bytes.

Además, una biblioteca puede compilarse bajo x86 con ints de 32 bits y es posible que compares sus componentes en un proceso de 64 bits que te daría un resultado diferente si lo hicieras a mano.


Si desea que la estructura tenga un tamaño determinado con GCC, por ejemplo, use __attribute__((packed)) .

En Windows, puede establecer la alineación en un byte al usar el compilador cl.exe con la opción / Zp .

Por lo general, es más fácil para la CPU acceder a datos que son múltiplos de 4 (u 8), dependiendo de la plataforma y también en el compilador.

Así que es una cuestión de alineación básicamente.

Necesitas tener buenas razones para cambiarlo.


Ver también:

para Microsoft Visual C:

http://msdn.microsoft.com/en-us/library/2e70t5y1%28v=vs.80%29.aspx

y GCC alegan compatibilidad con el compilador de Microsoft .:

http://gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html

Además de las respuestas anteriores, tenga en cuenta que, independientemente del paquete, no hay garantía de pedido de miembros en C ++ . Los compiladores pueden (y ciertamente lo hacen) agregar un puntero de tabla virtual y miembros de estructuras base a la estructura. Incluso la existencia de una tabla virtual no está garantizada por el estándar (no se especifica la implementación del mecanismo virtual) y, por lo tanto, se puede concluir que tal garantía es simplemente imposible.

Estoy bastante seguro de que la orden de los miembros está garantizada en C , pero no contaría con eso, al escribir un programa multiplataforma o compilador cruzado.


C99 N1256 borrador estándar

http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf

6.5.3.4 El operador sizeof :

3 Cuando se aplica a un operando que tiene estructura o tipo de unión, el resultado es el número total de bytes en dicho objeto, incluido el relleno interno y posterior.

6.7.2.1 Especificadores de estructura y unión :

13 ... Puede haber un relleno sin nombre dentro de un objeto de estructura, pero no al principio.

y:

15 Puede haber un relleno anónimo al final de una estructura o unión.

La nueva función de miembro de matriz flexible C99 ( struct S {int is[];}; ) también puede afectar el relleno:

16 Como caso especial, el último elemento de una estructura con más de un miembro nombrado puede tener un tipo de matriz incompleta; Esto se llama un miembro de matriz flexible. En la mayoría de las situaciones, el miembro de la matriz flexible se ignora. En particular, el tamaño de la estructura es como si se hubiera omitido el miembro de la matriz flexible, excepto que puede tener más relleno posterior del que implicaría la omisión.

Anexo J Cuestiones de Portabilidad reitera:

Los siguientes no están especificados: ...

  • El valor de los bytes de relleno al almacenar valores en estructuras o uniones (6.2.6.1)

C ++ 11 N3337 borrador estándar

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf

5.3.3 Sizeof :

2 Cuando se aplica a una clase, el resultado es el número de bytes en un objeto de esa clase, incluido el relleno requerido para colocar objetos de ese tipo en una matriz.

9.2 Miembros de la clase :

Un puntero a un objeto de estructura de diseño estándar, convertido adecuadamente usando un reinterpret_cast, apunta a su miembro inicial (o si ese miembro es un campo de bits, luego a la unidad en la que reside) y viceversa. [Nota: Por lo tanto, podría haber un relleno sin nombre dentro de un objeto de estructura de diseño estándar, pero no al principio, según sea necesario para lograr la alineación adecuada. - nota final]

Solo conozco suficiente C ++ para entender la nota :-)


El lenguaje C deja al compilador cierta libertad sobre la ubicación de los elementos estructurales en la memoria:

  • Los orificios de memoria pueden aparecer entre dos componentes y después del último componente. Se debió al hecho de que ciertos tipos de objetos en la computadora de destino pueden estar limitados por los límites de direccionamiento
  • Tamaño de "orificios de memoria" incluido en el resultado del operador sizeof. El tamaño solo no incluye el tamaño de la matriz flexible, que está disponible en C / C ++
  • Algunas implementaciones del lenguaje le permiten controlar el diseño de la memoria de las estructuras a través de las opciones pragma y compilador

El lenguaje C proporciona cierta seguridad al programador de la disposición de los elementos en la estructura:

  • compiladores necesarios para asignar una secuencia de componentes que aumentan las direcciones de memoria
  • La dirección del primer componente coincide con la dirección de inicio de la estructura
  • los campos de bit sin nombre pueden incluirse en la estructura para las alineaciones de direcciones requeridas de elementos adyacentes

Problemas relacionados con la alineación de los elementos:

  • Diferentes computadoras alinean los bordes de los objetos de diferentes maneras.
  • Diferentes restricciones en el ancho del campo de bits.
  • Las computadoras difieren en cómo almacenar los bytes en una palabra (Intel 80x86 y Motorola 68000)

Cómo funciona la alineación:

  • El volumen ocupado por la estructura se calcula como el tamaño del elemento individual alineado de una matriz de dichas estructuras. La estructura debe finalizar de modo que el primer elemento de la siguiente estructura siguiente no viole los requisitos de alineación

ps Hay información más detallada disponible aquí: "Samuel P.Harbison, Guy L.Steele CA Reference, (5.6.2 - 5.6.7)"