8

I have a predefined struct (actually several) where variables span across 32-bit word boundary. In Linux (and Windows using GCC) I am able to get my structs to pack to the correct size using 'attribute((packed))'. However I cannot get it to work the same way using VC++ and #pragma pack.

Using GCC this returns a correct size of 6 bytes:

struct
{
    unsigned int   a                : 3;
    unsigned int   b                : 1;
    unsigned int   c                : 15;
    unsigned int   troubleMaker     : 16;
    unsigned short padding          : 13;
} __attribute__((packed)) s;

Using VC++ this returns an incorrect size of 8 bytes

#pragma pack(push)
#pragma pack(1)

struct
{
    unsigned int   a                : 3;
    unsigned int   b                : 1;
    unsigned int   c                : 15;
    unsigned int   troubleMaker     : 16;
    unsigned short padding          : 13;
} s;

#pragma pack(pop)

I can get things to work by splitting 'troubleMaker' across the boundary manually but I'd prefer not to. Any ideas?

NJChim
  • 287
  • 1
  • 2
  • 7
  • 2
    They don't just span 32-bit boundaries, they also span single byte boundaries. To get a size of 6, the variables have to start in the middle of a byte. I'm surprised GCC allows that. In any case, if I were you I'd drop the bitfields, and just make the struct contain an array of 6 chars (or 3 shorts, or whatever), and then write accessor functions to mask out the desired bits. – jalf Sep 21 '09 at 16:34

6 Answers6

17

Crazy idea: just write a C99 or C++03 -conforming program in the first place


I would suggest not using vendor-specific C language extensions to match device or network bit formats. Even if you get the fields to line up using a series of one-per-vendor language extensions, you still have byte order to worry about, and you still have a struct layout that requires extra instructions to access.

You can write a C99 conforming program that will work on any architecture or host and at maximum speed and cache efficiency by using the standardized C API string and memory copy functions and the Posix hton and ntoh functions.

A good practice is to use the following functions for which there exist published standards:

C99: memcpy(), Posix: htonl(), htons(), ntohl(), ntohs()

Update: here is some code that should work the same everywhere. You may need to get <stdint.h> from this project if Microsoft still hasn't implemented it for C99, or just make the usual assumptions about int sizes.

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <arpa/inet.h>

struct packed_with_bit_fields {  // ONLY FOR COMPARISON
    unsigned int   a        : 3;
    unsigned int   b        : 1;
    unsigned int   c        : 15;
    unsigned int   troubleMaker : 16;
    unsigned short padding  : 13;
} __attribute__((packed));       // USED ONLY TO COMPARE IMPLEMENTATIONS

struct unpacked { // THIS IS THE EXAMPLE STRUCT
    uint32_t a;
    uint32_t b;
    uint32_t c;
    uint32_t troubleMaker;
}; // NOTE NOT PACKED

struct unpacked su;
struct packed_with_bit_fields sp;
char *bits = "Lorem ipsum dolor";

int main(int ac, char **av) {
  uint32_t x;   // byte order issues ignored in both cases

  // This should work with any environment and compiler
  memcpy(&x, bits, 4);
  su.a = x & 7;
  su.b = x >> 3 & 1;
  su.c = x >> 4 & 0x7fff;
  memcpy(&x, bits + 2, 4);
  su.troubleMaker = x >> 3 & 0xffff;

  // This section works only with gcc
  memcpy(&sp, bits, 6);
  printf( sp.a == su.a
      &&  sp.b == su.b
      &&  sp.c == su.c
      &&  sp.troubleMaker == su.troubleMaker
      ? "conforming and gcc implementations match\n" : "huh?\n");
  return 0;
}
DigitalRoss
  • 143,651
  • 25
  • 248
  • 329
  • Given that it's tagged as C++, writing a C++03-conforming program might be a better idea. ;) – jalf Sep 21 '09 at 16:45
  • Heh, good point, but it's also tagged gcc, it's using C89 code, and if MS actually had a C compiler it's possible that would be the real target. :-) – DigitalRoss Sep 21 '09 at 16:51
  • The reason I am even trying to do it this way is I am using legacy code that someone else wrote. We've been lucky up to this point but some new messages have been causing this problem. I was pretty sure it needed to be rethought but I wanted to see if anyone had any useful input on how to make it work as it is... – NJChim Sep 21 '09 at 17:18
  • 2
    Sure, systems work is usually legacy code, I have sympathy. I just wrote the headline that way to scare any new guys that read it into writing conforming code for the next poor guy to come along and maintain. :-) – DigitalRoss Sep 21 '09 at 17:37
  • 1
    I don't see how the code you posted works everywhere since it is using __attribute__((packed)) which is the specific issue I am having... This doesn't exist in the vc++ compiler. – NJChim Sep 21 '09 at 20:56
  • You will note that the packed structure is used only in the *"// works only with gcc"* section and is there only to do the comparison at the end of the program to show that the two implementations produce identical final results – DigitalRoss Sep 22 '09 at 14:35
7

Alignment and ordering of bitfields are notoriously implementation-specific. It is much safer to declare a normal integer field and manipulate the "bitfields" within using masks and bitwise (| & ^) operators .

Paul Lalonde
  • 5,020
  • 2
  • 32
  • 35
  • true enough. Behavior of bitfields is not standardized over compilers. Bitfields tend to behave differently for a little-endian and big-endian architectures. Bitfields are great for internal status, but not so much for exchanged data because of this. – Adriaan Sep 22 '09 at 11:51
2

I don't believe this behavior is supported in Visual Studio. In addiction to the pack macro I tried using __declspec(align(1)) and got the same behavior. I think you are stuck with 12 bytes or re-ordering your structure a bit.

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • 1
    Thanks for the suggestion but reordering isn't an option my structs are coming from an external standard. – NJChim Sep 21 '09 at 17:11
0

I believe VC++ doesn't support this, and I have grave doubts whether GCC's behaviour in this respect is actually standard.

Anton Tykhyy
  • 19,370
  • 5
  • 54
  • 56
  • 1
    It's not standard in either case. The standard doesn't specify a way to pack structs. Both MSVC's pragma and GCC's attribute are nonstandard extensions. – jalf Sep 21 '09 at 16:38
  • 2
    Almost everything about bitfields is implementation-dependent. – Kragen Javier Sitaker Sep 21 '09 at 16:38
  • 1
    Expanding on what Kragen says: hence both compilers are standards-compliant in this regard. The assertion in the question that 6 bytes is "correct" and 8 bytes is "incorrect" is what contravenes the standard. – Steve Jessop Sep 21 '09 at 16:52
0

If it absoloutely defnitely needs to be 6 bytes then define it as 3 shorts and get the data out yourself ... it won't slow things down ... the compiler is just doing this anyway ...

Goz
  • 61,365
  • 24
  • 124
  • 204
0

Shorter example with only conforming code


struct unpacked {  // apparently my other example was too long and confusing
    uint32_t a;    // ...here is a much shorter example with only the conforming
    uint32_t b;    // ...code. (The other program had the gcc-specific declaration,
    uint32_t c;    // but only for test code. Still, it was a bit long.)
    uint32_t troubleMaker;
};

struct unpacked su;
char *bits = "Lorem ipsum dolor";

void f(void) {
  uint32_t x;

  memcpy(&x, bits, 4);
  su.a = x & 7;
  su.b = x >> 3 & 1;
  su.c = x >> 4 & 0x7fff;
  memcpy(&x, bits + 2, 4);
  su.troubleMaker = x >> 3 & 0xffff;
  return 0;
}
DigitalRoss
  • 143,651
  • 25
  • 248
  • 329
  • Please, edit the comment to be at the top of the struct, like this seems that each line is commenting what it is on the left, making impossible to understand what is happening at a glance – Matteo May 12 '20 at 07:47