uint32_t uint16_t sistemas que para lenguaje int8_t embebidos c++ memory-alignment bit-fields memory-layout object-layout

c++ - sistemas - uint16_t que es



¿Por qué aumenta el tamaño de la clase cuando int64_t cambia a int32_t? (4)

En mi primer ejemplo tengo dos int64_t bits usando int64_t . Cuando compilo y obtengo el tamaño de la clase obtengo 8.

class Test { int64_t first : 40; int64_t second : 24; }; int main() { std::cout << sizeof(Test); // 8 }

Pero cuando cambio el segundo bitfeild para que sea int32_t el tamaño de la clase se duplica a 16:

class Test { int64_t first : 40; int32_t second : 24; }; int main() { std::cout << sizeof(Test); // 16 }

Esto sucede tanto en GCC 5.3.0 como en MSVC 2015. ¿Pero por qué?


En tu primer ejemplo

int64_t first : 40; int64_t second : 24;

Tanto el first como el second utilizan los 64 bits de un solo entero de 64 bits. Esto hace que el tamaño de la clase sea un solo entero de 64 bits. En el segundo ejemplo tienes

int64_t first : 40; int32_t second : 24;

Que es dos campos de bits separados que se almacenan en dos trozos diferentes de memoria. Utiliza 40 bits del entero de 64 bits y luego usa 24 bits de otro entero de 32 bits. Esto significa que necesita al menos 12 bytes (este ejemplo utiliza bytes de 8 bits). Lo más probable es que los 4 bytes adicionales que ve estén rellenados para alinear la clase en los límites de 64 bits.

Como otras respuestas y comentarios han señalado, este es un comportamiento definido por la implementación y puede ver / ver diferentes resultados en diferentes implementaciones.


Estándar dice:

§ 9.6 campos de bits

La asignación de campos de bits dentro de un objeto de clase está definida por la implementación. La alineación de los campos de bits está definida por la implementación. [ Nota: los campos de bits dividen las unidades de asignación en algunas máquinas y no en otras. Los campos de bits se asignan de derecha a izquierda en algunas máquinas, de izquierda a derecha en otras máquinas. - nota final ]

papel c ++ 11

Por lo tanto, el diseño depende de la implementación del compilador, los indicadores de compilación, el arco de destino, etc. Acabo de marcar varios compiladores y la salida es principalmente 8 8 :

#include <stdint.h> #include <iostream> class Test32 { int64_t first : 40; int32_t second : 24; }; class Test64 { int64_t first : 40; int64_t second : 24; }; int main() { std::cout << sizeof(Test32) << " " << sizeof(Test64); }


Las reglas del Estándar C para campos de bits no son lo suficientemente precisas para decirle a los programadores algo útil sobre el diseño, pero aún así niegan a las implementaciones lo que de otra manera podrían ser libertades útiles.

En particular, se requiere que los campos de bits se almacenen dentro de objetos que son de los tipos indicados, o el equivalente firmado / sin signo de los mismos. En su primer ejemplo, el primer campo de bits debe almacenarse en un objeto int64_t o uint64_t, y el segundo también, pero hay suficiente espacio para que quepan en el mismo objeto. En el segundo ejemplo, el primer campo de bits debe almacenarse en un int64_t o uint64_t, y el segundo en un int32_t o uint32_t. El uint64_t tendrá 24 bits que se "trenzan" incluso si se agregaran campos de bits adicionales al final de la estructura; el uint32_t tiene 8 bits que no se utilizan actualmente, pero estaría disponible para el uso de otro campo de bits int32_t o uint32_t cuyo ancho era inferior a 8 se agregaron al tipo.

En mi humilde opinión, el estándar tiene el peor equilibrio posible entre dar libertad a los compiladores y darles a los programadores información / control útil, pero es lo que es. Personalmente creo que los campos de bits serían mucho más útiles si la sintaxis preferida permitiera a los programadores especificar su diseño precisamente en términos de objetos ordinarios (por ejemplo, el campo de bits "foo" debería almacenarse en 3 bits, comenzando en el bit 4 (valor de 16), del campo " foo_bar ") pero no conozco ningún plan para definir tal cosa en el Estándar.


Para agregar a lo que otros ya han dicho:

Si desea examinarlo, puede usar una opción de compilador o un programa externo para generar el diseño de la estructura.

Considere este archivo:

// test.cpp #include <cstdint> class Test_1 { int64_t first : 40; int64_t second : 24; }; class Test_2 { int64_t first : 40; int32_t second : 24; }; // Dummy instances to force Clang to output layout. Test_1 t1; Test_2 t2;

Si usamos un indicador de salida de diseño, como Visual Studio''s /d1reportSingleClassLayoutX (donde X es todo o parte de la clase o nombre de la estructura) o Clang ++''s -Xclang -fdump-record-layouts (donde -Xclang le dice al compilador que interprete -fdump-record-layouts como un comando Clang frontend en lugar de un comando GCC frontend), podemos volcar los diseños de memoria de Test_1 y Test_2 a la salida estándar. [Desafortunadamente, no estoy seguro de cómo hacerlo directamente con GCC].

Si lo hacemos, el compilador dará salida a los siguientes diseños:

  • Estudio visual:

cl /c /d1reportSingleClassLayoutTest test.cpp // Output: tst.cpp class Test_1 size(8): +--- 0. | first (bitstart=0,nbits=40) 0. | second (bitstart=40,nbits=24) +--- class Test_2 size(16): +--- 0. | first (bitstart=0,nbits=40) 8. | second (bitstart=0,nbits=24) | <alignment member> (size=4) +---

  • Sonido metálico:

clang++ -c -std=c++11 -Xclang -fdump-record-layouts test.cpp // Output: *** Dumping AST Record Layout 0 | class Test_1 0 | int64_t first 5 | int64_t second | [sizeof=8, dsize=8, align=8 | nvsize=8, nvalign=8] *** Dumping IRgen Record Layout Record: CXXRecordDecl 0x344dfa8 <source_file.cpp:3:1, line:6:1> line:3:7 referenced class Test_1 definition |-CXXRecordDecl 0x344e0c0 <col:1, col:7> col:7 implicit class Test_1 |-FieldDecl 0x344e1a0 <line:4:2, col:19> col:10 first ''int64_t'':''long'' | `-IntegerLiteral 0x344e170 <col:19> ''int'' 40 |-FieldDecl 0x344e218 <line:5:2, col:19> col:10 second ''int64_t'':''long'' | `-IntegerLiteral 0x344e1e8 <col:19> ''int'' 24 |-CXXConstructorDecl 0x3490d88 <line:3:7> col:7 implicit used Test_1 ''void (void) noexcept'' inline | `-CompoundStmt 0x34912b0 <col:7> |-CXXConstructorDecl 0x3490ee8 <col:7> col:7 implicit constexpr Test_1 ''void (const class Test_1 &)'' inline noexcept-unevaluated 0x3490ee8 | `-ParmVarDecl 0x3491030 <col:7> col:7 ''const class Test_1 &'' `-CXXConstructorDecl 0x34910c8 <col:7> col:7 implicit constexpr Test_1 ''void (class Test_1 &&)'' inline noexcept-unevaluated 0x34910c8 `-ParmVarDecl 0x3491210 <col:7> col:7 ''class Test_1 &&'' Layout: <CGRecordLayout LLVMType:%class.Test_1 = type { i64 } NonVirtualBaseLLVMType:%class.Test_1 = type { i64 } IsZeroInitializable:1 BitFields:[ <CGBitFieldInfo Offset:0 Size:40 IsSigned:1 StorageSize:64 StorageOffset:0> <CGBitFieldInfo Offset:40 Size:24 IsSigned:1 StorageSize:64 StorageOffset:0> ]> *** Dumping AST Record Layout 0 | class Test_2 0 | int64_t first 5 | int32_t second | [sizeof=8, dsize=8, align=8 | nvsize=8, nvalign=8] *** Dumping IRgen Record Layout Record: CXXRecordDecl 0x344e260 <source_file.cpp:8:1, line:11:1> line:8:7 referenced class Test_2 definition |-CXXRecordDecl 0x344e370 <col:1, col:7> col:7 implicit class Test_2 |-FieldDecl 0x3490bd0 <line:9:2, col:19> col:10 first ''int64_t'':''long'' | `-IntegerLiteral 0x344e400 <col:19> ''int'' 40 |-FieldDecl 0x3490c70 <line:10:2, col:19> col:10 second ''int32_t'':''int'' | `-IntegerLiteral 0x3490c40 <col:19> ''int'' 24 |-CXXConstructorDecl 0x3491438 <line:8:7> col:7 implicit used Test_2 ''void (void) noexcept'' inline | `-CompoundStmt 0x34918f8 <col:7> |-CXXConstructorDecl 0x3491568 <col:7> col:7 implicit constexpr Test_2 ''void (const class Test_2 &)'' inline noexcept-unevaluated 0x3491568 | `-ParmVarDecl 0x34916b0 <col:7> col:7 ''const class Test_2 &'' `-CXXConstructorDecl 0x3491748 <col:7> col:7 implicit constexpr Test_2 ''void (class Test_2 &&)'' inline noexcept-unevaluated 0x3491748 `-ParmVarDecl 0x3491890 <col:7> col:7 ''class Test_2 &&'' Layout: <CGRecordLayout LLVMType:%class.Test_2 = type { i64 } NonVirtualBaseLLVMType:%class.Test_2 = type { i64 } IsZeroInitializable:1 BitFields:[ <CGBitFieldInfo Offset:0 Size:40 IsSigned:1 StorageSize:64 StorageOffset:0> <CGBitFieldInfo Offset:40 Size:24 IsSigned:1 StorageSize:64 StorageOffset:0> ]>

Tenga en cuenta que la versión de Clang que utilicé para generar esta salida (la que usó Rextester ) parece estar predeterminada para optimizar ambos campos de bits en una sola variable, y no estoy seguro de cómo deshabilitar este comportamiento.