C ++ El comstackdor está cambiando la alineación de mis estructuras. ¿Cómo puedo prevenir esto?

Estoy escribiendo algo de código para leer archivos de bitmap.

Aquí está la estructura que estoy usando para leer el encabezado del bitmap. Ver también:

https://msdn.microsoft.com/en-us/library/windows/desktop/dd183374(v=vs.85).aspx

struct BITMAPFILEHEADER { WORD bfType; // 2 DWORD bfSize; // 6 WORD bfReserved1; // 8 WORD bfReserved2; // 10 DWORD bfOffBits; // 14 }; // should add to 14 bytes 

Si pongo el siguiente código en mi función principal:

 std::cout << "BITMAPFILEHEADER: " << sizeof(BITMAPFILEHEADER) << std::endl; 

el progtwig imprime:

 BITMAPFILEHEADER: 16 

Parece estar realineando los datos en la estructura en los límites de 4 bytes, presumiblemente por eficiencia. Por supuesto, esto me hace incapaz de leer un bitmap … Aunque Microsoft, y otros, especifiquen que esta es la forma de hacerlo …

¿Cómo puedo evitar la realineación de la estructura?

La solución que encontré que funciona en los comstackdores de gcc, bajo linux:

 struct BITMAPFILEHEADER { WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits; } __attribute__((packed)); 

Probablemente haya una forma mejor y más cruzada de comstackdor / plataforma para hacer las cosas, pero no sé qué es.

Para evitar esto, obviamente puede designar la granularidad de comstackción. Solo usa este interruptor:

 ##pragma pack(1) 

Esto le dice al comstackdor que se alinee con los límites de 1 byte (no hacer nada)

Para reanudar el relleno normal (antes del #pragma pack anterior):

 #pragma pack(pop) 

¿Cómo puedo evitar la realineación de la estructura?

En C ++, creo que no se puede evitar que el comstackdor realine la “estructura BITMAPFILEHEADER”.

Con g ++ – 5 en mi Ubuntu 15.10, el mismo relleno ocurre en todos -O0, -O2, -O3, -Os. Todavía no he encontrado una opción de comstackdor para lograr este objective

Algunas de las respuestas en la referencia indican además que no puede controlar cómo las optimizaciones pueden impulsar el relleno y la alineación.

Consulte también https://stackoverflow.com/a/19471823/2785528

Vea también ¿Por qué gcc genera un código 15-20% más rápido si optimizo el tamaño en lugar de la velocidad?

Otras dos respuestas han sugerido el uso de pragmas. Cualquier pragma que haya probado ha funcionado de manera confiable, para ese comstackdor en particular, usando las opciones elegidas para esa aplicación. Los pragmas no están estandarizados, y por lo tanto (IMHO) no son parte de C ++. Lo mismo con las extensiones de comstackdor … Los desarrolladores de C ++ tampoco deben usar.

En pocas palabras, C ++ es totalmente capaz de hacer lo que se necesita.


Parece estar realineando los datos en la estructura en los límites de 4 bytes, presumiblemente por eficiencia.

Estoy de acuerdo en que lo más probable es que el relleno se agregue a la noción de “eficiencia” de algunos comstackdores (o comstackdores de comstackdores). Y como la estructura claramente no es más eficiente en espacio, entonces quizás la alineación permita la producción de código ‘más rápido’. Todos hemos intercambiado espacio por rendimiento, ¿verdad? Esta es generalmente una buena idea, a menos que necesite un ’empaque’ que el comstackdor no proporciona.


A continuación presento una posible implementación (con código) que proporciona un control absoluto del tamaño de los datos y el relleno, y, en este diseño C ++, (sin pragma, sin extensiones de comstackdor) produce un objeto de exactamente 14 bytes.

Encontré un comentario o respuesta que podría ser similar al enfoque, pero fue sin código.

Yo llamo a este enfoque ‘Técnica 1 (de 5)’.

Véase también la Nota 1 después del código.

Nota 0: tengo todo el siguiente código en un archivo (todas las partes están disponibles a continuación, pero se han reorganizado).


El problema: sizeof (struct BITMAPFILEHEADER) se informa como 16 bytes:

 // sizeof() reports 16 bytes -- the compiler adds pad struct BITMAPFILEHEADER { // from MS docs WORD bfType; // 2 - The file type; must be BM (bit mapped?) DWORD bfSize; // 4 - The size, in bytes, of the bitmap file. WORD bfReserved1; // 2 - Reserved; must be zero. WORD bfReserved2; // 2 - Reserved; must be zero. DWORD bfOffBits; // 4 - The offset, in bytes, from the beginning of }; // this (14 byte) structure to the bitmap bits 

Esto me hace incapaz de leer un bitmap

Creo que está diciendo que la lectura binaria de su fuente crea un objeto no válido debido al problema de alineación (aunque no muestra el código de su “problema”).

La siguiente implementación propuesta, basada en una matriz de 14 bytes, proporciona un control absoluto del tamaño de los datos y el relleno, y produce un objeto de exactamente 14 bytes.

 // sizeof reports 14 bytes class BITMAPFILEHEADER_t { private: enum Constraints : size_t { // bf[indx] Size Type = 0, TSz = 2, // 1 WORD Size = 2, SSz = 4, // 2 WORD Reserved1 = 6, RSz = 2, // 1 WORD Reserved2 = 8, // 1 WORD OffBits = 10, OSz = 4, // 2 WORD // BfSz = 14, EndConstraints }; char bf[BfSz]; // 14 byte array // reinterpret_cast a) explicitly allows conversion to char* from T* // b) generates no code (compile time directive) // vvv---short form verbose form---vvvvvvvvvvvvvvvv char* ric (WORD* w) { return reinterpret_cast(w); } char* ric (DWORD* dw) { return reinterpret_cast(dw); } char* ric (BITMAPFILEHEADER_t* hdr14) { return reinterpret_cast(hdr14); } char* ric (BITMAPFILEHEADER* hdr16) { return reinterpret_cast(hdr16); } void memCpy (char* to, char* from, size_t count) { for (uint i = 0; i < count; ++i) { to[i] = from[i]; } } // compute 'distance' between two addrs uint64_t delta(char* highAddr, char* lowAddr) { uint64_t dlta = (highAddr - lowAddr); if(false) { std::cerr << "\n diag: delta is " << dlta << std::flush; } return(dlta); } public: BITMAPFILEHEADER_t() = delete; BITMAPFILEHEADER_t(WORD utype, DWORD usize, DWORD uoff) { confirmFieldPlacement(); // check layout setFields (utype, usize, uoff); // fill fields if(false) std::cout << "\n ctor 68 " << __PRETTY_FUNCTION__ << std::flush; if(false) std::cout << "\n " << dumpFieldHex ("dumpFieldHex 91 \n"); if(false) std::cout << " " << ::dumpByteHex (ric(this), BfSz, "dumpByteHex 94 \n", 6) << std::endl; } // BITMAPFILEHEADER_t(w, dw, dw) // convert // to 14 byte from 16 byte BITMAPFILEHEADER_t (BITMAPFILEHEADER& bmfh) { confirmFieldPlacement(); // ctor must check setFields(bmfh.bfType, bmfh.bfSize, bmfh.bfOffBits); } ~BITMAPFILEHEADER_t() = default; // all POD // convert // to 16 byte from 14 byte BITMAPFILEHEADER toBMFH() { BITMAPFILEHEADER bmfh; // to from count memCpy (ric(&bmfh.bfType), &bf[Type], TSz); memCpy (ric(&bmfh.bfSize), &bf[Size], SSz); memCpy (ric(&bmfh.bfReserved1), &bf[Reserved1], RSz); memCpy (ric(&bmfh.bfReserved2), &bf[Reserved2], RSz); memcpy (ric(&bmfh.bfOffBits), &bf[OffBits], OSz); return bmfh; } void setFields (WORD utype, DWORD usize, DWORD uoff) { // to from count memCpy (&bf[Type], ric(&utype), TSz); // bfType memCpy (&bf[Size], ric(&usize), SSz); // bfSize DWORD zero = 0; // reserved values must be 0 memCpy (&bf[Reserved1], ric(&zero), RSz); // bfReserved1 memCpy (&bf[Reserved2], ric(&zero), RSz); // bfReserved2 memcpy (&bf[OffBits] , ric(&uoff) , OSz); // bfOffBits } // void BITMAPFILEHEADER_t::setFields() // reads 1x 14 bytes into bf[] void read_14_Bytes (std::stringstream& ss14) { (void) ss14.read (&bf[0], 14); // ^^^^ binary } // writes 1x 14 bytes from bf[] void write_14_Bytes (std::ostream& ss14) { (void) ss14.write (&bf[0], 14); // ^^^^^ binary } std::string dumpFieldHex(std::string lbl) { std::stringstream ss; ss << lbl << std::flush << std::hex // from len << ::dumpByteHex( &bf[Type], TSz, " bfType : ") // 1 WORD << ::dumpByteHex( &bf[Size], SSz, " bfSize : ") // 1 DWORD << ::dumpByteHex( &bf[Reserved1], RSz, " bfReserved1 : ") // 1 DWORD << ::dumpByteHex( &bf[Reserved2], RSz, " bfReserved2 : ") // 1 DWORD << ::dumpByteHex( &bf[OffBits], OSz, " bfOffBits : ") // 1 DWORD << std::dec << std::flush; return(ss.str()); } // std::string BITMAPFILEHEADER_t::dumpFieldHex(lbl) std::string dumpByteHex (std::string label) { return ::dumpByteHex (ric(this), // char* startAddr, BfSz, // size_t len, label, // std::string label, 0); // int indent); } // std::string BITMAPFILEHEADER_t::dumpByteHex(lbl) private: void confirmFieldPlacement() { // distance ( to field, from start addr ) assert( delta ( &bf[Type], ric(this) ) == Type); // 0, len 2 assert( delta ( &bf[Size], ric(this) ) == Size); // 2, len 4 assert( delta ( &bf[Reserved1], ric(this) ) == Reserved1); // 6, len 2 assert( delta ( &bf[Reserved2], ric(this) ) == Reserved2); // 8, len 2 assert( delta ( &bf[OffBits], ric(this) ) == OffBits); // 10, len 4 // expected magic numbers ---------------------^^^^-----------^^ assert( sizeof(BITMAPFILEHEADER_t) == BfSz); // sizeof whole instance 14 bytes } // void BITMAPFILEHEADER_t::confirmFieldPlacement() }; // class BITMAPFILEHEADER_t 

Los únicos datos en BITMAPFILEHEADER_t son bf. Exactamente 14 bytes. Sin almohadillas.


Para la prueba de unidad, por lo general minimizo 'principal':

 int main(int , char** ) { T528_t t528; // unit test 528 Time_t start_us = HRClk_t::now(); int retVal = t528.exec(); // run unit tests auto duration_us = std::chrono::duration_cast(HRClk_t::now() - start_us); std::cout << "\n T528_t::exec() duration " << duration_us.count() << " us" << std::endl; return(retVal); } 

Para su revisión, el código de prueba de la unidad "t528.exec ()" proporciona:

a) una demostración con "void read_14_Byte_records (std :: stringstream & ss14)" y "void write_14_Byte_records (std :: ostream & ss14)"

b) dos conversiones, entre struct (16 bytes) y clase (14 bytes). Ver también "T528 :: conversion_therapy ()".

c) una demostración que ilustra técnicas estándar para hacer visible el comstackdor insertado "oculto".

d) una demostración para hacer visible que la clase no tiene relleno.

e) una implementación en C ++ de dumpByteHex ().


 class T528_t { private: // reinterpret_cast a) explicitly allows conversion to char* from T* // b) generates no code (compile time directive) // vvv---short form verbose form---vvvvvvvvvvvvvvvv char* ric(BITMAPFILEHEADER_t* hdr14) { return(reinterpret_cast(hdr14)); } char* ric(BITMAPFILEHEADER* hdr16) { return(reinterpret_cast(hdr16)); } public: T528_t() { std::cout << "\n " << __PRETTY_FUNCTION__ << " ctor "; std::cout << "\n sizeof (struct BITMAPFILEHEADER ) " << sizeof(BITMAPFILEHEADER); // reports 16 bytes std::cout << "\n sizeof (class BITMAPFILEHEADER_t) " << sizeof(BITMAPFILEHEADER_t) // reports 14 bytes << std::endl; } ~T528_t() = default; int exec() { std::cout << "\n " << __PRETTY_FUNCTION__ << std::endl; { std::stringstream ss14; // for testing binary i/o write_14_Byte_records (ss14); // push_back some number of // 14 byte records into ss14 read_14_Byte_records(ss14); // read/show all 14 byte records in ss14 } struct_pad_demo(); // using struct class_no_pad_demo(); // using class conversion_therapy(); return(0); } // int exec() private: // methods void write_14_Byte_records (std::stringstream& ss14) { std::cout << "\n " << __PRETTY_FUNCTION__ << " START =" << std::flush; { BITMAPFILEHEADER_t hdr14 (0x1122, 0x33445566, 0x778899AA); hdr14.write_14_Bytes (ss14); if(!ss14.good()) { std::cerr << " Err: hdr14.write_14_Bytes(ss14) (!ss14.good())" << std::endl; assert(0); // TBR } } std::cout << "=" << std::flush; // one per record WORD w = 0x1010; DWORD dw = 0x10101010; for (int i=0; i<10; ++i) { w = static_cast ( w + 0x0101); dw = static_cast(dw + 0x01010101); { BITMAPFILEHEADER_t hdr14 ( w, dw, dw ); hdr14.write_14_Bytes (ss14); if(!ss14.good()) { std::cerr << " Err: hdr14.write_14_Bytes(ss14) (!ss14.good())" << std::endl; assert(0); // TBR } } // one per record: std::cout << ((1 == (i % 2)) ? '=' : '.') << std::flush; } { BITMAPFILEHEADER_t hdr14 (0x2211, 0x66554433, 0xAA998877); hdr14.write_14_Bytes (ss14); if(!ss14.good()) { std::cerr << " hdr14.write_14_Bytes(ss14) (!ss14.good())" << std::endl; assert(0); // TBR } } std::cout << "=" << std::flush; // one per record std::cout << "\n " << __PRETTY_FUNCTION__ << " END =============\n" << std::endl; } // void T528::write_14_Byte_records (std::stringstream&) void read_14_Byte_records (std::stringstream& ss14) { std::cout << " " << __PRETTY_FUNCTION__; for (int i = 0; true; ++i) // infinite loop, break out at ss14.eof() { // construct a work buffer to receive stream data BITMAPFILEHEADER_t workBuff(0, 0, 0); if(false) { // diag only, buff contents after ctor, before read_14_Bytes() std::cout << "\n " << workBuff.dumpFieldHex("pre-read:\n"); // err 8,9? std::cout << " " << workBuff.dumpByteHex ("pre-read:\n "); } // binary read of 14 byte data into workBuff workBuff.read_14_Bytes (ss14); if(ss14.eof()) { // no more data! std::cout << "\n\n ss14.eof() after " << std::dec << i << " records." << std::endl; break; } assert(ss14.good()); // tbr std::cout << "\n\n post-read hex-dump of record [" << std::dec << i << "] (from ss14)" << std::endl; std::cout << " " << workBuff.dumpFieldHex("fields within class\n"); std::cout << " " << workBuff.dumpByteHex ("class\n "); } // for () ss14.eof() // stringstream has no member named 'close'. } // void T528::read_14_Byte_records (std::stringstream&) void struct_pad_demo() { std::cout << "\n\n\n " << __PRETTY_FUNCTION__ << " START ==================================" << std::endl; BITMAPFILEHEADER hdr16; // a struct // default ctor does nothing, so data is uninitialized { size_t sz = sizeof(BITMAPFILEHEADER); // 16 // show uninitialized std::cout << "\n " << dumpByteHex ( ric(&hdr16), sz, " uninitialized struct hdr16" " may have 'noise'\n ") << std::endl; // because all elements are POD, // we can use std::memset to fill hdr16 with 0xff // to val count std::memset(&hdr16, 0xff, sz); std::cout << " " << dumpByteHex(ric(&hdr16), sz, " struct after memset(0xff) \n ") << std::endl; // zero the fields hdr16.bfType = 0; // 2 hdr16.bfSize = 0; // 4 hdr16.bfReserved1 = 0; // 2 hdr16.bfReserved2 = 0; // 2 hdr16.bfOffBits = 0; // 4 // NO visible pad, so any pad is still 0xff std::cout << " " << dumpByteHex( ric(&hdr16), sz, " struct fields zero'd (no field named" " 'pad', so any such stay 0xff ) \n ") << " ^^ ^^" << std::endl; // fields to unique values hdr16.bfType = 0x1122; // 2 hdr16.bfSize = 0x33445566; // 4 hdr16.bfReserved1 = 0xdead; // 2 hdr16.bfReserved2 = 0xbeef; // 2 hdr16.bfOffBits = 0x778899AA; // 4 std::cout << " " << dumpByteHex(ric(&hdr16), sz, " struct fields unique, pad still 0xff" "\n ") << " ^^ ^^" << std::endl; std::cout << " " << __PRETTY_FUNCTION__ << " END ==================================\n" << std::endl; } } // void T528::struct_pad_demo() void class_no_pad_demo() { std::cout << "\n\n " << __PRETTY_FUNCTION__ << " START ==================================" << std::endl; BITMAPFILEHEADER_t hdr14(0, 0, 0); // 14 // dummy field values--^--^--^ as fill { size_t sz = sizeof(BITMAPFILEHEADER_t); // 14 // show dummy initialized std::cout << "\n " << dumpByteHex(ric(&hdr14), sz, " class hdr14 after ctor," " fields have default values \n ") << std::endl; // because all elements are POD, // we can use std::memset to fill hdr14 with 0xff // to val count std::memset (&hdr14, 0xff, sz); // works on class, also std::cout << " " << dumpByteHex(ric(&hdr14), sz, " class after memset(0xff) \n ") << std::endl; // zero the private fields (no pad, and bfReserved zero'd) hdr14.setFields(0,0,0); // bfType, bfSize, bfOffBits // no pads, no 0xff shown std::cout << " " << dumpByteHex(ric(&hdr14), sz, " class fields zero'd (no 0xff shows there are no pads), 14 bytes\n ") << std::endl; // fields to unique values hdr14.setFields(0x2211, 0x66554433, 0xAA998877); // bfType bfSize bfOffBits std::cout << " " << dumpByteHex(ric(&hdr14), sz, " class fields unique, reserved's are 0\n ") << std::endl; std::cout << " " << __PRETTY_FUNCTION__ << " END ==================================\n" << std::endl; } } // void T528::class_no_pad_demo() void conversion_therapy() { std::cout << "\n\n " << __PRETTY_FUNCTION__ << " START ==================================" << std::endl; BITMAPFILEHEADER hdr16; // a struct std::memset(&hdr16, 0xff, 16); // fill with 0xff BITMAPFILEHEADER_t hdr14 (hdr16); // conversion ctor std::cout << "\n " << dumpByteHex(ric(&hdr14), 14, " class fields filled from struct of 0xff, " "reserved's are 0, no pad\n ") << std::endl; // unique private fields (no pad, and bfReserved zero'd) hdr14.setFields(0x1234, 0x56789abc, 0xdef0beef); // bfType, bfSize, bfOffBits hdr16 = hdr14.toBMFH(); std::cout << " " << dumpByteHex(ric(&hdr16), 16, " struct fields filled from unique fields class, " "reserved's are 0, pad un-touched, may be garbage \n ") << " ^^ ^^---may be garbage, but not accessible in struct \n" << std::endl; std::cout << " " << __PRETTY_FUNCTION__ << " END ==================================\n" << std::endl; } // void T528::conversion_therapy() }; // class T528_t 

guía de refactoría: el include y la función de utilidad C ++ dumpByteHex (), junto con sugerencias de secuencia para volver a unir este código en orden

 #include  // 'reduced' chrono footprint --------------vvvvvvv typedef std::chrono::high_resolution_clock HRClk_t; // std-chrono-hi-res-clk typedef HRClk_t::time_point Time_t; typedef std::chrono::milliseconds MS_t; // std-chrono-milliseconds typedef std::chrono::microseconds US_t; // std-chrono-microseconds typedef std::chrono::nanoseconds NS_t; // std-chrono-nanoseconds using namespace std::chrono_literals; // suffixes include 100ms, 2s, 30us #include  // std::cout, std::cerr #include  // setw(), setfill() #include  #include  #include  // memset #include  // assert // forward std::string dumpByteHex (char* startAddr, size_t len, std::string label = "", int indent = 0); // Linux substitutes typedef uint16_t WORD; typedef uint32_t DWORD; // reports 16 bytes -- compiler adds unlabled pad struct BITMAPFILEHEADER .... // sizeof() reports 14 bytes class BITMAPFILEHEADER_t .... //int main(int argc, char* argv[]) int main(int , char** ) .... // C++ support function std::string dumpByteHex (char* startAddr, // reinterpret_cast explicitly size_t len, // allows to char* from T* std::string label, // = "", int indent) // = 0 { std::stringstream ss; if(len == 0) { std::cerr << "\n dumpByteHex() err: data length is 0? " << std::endl << std::dec; assert(len != 0); } // Output description ss << label << std::flush; unsigned char* kar = reinterpret_cast(startAddr); // signed to unsigned // unsigned char* kar = static_cast(startAddr); // invalid cast std::string echo; // holds input chars until eoln size_t indx; size_t wSpaceAdded = false; for (indx = 0; indx < len; indx++) { if((indx % 16) == 0) { if(indx != 0) // echo is empty the first time through for loop { ss << " " << echo << std::endl; echo.erase(); } // fields are typically < 8 bytes, so skip when small if(len > 7) { if (indent) { ss << std::setw(indent) << " "; } ss << std::setfill('0') << std::setw(4) << std::hex << indx << " " << std::flush; } // normally show index } // hex code ss << " " << std::setfill('0') << std::setw(2) << std::hex << static_cast(kar[indx]) << std::flush; if((indx % 16) == 7) { ss << " "; wSpaceAdded = true; } // white space for readability // defer the echo-of-input, capture to echo if (std::isprint(kar[indx])) { echo += kar[indx]; } else { echo += '.'; } } // finish last line when < 17 characters if (((indx % 16) != 0) && wSpaceAdded) { ss << " "; indx++; } // when white space added while ((indx % 16) != 0) { ss << " "; indx++; } // finish line // the last echo ss << " " << echo << '\n'; return ss.str(); } // void dumpByteHex() 

Nota 1:

Mi código muestra una técnica típica de C ++ que proporciona un control absoluto del tamaño de los datos. La "técnica 1" de arriba es una de 5.

Las ideas se utilizaron durante la actualización de la SCU (unidad de control de estantería), que fue abrumada por el éxito de marketing. La SCU ya no podía manejar (ciclos de CPU / segundo insuficientes) un estante completo de las tarjetas de características más nuevas. Demasiado estado por segundo, demasiado PM (monitoreo de rendimiento) por segundo, demasiadas alarmas por segundo, etc.

La solución predeterminada para estos males es más capacidad de procesamiento y más memoria. Así ... portamos la aplicación SCU: de C a C ++ (un nuevo comstackdor), de 1 MIP (cisc) a 15 mip (risc) (nuevo procesador), de 1 MBytes a 16 MBytes, y un nuevo sistema operativo (vxWorks ) y se agregó Ethernet, y control y acceso SNMP y telnet.

El requisito principal es la compatibilidad con versiones anteriores . La base instalada de tarjetas de características existentes era grande. La nueva SCU tenía que trabajar con cualquier tarjeta de características de edad, en cualquier lugar instalado.

La técnica 1 (de 5) es la más fácil de entender y la adecuada para datos de recuento de campos pequeños. Puede ser tedioso con mensajes más grandes. El mayor recuento de campos de cualquier mensaje fue probablemente cercano a 100. Este gran número de campos inspiró al menos 2 de las otras 4 técnicas.


Nota 2:

Claramente, el soporte de automatización es posible para aplicar la Técnica 1.


Nota 3:

En mi humilde opinión, creo que hay una razón para el uso de pragmas o extensiones de comstackdor ... estos atajos pueden generar código para apoyar el "esquema más grande de las cosas". es decir, permite que la integración continúe, apoye el desarrollo de problemas sin mensajes, etc.