views:

1143

answers:

6

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?

A: 

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

Anton Tykhyy
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
Almost everything about bitfields is implementation-dependent.
Kragen Javier Sitaker
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
+2  A: 

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
Thanks for the suggestion but reordering isn't an option my structs are coming from an external standard.
NJChim
+9  A: 

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
Given that it's tagged as C++, writing a C++03-conforming program might be a better idea. ;)
jalf
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
They have a C89 compiler.
jalf
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
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
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
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
+1  A: 

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
if it absolutely definitely needs to be 6 bytes use unsigned char[6]. :)
sellibitze
+5  A: 

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
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
A: 

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