9

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?

1
  • 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. Commented Sep 21, 2009 at 16:34

6 Answers 6

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;
}
Sign up to request clarification or add additional context in comments.

6 Comments

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

1 Comment

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.
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.

1 Comment

Thanks for the suggestion but reordering isn't an option my structs are coming from an external standard.
0

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

3 Comments

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.
Almost everything about bitfields is implementation-dependent.
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.
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 ...

1 Comment

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

1 Comment

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.