Packing bools with bit field (C++)
Asked Answered
H

4

5

I'm trying to interface with Ada code using C++, so I'm defining a struct using bit fields, so that all the data is in the same place in both languages. The following is not precisely what I'm doing, but outlines the problem. The following is also a console application in VS2008, but that's not super relevant.

using namespace System;
int main() {
    int array1[2] = {0, 0};
    int *array2 = new int[2]();
    array2[0] = 0;
    array2[1] = 0;
    
    #pragma pack(1)
    struct testStruct {
        // Word 0 (desired)
        unsigned a : 8;
        unsigned b : 1;
        bool c : 1;
        unsigned d : 21;
        bool e : 1;

        // Word 1 (desired)
        int f : 32;

        // Words 2-3 (desired)
        int g[2]; //Cannot assign bit field but takes 64 bits in my compiler
    };
    testStruct test;

    Console::WriteLine("size of char: {0:D}", sizeof(char) * 8);
    Console::WriteLine("size of short: {0:D}", sizeof(short) * 8);
    Console::WriteLine("size of int: {0:D}", sizeof(int) * 8);
    Console::WriteLine("size of unsigned: {0:D}", sizeof(unsigned) * 8);
    Console::WriteLine("size of long: {0:D}", sizeof(long) * 8);
    Console::WriteLine("size of long long: {0:D}", sizeof(long long) * 8);
    Console::WriteLine("size of bool: {0:D}", sizeof(bool) * 8);
    Console::WriteLine("size of int[2]: {0:D}", sizeof(array1) * 8);
    Console::WriteLine("size of int*: {0:D}", sizeof(array2) * 8);
    Console::WriteLine("size of testStruct: {0:D}", sizeof(testStruct) * 8);
    Console::WriteLine("size of test: {0:D}", sizeof(test) * 8);

    Console::ReadKey(true);

    delete[] array2;
    return 0;
}

(If it wasn't clear, in the real program, the basic idea is that the program gets a void* from something communicating with the Ada code and casts it to a testStruct* to access the data.)

With #pragma pack(1) commented out, the output is:

size of char: 8
size of short: 16
size of int: 32
size of unsigned: 32
size of long: 32
size of long long: 64
size of bool: 8
size of int[2]: 64
size of int*: 32
size of testStruct: 224
size of test: 224

Obviously 4 words (indexed 0-3) should be 448 = 32*4 = 128 bits, not 224. The other output lines were to help confirm the size of types under the VS2008 compiler.

With #pragma pack(1) uncommented, that number (on the last two lines of output) is reduced to 176, which is still greater than 128. It seems that the bools aren't being packed together with the unsigned ints in "Word 0".

Note: a&b, c, d, e, f, packaged in different words would be 5, +2 for the array = 7 words, times 32 bits = 224, the number we get with #pragma pack(1) commented out. If c and e (the bools) instead take up 8 bits each, as opposed to 32, we get 176, which is the number we get with #pragma pack(1) uncommented. It seems #pragma pack(1) is only allowing the bools to be packed into single bytes by themselves, instead of words, but not the bools with the unsigned ints at all.

So my question, in one sentence: Is there a way to force the compiler to pack a through e into one word? Related is this question: C++ bitfield packing with bools , but that doesn't answer my question; it only points out the behavior I'm trying to force to go away.

If there is literally no way to do this, does anyone have any ideas for workarounds? I'm at a loss, because:

  1. I was asked to avoid changing the struct format that I'm copying (no re-ordering).
  2. I don't want to change the bools to unsigned ints because it may cause problems down the road with constantly having to re-cast it to bool and maybe accidentally using the wrong version of an overloaded function, not to mention making the code more obscure for others who read it later.
  3. I don't want to declare them as private unsigned ints then make public accessors or something because all other members of all other structs in the project are accessed directly without () afterward, so it would seem a bit hacky and obtuse, and one would almost NEED the IntelliSense or trial-and-error to remember which needs () and which doesn't.
  4. I would like to avoid creating another struct type just for the data conversion (and e.g. make a constructor for testStruct that takes in a single testStructImport-type object) because the actual struct is very long with lots of bit-field-specified variables.
Honea answered 15/7, 2014 at 18:37 Comment(12)
Maybe you could change the Ada corresponding record to match these alignments?Tecla
Make all members of that bit-field private and write accessors - it's an implementation detail anyway.Descry
I'll look up the details, but what it comes down to is bitfields of different types won't "merge", and each new bitfield type is aligned for the underlying type. You'll want to store each of these as unsigned and do casts as appropriate.Placentation
Also, bitfields are in an implementation defined order, so it's best to do it manually anywayPlacentation
@Holt: That's what I meant by #1 in the list. I was asked to avoid changing the Ada record. (It would also probably take several hours, even if I did. Like I said, very long, and a mess to reorganize.)Honea
@KeitM Okay I thought you were talking of the C structure. By the way, on windows with g++ v4.6.2 (MinGW), the size of your structure is 128 bits, even without #pragma pack(1).Tecla
As a workaround, I'd try masks and accessors.Bermudez
DieterLücking: That's what I meant by #3. Every other struct in the (large) project does not use accessors. @MooingDuck: That's what I meant by #2. It would potentially be a lot of casts and cause problems with overloaded functions when a cast is forgotten. Was wondering if there's a way to force it more directly. As for the order, what do you mean by "manually?"Honea
@Tecla Well, I was talking about both, technically. I'd have to re-order both for the data locations to match up.Honea
Are you targeting Microsoft C++ or C++/CLI (which your example uses)? The behavior is about the same (only "unsigned" bit fields get packed) but if using C++/CLI you can actually use properties to encapsulate your unpackable bools (and imho that would be nicer than accessors)Elagabalus
@Elagabalus Honestly, I have no idea which I'm using. I think I may have mixed-and-matched, going from the Wikipedia article.Honea
Visual Studio 2015 also reproduces the issue, in addition to VS 2008 mentioned in the question.Honea
H
0

There is no easy, elegant method without using accessors or an interface layer. Unfortunately, there is nothing like a #pragma thing to fix this. I ended up just converting the bools to unsigned int and renaming variables from e.g. f to f_flag or f_bool to encourage correct usage and make it clear what the variables contained. It's lower-effort than Thomas's solution, but not as robust, obviously, and still gets around some of the main drawbacks with any of the easier methods.

Honea answered 25/8, 2016 at 22:37 Comment(0)
N
5

I recommend that you create a "normal" structure without any bit packing. Use default POD types for the members.

Create interface functions for loading the "normal" fields from a buffer (uint8_t), and storing to a buffer.

This will allow you to use the data members in a sane method in your program. The bit packing and unpacking will be handled by the interface function. The bit twiddling should use bitwise AND and bitwise OR functions and not rely on the bit field notation in a structure. This will allow you to adjust the bit twiddling and be more portable among compilers.

This is how I designed my protocol classes. And I don't have to worry about bit field positioning, Endianess or things of that sort.

Also, I can use block I/O for reading and writing the buffer.

Nutation answered 15/7, 2014 at 18:56 Comment(4)
Thanks, that is another workaround. By the way, though, uint8_t does not exist in Visual Studio 2008 (my IDE, as I mentioned). It seems it is a C++11 thing.Honea
uint8_t is part of C99 and defined in "stdint.h", you can either update to at least Visual Studio 2010 or download this header file: see this question.Comprehensible
@Étienne Yeah, I tried including it, and VS2008 couldn't find it. Upgrading is less of an option since we're commercial and don't want to make our new projects incompatible with older machines, so thanks for the link!Honea
Or you could use unsigned char with Visual Studio 2008.Nutation
H
0

Years after I posted this question, user @WaltK added this comment to the linked, related question:

"If you want to have more control over the layout of bit field structures in memory, consider using this bit field facility, implemented as a library header file."

Honea answered 15/7, 2014 at 18:37 Comment(1)
I hate to make an answer that's almost just a link to an answer, but it's a long PDF, and I can't copy-paste out of it, so I don't think it makes sense to quote the whole thing in-answer. If the link ever breaks, try searching "C plus plus library bit fields" or the github user at github.com/wkaras .Honea
W
0

Try packing in this way:

    #pragma pack( push, 1 )
struct testStruct {
    // Word 0 (desired)
    unsigned a : 8;
    unsigned b : 1;
    unsigned c : 1;
    unsigned d : 21;
    unsigned e : 1;

    // Word 1 (desired)
    unsigned f : 32;

    // Words 2-3 (desired)
    unsigned g[2]; //Cannot assign bit field but takes 64 bits in my compiler
};
#pragma pack(pop)
Weywadt answered 15/7, 2014 at 20:16 Comment(1)
Oh, you edited it. Yes, that works, as it also does without changing the pragma lines. I addressed that in #2 in the list of workarounds that I didn't like.Honea
H
0

There is no easy, elegant method without using accessors or an interface layer. Unfortunately, there is nothing like a #pragma thing to fix this. I ended up just converting the bools to unsigned int and renaming variables from e.g. f to f_flag or f_bool to encourage correct usage and make it clear what the variables contained. It's lower-effort than Thomas's solution, but not as robust, obviously, and still gets around some of the main drawbacks with any of the easier methods.

Honea answered 25/8, 2016 at 22:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.