4

Consider the following small program, which uses a union to assign the bits of an integer directly rather than using bit operations. The print statements print correctly, but that does not mean it will always work.

Is this well-defined behavior in C?

#include <stdio.h>
#include <inttypes.h>


union IntBitField{
    int32_t foo;

    struct bitfield {
        unsigned int BIT0:1;
        unsigned int BIT1:1;
        unsigned int BIT2:1;
        unsigned int BIT3:1;
        unsigned int BIT4:1;
        unsigned int BIT5:1;
        unsigned int BIT6:1;
        unsigned int BIT7:1;
        unsigned int BIT8:1;
        unsigned int BIT9:1;
        unsigned int BIT10:1;
        unsigned int BIT11:1;
        unsigned int BIT12:1;
        unsigned int BIT13:1;
        unsigned int BIT14:1;
        unsigned int BIT15:1;
        unsigned int BIT16:1;
        unsigned int BIT17:1;
        unsigned int BIT18:1;
        unsigned int BIT19:1;
        unsigned int BIT20:1;
        unsigned int BIT21:1;
        unsigned int BIT22:1;
        unsigned int BIT23:1;
        unsigned int BIT24:1;
        unsigned int BIT25:1;
        unsigned int BIT26:1;
        unsigned int BIT27:1;
        unsigned int BIT28:1;
        unsigned int BIT29:1;
        unsigned int BIT30:1;
        unsigned int BIT31:1;
    } bar;
} FooBar;
int main(){
    FooBar.foo = 0;
    printf("Size of the union: %zu\n", sizeof(union IntBitField));
    printf("Before setting any bits %"PRId32"\n", FooBar.foo);
    FooBar.bar.BIT31 = 1; // Highest order bit
    printf("Set BIT31 %"PRId32"\n", FooBar.foo);
}

I have looked at this question and this question, as well as this question but I am still uncertain.

Community
  • 1
  • 1
merlin2011
  • 71,677
  • 44
  • 195
  • 329
  • 2
    `int` is not always 32-bit long. – MikeCAT Mar 03 '16 at 01:11
  • @MikeCAT, I have corrected the question to represent my intent better. – merlin2011 Mar 03 '16 at 01:13
  • 2
    You should include `inttypes.h` and use `"%"PRId32` instead of `"%d"` to print `int32_t`. – MikeCAT Mar 03 '16 at 01:14
  • 1
    @MikeCAT, That is technically correct, but it is orthogonal to the question. If you'd like to edit it, please feel free to do so. – merlin2011 Mar 03 '16 at 01:16
  • 1
    @merlin2011: Please keep in mind that questions are not only read by you or me or Mike, but also by beginners who tend to adopt bad habbits like using wrong type specifiers in format strings. With you reps, you shouldn't have such simple errors in your questions. Also, I don't understand why you don't use `int32_t` consistently. That would make concerns about the width of `int` irrelevant. I'm also not sure why you want to store `-1` and `0` only in the bits (2's complement `int` assumed). I'd think a bit should conform to `_Bool` and boolean operators in general, thus `0` and `1`. – too honest for this site Mar 03 '16 at 02:11
  • @Olaf, Both of you have edit privileges, and I have already indicated that I do not mind edits. Why are you not exercising them? – merlin2011 Mar 03 '16 at 02:15
  • Can't speak for @MikeCAT, but I think it is your question, thus it's your's to edit. With higher reps comes higher responsibility. I'm also not really sure about your intend. – too honest for this site Mar 03 '16 at 02:24
  • I'd actually strongly recommend using a simple `uint32_t` and a set of `inline` functions so set/clear/test a bit. That way you can also use a variable to specify the bit. And that will not be any slower than a bit-field struct. – too honest for this site Mar 03 '16 at 02:30
  • @MikeCAT: Why did you remove `stdint.h`? – too honest for this site Mar 03 '16 at 02:31
  • @Olaf Because its functionalities are included in `inttypes.h` and it isn't needed. [c - difference between stdint.h and inttypes.h - Stack Overflow](http://stackoverflow.com/questions/7597025/difference-between-stdint-h-and-inttypes-h) – MikeCAT Mar 03 '16 at 02:32

2 Answers2

4

The answer is the code will always compile/run (it that sense it is "standard"), but it is not "portable" in the sense that the value of foo on the last line is not guaranteed. Different compiler implementations can store the bit fields in different bits of the memory, which is to say the memory foo references. In fact, when considering padding the bit field might not even completely overlap foo.

This is what the first answer you link to is trying to say.

Brad
  • 3,190
  • 1
  • 22
  • 36
  • If I force alignment, would that make it portable? Also, can you link to a standard document that indicates it is not portable? – merlin2011 Mar 03 '16 at 01:25
  • Not really - it depends on the compiler and endian-ness of the system too, so no. If you really want cross-platform, portable bit manipulation you have to roll your own for your exact targets or use a bit-array library. – Brad Mar 03 '16 at 01:26
  • I added `gcc` as a tag. – merlin2011 Mar 03 '16 at 01:27
  • references and details here and links at the bottom: http://en.cppreference.com/w/cpp/language/bit_field – Brad Mar 03 '16 at 01:29
  • If `int` is 32-bit then I think it is guaranteed to exactly overlap (structs may not have initial padding, and there is a rule that the next bit-field must be stored in the same unit as the previous bit-field if it is not full) – M.M Mar 03 '16 at 02:03
  • usually it does - and I've never personally used a compiler that didn't. however the spec reads: "The following properties of bit fields are unspecified: Alignment of the allocation unit that holds a bit field" - Seems pretty clear that a compliant compiler could align however it wants – Brad Mar 03 '16 at 02:12
  • It is even worse: a single bit `int` can either hold `0` and `1` or `0` and `-1` assuming 2's complement. It is implementation defined as I understand the standard - if I'm wrong, it would be `0` and `-1`. – too honest for this site Mar 03 '16 at 02:27
3
  1. Its OK to use int for bit fields. Better to use signed int for bit fields. Even better to use unsigned. This is one place (maybe the only place) where int and signed int differ. signed int xxx:1; takes on the values of 0 or -1. int xxx:1; takes on the values of 0 or 1 or 0 or -1 - it is implementation defined.

  2. FooBar.bar.BIT31 = 1; // Highest order bit is not portable in that BIT31 may be the least significant bit. Code is counting on a certain endian.

  3. Portability suffers on systems where int/unsigned width vary like 16, 32 or 64 bit.

  4. Using long BIT31:1; or uint32_t BIT31:1; may not be portable as any bit-filed wider than int/unsigned is either implementation defined (or possible UB).

  5. Given the potential for lots of porting problem, strongly advise against using bit fields for portable sections of code.

  6. If the total bit width is not a multiple of sizeof(unsigned)*CHAR_BIT and/or the fields attempt to cross alignment restricted bit width, padding can be expected. This is often a significant complication.

  7. Only place I use bit fields is when mapping hardware addressed memory and that section of code needs limited, if any, portability.

Conclusion:

Is it well-defined behavior to address a 32-bit int unsigned using a bit field inside a union?

In general no. In specific applications, usually yes, when using unsigned fields that do not cause padding.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256