These days a collegue has encountered a new problem within a program: Accessing members of a struct, we obtain from a device via USB, gives unreliable results. The program has worked compiled with the Qt version 4.x but in 5.2 the same code gives bad results. What happened?
Qt now uses the MinGW / gcc compiler 4.8 instead of 4.6 and it seems that the new gcc doesn’t handle pack structs correct anymore. After googling this effect I found a related bug report for gcc which is for version 4.7 and has a reporting date from March 2012. It mostly affects MinGW – the Windows port of gcc – due to other default settings of compiler options. Sure, there will be the same under Linux if we use the same compiler options. And two and a half years and more than one release versions of MinGW later this major bug is already present. Digging deeper shows that this bug is in gcc even since version 4.2 but affects the normal user in 4.7 due to changing the defaults of a compiler option (see below). In the past I was convinced about the quality of open source software like gcc but the reputation drops significantly n my eyes if such failures remain unsolved for such a long time.
To illustrate the problem, we should look at some code. We define some structs in C:
struct A{ long int a; short int b; float c; }; struct __attribute__((__packed__)) B{ long int a; short int b; float c; };
A and B looks identical but the compiler alignes the variables in A at 32-bit boundaries. It means between b and c is a gap of 2 bytes so the c is located at another physical address and the struct A is two bytes longer (12 bytes) than the sum of its elements (10 bytes). These aligning and the amount of filling gaps may be different from compiler to compiler and from platform to platform.
With B the compiler was instructed to pack the elements without gaps so the length. So B should be as short as possible with the given elements and should be compatible between compilers and platforms. But accessing elements in B may be slower than in A, what is the reason for the gaps in A.
The so called pack structs like the struct B on my example are very popular with binary data streams between computers and/or controllers on network interfaces or serial lines like USB or COM-ports. Because every compiler has it’s own syntax for pack structs and not all compilers supports it at all (like VisualBasic or LabView) using it not a very good code practise. But especially with complex and/or long structures it’s uncomparable comfortable.
There is another problem with pack structs: ARM processors cannot load float variables from address boundaries which are not 4-byte aligned. So the element c in sample struct B cannot be read with an ARM processor. It gives no error but a nonsense value what makes it very dangerous. And there is no other solution as change the source text, for example, to copy the bytes from the packed struct to a temporary float variable before accessing.
Back to the MinGW/gcc error. This error can be solved by adding the compiler switch -mno-ms-bitfields. In Qt we add QMAKE_CXXFLAGS+= -mno-ms-bitfields to the profile of our project.
A more portable solution would be to use the #pragma pack(1) directive:
#pragma pack(1) struct B{ long int a; short int b; float c; } #pragma pack()
It gives the desired behaviour with gcc and VisualC.
If using pack structs I generally recommend to test the compiler behaviour at runtime during initializing the application. A simple test like
if(sizeof(B)!=10)printf("Error, Pack structs don't work!");
will detect unexpected compiler behaviour and helps porting applications.
Conclusion:
- Avoid using of “packed structs” and transmitting of binary streams which are interpreted as structs, because they are compiler and platform dependend
- If using them nevertheless, be critical and test the structs at runtime if they have the desired size.
- If an ARM platform for your program seems possible (even later, what is true et least for every Linux program) keep in mind that floats never can be accessed directly from an address which is not aligned to 4.