1

Similar question, but specific to packed structs: Why would the size of a packed structure be different on Linux and Windows when using gcc?


I'm building a shared library for Linux and Windows that needs to deal with well-structured data over a network connection. I'm using gcc 4.8.2 on Linux, and cross-compiling for Windows targets using i686-pc-mingw32-gcc 4.8.1.

I've made this little program to demonstrate the issue (note the GCC attributes are commented out, left them in for reference):

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

typedef uint16_t word_t;

typedef enum //__attribute__((__packed__))
{
  PRIO_0 = 0,
  PRIO_1,
  PRIO_2,
  PRIO_3,
  PRIO_4,
  PRIO_5,
  PRIO_6,
  PRIO_7,
}
prio_t;

typedef enum //__attribute__((__packed__))
{
  FLAG_A = 0,
  FLAG_B,
}
flag_t;

typedef struct //__attribute__((__packed__))
{
  word_t id     : 8;
  prio_t prio   : 3;
  flag_t flag_1 : 1;
  flag_t flag_2 : 1;
  flag_t flag_3 : 1;
  flag_t flag_4 : 1;
  word_t spare  : 1;
}
recd_t;


int main(int argc, char *argv[])
{
#define NAME_WIDTH 32

  printf("%-*s = %lu\n", NAME_WIDTH, "sizeof(prio_t)", (unsigned long)sizeof(prio_t));
  printf("%-*s = %lu\n", NAME_WIDTH, "sizeof(flag_t)", (unsigned long)sizeof(flag_t));
  printf("%-*s = %lu\n", NAME_WIDTH, "sizeof(recd_t)", (unsigned long)sizeof(recd_t));

  return 0;
}

I'm compiling for Linux using: gcc -g -Wall test.c -o ./test

And Windows: i686-pc-mingw32-gcc -g -Wall test.c -o ./test.exe

Very straightforward I thought. When run on Linux, the output is what I would expect:

sizeof(prio_t)                   = 4
sizeof(flag_t)                   = 4
sizeof(recd_t)                   = 4

But on Windows:

sizeof(prio_t)                   = 4
sizeof(flag_t)                   = 4
sizeof(recd_t)                   = 12

So what's the deal with the Windows sizes? Why are they different from Linux in this case?


I will eventually need to pack these enums and structs, but this issue appears before any packing is done. When enabled though, the results are similar:

Linux:

sizeof(prio_t)                   = 1
sizeof(flag_t)                   = 1
sizeof(recd_t)                   = 2

Windows:

sizeof(prio_t)                   = 1
sizeof(flag_t)                   = 1
sizeof(recd_t)                   = 6
Community
  • 1
  • 1
ardnew
  • 2,028
  • 20
  • 29
  • 1
    If you really want "well-structured data over a network connection", my recommendation is _not_ to try to do it with packed structs. I know that's what they seem to be for, but it can be arbitrarily hard to get it to work, and you still won't have solved the endianness issue. See further discussion at [this question](http://stackoverflow.com/questions/31036177/how-to-handle-portability-issues-in-a-binary-file-format). – Steve Summit Jul 10 '15 at 20:43
  • Why `uint16_t word_t;` and not `uint8_t word_t;`? – alk Jul 10 '15 at 20:55
  • @SteveSummit thanks for the link. not sure if you're assuming IP communication with the endianness comment, but it will more than likely be using ARINC 429. didn't want the network details to affect the question – ardnew Jul 10 '15 at 20:56
  • @alk external interface definition. not my choice :) – ardnew Jul 10 '15 at 20:57
  • 1
    If you have anything portable in mind: (1) don't use bitfields, (2) see 1, (3) serialize your data instead of using packed structs. – M.M Jul 11 '15 at 00:13
  • 9 warnings raised by gcc using '-Wall -Wextra -pedantic -std-c99' Some of which are about using gcc extensions. So, this code does not cleanly compile with gcc and certainly will not cleanly compile using ming. Strongly suggest 1) compile with all warnings enabled (for gcc, at a minimum use: -Wall -Wextra -pedantic -std=c99 2) fixing the code to obtain a clean compile. Then repost the corrected code. – user3629249 Jul 11 '15 at 01:14
  • do not 'typedef' struct and enum definitions it just clutters the code, leads to misunderstandings, and clutters the compiler name space. This statement: 'typedef uint16_t word_t;' gains you nothing and obscures the code. Strongly suggest using the built-in type names when every possible. – user3629249 Jul 11 '15 at 01:19
  • Besides the problems mentioned above, there are also two warnings about unused parameters: 'argc' and 'argv[]' Strongly suggest declaring main as: 'int main( void )' – user3629249 Jul 11 '15 at 01:28
  • @MattMcNabb that is becoming very apparent through this exercise, thanks :) – ardnew Jul 11 '15 at 05:58
  • @user3629249 please understand this code is to demonstrate a particular behavior and is not production code intended for thorough portability. the namespace and `main()` signature comments are a little out of context for the issue at hand, but they are appreciated :) – ardnew Jul 11 '15 at 06:01
  • @user3629249 i agree the `word_t` typedef serves no purpose in this code. i should have not included it. it's a leftover from the actual code that adheres to an interface where it absolutely helps readability – ardnew Jul 11 '15 at 06:13

1 Answers1

3

The C specification has an informative annex (Annex J) that summarizes unspecified behavior, undefined behavior, and implementation defined behavior. Here's what it says about bit-fields.

J.3 Implementation-defined behavior

A conforming implementation is required to document its choice of behavior in each of the areas listed in this subclause. The following are implementation-defined:

J.3.9 Structures, unions, enumerations, and bit-fields

  • Whether a "plain" int bit-field is treated as a signed int bit-field or as an unsigned int bit-field (6.7.2, 6.7.2.1).

  • Allowable bit-field types other than _Bool, signed int, and unsigned int (6.7.2.1).

  • Whether atomic types are permitted for bit-fields (6.7.2.1).
  • Whether a bit-field can straddle a storage-unit boundary (6.7.2.1).
  • The order of allocation of bit-fields within a unit (6.7.2.1).
  • The alignment of non-bit-field members of structures (6.7.2.1). This should present no problem unless binary data written by one implementation is read by another.
  • The integer type compatible with each enumerated type (6.7.2.2).

You can draw your own conclusions, but I would not use bit-fields in code that's intended to be portable.


It seems that on windows, the compiler starts a new "unit" every time the type changes. So in the unpacked case, you have a word_t (2 bytes) followed by a prio_t (4 bytes), a flag_t (4 bytes), and another word_t (2 bytes) for a total of 12 bytes. When packed it's 2,1,1,2 for a total of 6. If you declared all the fields as uint16_t, you'll probably get the correct size on windows, but you still have the problem of "the order of allocation of bit-fields within a unit" is implementation defined.

user3386109
  • 34,287
  • 7
  • 49
  • 68
  • thanks! looks like I'm using a handful of implementation-defined behaviors in this one struct alone. i didn't expect my two versions of gcc to have such different behavior (if that's the cause?) – ardnew Jul 10 '15 at 21:26
  • yes I just found that same thing! replacing my enum types with `word_t` for every member leaves me with the struct size I'm expecting. This means i will just need to do the appropriate type casting when reading and writing to the members. can't mark your comment as the answer, so I'll do what i can :) thanks – ardnew Jul 10 '15 at 21:40
  • @ardnew You're welcome, I moved the comment to the answer to tidy things up :) – user3386109 Jul 10 '15 at 21:44
  • @ardnew It's not surprising to see different implementation-defined behavior for different triples - each triple counts as a separate "implementation", there is no "gcc implementation", only "x86-linux-gnu", "i686-pc-mingw32", "x86_64-w64-mingw32", "x86_64-linux-musl", "aarch64-linux-gnu", etc. – o11c Jul 10 '15 at 22:07
  • @ardnew Note that in C++ you can specify the underlying type of an enum, but in C it is always the first of `unsigned int`, `signed int`, `unsigned long`, `signed long`, `unsigned long long`, `signed long long` that fits all the values, so you could try `unsigned int id : 8;` and keep the enums. – o11c Jul 10 '15 at 22:09
  • @o11c thanks for the platform triples distinction, very insightful – ardnew Jul 11 '15 at 06:17
  • @o11c the code is pure C (for now), so I'll play with the type resolution you describe to see if i can avoid using the same type for all members – ardnew Jul 11 '15 at 06:20