14

We recently found some code submitted to our codebase, along the following lines:

#pragma pack(push,1)
struct xyzzy {
    BITMAPINFOHEADER header;
    char plugh;
    long twisty;
} myVar;

My question is: does the packing apply only to the immediate structure or might it affect the packing of the BITMAPINFOHEADER as well. I can't see the latter case being very useful since it would make the structure different to what you would get from the Windows API calls, for example. Case in point, let's assume the structure is:

typedef struct {
    char aChar;
    DWORD biSize;
} BITMAPINFOHEADER;

That structure would be vastly different with a packing of one rather than the default eight for Windows (32-bit anyway, may be sixteen for 64-bit).

Is the BITMAPINFOHEADER "protected" from the packing by virtue of the fact it's almost certainly be declared earlier? If it was declared as part of the outer declaration, would it be subject to the packing then?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • 1
    Fortunately the case in point shouldn't be a problem anyway, as the "real" `BITMAPINFOHEADER` is already "perfectly" packed. But I see the point and it's an interesting question, +1. – Matteo Italia Mar 06 '18 at 07:22
  • 1
    @Yunnosch: the standard doesn't know a thing about `#pragma pack`, so we are left with vendor-specific documentation. – Matteo Italia Mar 06 '18 at 07:23
  • 1
    @MatteoItalia Yes, that is what I intended to imply. Still, referring to at least one specified tool chain and its documentation would make a (tool specific) decent answer. – Yunnosch Mar 06 '18 at 07:25
  • A Win32 application should not declare BITMAPINFOHEADER, it is [provided by Windows](https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx). We'll have to assume it is correctly packed. – Lundin Mar 06 '18 at 09:06
  • 3
    To avoid such problems the headers declarations are crafted in such a way to keep the correct variables layout whichever packing will be applied. Have a look inside Win or *nix headers and you'll find a lot of padding variables to keep layout alignment. See my answer https://stackoverflow.com/questions/44485168/is-struct-packing-deterministic/44486156#44486156 ;-) – Frankie_C Mar 06 '18 at 11:12

5 Answers5

11

From the relevant documentation:

pack takes effect at the first struct, union, or class declaration after the pragma is seen. pack has no effect on definitions.

header is a member definition, so it isn't affected.

it was declared as part of the outer declaration, would it be subject to the packing then?

Yes, as it would be a struct declaration.

Also, as Lightness Races in Orbit remarks in a comment, a more convincing wording can be found immediately before:

To pack a class is to place its members directly after each other in memory.

i.e. it says nothing about what those members themselves contain, which may be data and/or padding. The fact that (as explored above) packedness is attached to a type would seem to reinforce that


Still, the documentation is vague enough, so it's best to test that this interpretation is correct; both gcc and VC++ behave as expected. Not that I'm particularly surprised - anything different would break havoc in the type system (taking a pointer to a member of a packed structure would actually provide a pointer to something different than its type says1).

The general idea is: once you finish defining a struct, its binary layout is fixed, and any of its instantiations will conform to it, including subobjects of packed structures. The current #pragma pack value is considered only when defining new structures, and when doing so the binary layout of the members is a fixed black box.


Notes

  1. To be honest, this is a bit of an x86-centric view; machines with stronger alignment requirements would object that even pointers to layout-correct but misaligned structures aren't kosher: although the offsets of fields relative to the given pointer are correct, they aren't really pointers that can be used as they are.

    OTOH, given a pointer to an unaligned object you can always detect that it's unaligned and memcpy it to a correctly-aligned location, so it's not as bad as a hypothetical pointer to a packed object, whose layout is effectively unknown unless you happen to know the packing of its parent.

Community
  • 1
  • 1
Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
  • _"`header` is a member definition, so it isn't affected."_ I'm not entirely convinced by this rationale. Clearly `xyzzy` itself is also a definition - there is something dodgy about the MSDN wording (go figure). I suspect what it _really_ means is that you can't apply `__attribute__((__packed__))` to the definition of an instance of type `xyzzy` and expect it to be packed, if the definition of `xyzzy` itself wasn't already. – Lightness Races in Orbit Mar 06 '18 at 17:49
  • 1
    Personally I think the only "proof" (other than the testing you've done) is this wording: _"To pack a class is to place its members directly after each other in memory"_. i.e. it says nothing about what those members themselves contain, which may be data and/or padding. The fact that (as explored above) packedness is attached to a _type_ would seem to reinforce that. – Lightness Races in Orbit Mar 06 '18 at 17:51
  • @LightnessRacesinOrbit I agree with both considerations, although the way I see it the definition thing is meant to apply both to members and actual variable definitions (e.g. globals or even locals defined while the `#pragma` is active). That being said, your quotation and its interpretation is quite significant as well, I'll add it to the answer. – Matteo Italia Mar 06 '18 at 18:19
9

From what I have seen, it only applies to immediate structure.

Take a look at the snippet below:

#include <stdio.h>

struct /*__attribute__((__packed__))*/ struct_Inner {
    char a;
    int b;
    char c;
};

struct __attribute__((__packed__)) struct_Outer {
    char a;
    int b;
    char c;
    struct struct_Inner stInner;
};

int main() 
{
   struct struct_Inner oInner;
   struct struct_Outer oOuter;
   printf("\n%zu Bytes", sizeof(oInner));
   printf("\n%zu Bytes", sizeof(oOuter));
}

Prints:

12 Bytes
18 Bytes

When I pack the struct_Inner it prints:

6 Bytes
12 Bytes

This is when using GCC 7.2.0.

Again, this is not specific to C standard by any means (I just had to read), its more to do with what compilers do.

Is the BITMAPINFOHEADER "protected" from the packing by virtue of the fact it's almost certainly be declared earlier?

I guess yes. It would entirely depend upon the way BITMAPINFOHEADER is declared.

tambre
  • 4,625
  • 4
  • 42
  • 55
WedaPashi
  • 3,561
  • 26
  • 42
  • 6
    Don't worry, providing tool-specific "observations from the wild" (with adequate statement of tool name and version) is considered here at StackOverflow to be adequate participation in the answering process. – Yunnosch Mar 06 '18 at 07:31
  • @Yunnosch Considering the question involves `#pragma ...`, I'd say a tool-specific answer is all that's *possible*. – Andrew Henle Mar 06 '18 at 10:59
  • This is irrelevant to a question about MSVC. – Ruslan Mar 06 '18 at 14:39
6

Assuming GCC (or Clang emulating GCC), you can find some relevant information at Structure Layout Pragmas, where it says that the presence of the push preserves the current packing state on a stack of states:

For compatibility with Microsoft Windows compilers, GCC supports a set of #pragma directives that change the maximum alignment of members of structures (other than zero-width bit-fields), unions, and classes subsequently defined. The n value below always is required to be a small power of two and specifies the new alignment in bytes.

  1. #pragma pack(n) simply sets the new alignment.
  2. #pragma pack() sets the alignment to the one that was in effect when compilation started (see also command-line option -fpack-struct[=n] see Code Gen Options).
  3. #pragma pack(push[,n]) pushes the current alignment setting on an internal stack and then optionally sets the new alignment.
  4. #pragma pack(pop) restores the alignment setting to the one saved at the top of the internal stack (and removes that stack entry). Note that #pragma pack([n]) does not influence this internal stack; thus it is possible to have #pragma pack(push) followed by multiple #pragma pack(n) instances and finalized by a single #pragma pack(pop).

Thus, the #pragma in the code added affects all subsequent structure definitions too, until countermanded by #pragma pack(pop). I'd be worried about that.

The documentation doesn't say what happens if you do #pragma pack(pop) when there is no state on the internal stack. Most likely, it falls back to the setting when compilation started.

Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
6

According to GCC reference:

In the following example struct my_packed_struct's members are packed closely together, but the internal layout of its s member is not packed - to do that, struct my_unpacked_struct would need to be packed too.

  struct my_unpacked_struct
   {
      char c;
      int i;
   };

  struct my_packed_struct __attribute__ ((__packed__))
    {
       char c;
       int  i;
       struct my_unpacked_struct s;
    };

You may only specify this attribute on the definition of a enum, struct or union, not on a typedef which does not also define the enumerated type, structure or union.

msc
  • 33,420
  • 29
  • 119
  • 214
  • 3
    TBH this is a correct answer *for gcc* and for its `__attribute__ ((__packed__))`, OP actually asked about `#pragma pack` on VC++ 2015. Of course I expect it to be the same (hence the upvote), but given that we are talking about compiler-specific behavior and non-standard extensions I'd expect an answer to tackle the specifics of OP's environment. – Matteo Italia Mar 06 '18 at 09:10
  • It must be like this so that it's possible to obtain a pointer to the nested struct and receive the same data layout as any other pointer of that type. – boot4life Mar 06 '18 at 14:37
  • 2
    Why are you citing GCC reference to answer a question about MSVC? – Ruslan Mar 06 '18 at 14:38
3

This does not directly answer the question, but might offer an idea why existing compilers decided not to pack sub-structs, and why future compilers are unlikely to change that.

A packing that transitively affected sub-structures would break the type system in subtle ways.

Consider:

//Header A.h
typedef struct {
    char aChar;
    DWORD biSize;
} BITMAPINFOHEADER;


// File A.c
#include <A.h>

void doStuffToHeader(BITMAPINFOHEADER* h)
{
    // compute stuff based on data stored in h
    // ...
}


// File B.c
#include <A.h>

#pragma pack(push,1)
struct xyzzy {
    BITMAPINFOHEADER header;
    char plugh;
    long twisty;
} myVar;

void foo()
{
    doStuffToHeader(&myVar.header);
}

I pass a pointer to a packed struct to a function that is not aware of the packing. Any attempts by the function to read or write data from the struct will easily break in horrible ways. If a compiler deems this unacceptable, it has two possibilities for fixing the problem:

  • Transparently unpack the sub-struct into a temporary for the function call and re-pack the result later.
  • Internally change the type of the header field in xyzzy to something that indicates that it's now a packed type and incompatible with normal BITMAPINFOHEADER.

Both of these are obviously problematic. With this reasoning, even if I wanted to write a compiler that supported packing of sub-structs, I would run into numerous follow-up problems. I'd expect my users to start questioning my design decisions in this regard very soon.

ComicSansMS
  • 51,484
  • 14
  • 155
  • 166