2

I have a structure that is intented to use 32 bits of storage:

struct foo_t {
    color_t color : 10
    some_type_t some_field : 22;
} 

, where color_t is an enum defined as

typedef enum {
    RED = 0,
    // other values...
    BLUE = 255
} color_t

Note that color_t values currently fit in 8 bits, although in the future we might add more values (thus we reserved 10 bits for color)

In C99, I wonder if there is any guarantee that the width of color will be respected by the compiler. As discussed in this question, the compiler might choose to represent color_t as a char. At that point, the specified width appears incorrect according to the C99 spec:

The expression that specifies the width of a bit-field shall be an integer constant expression with a nonnegative value that does not exceed the width of an object of the type that would be specified were the colon and expression omitted.

How can I enforce that the color field uses 10 bits, then? Note that the problem goes away if the compiler used a regular integer to represent color_t, but this behavior cannot be assumed.

Community
  • 1
  • 1
user3120046
  • 111
  • 6

3 Answers3

3

Add a final tag to your enum definition:

typedef enum {
    //...
    DUMMY_FORCE_WIDTH = 0xffffffffu, // INT_MAX,
} color_t;

That has the added benefit of forcing the compiler / ABI to give your enum enough space for growth everywhere.

Of couse, that presupposes that your compiler allows enum's as bit-field types. It need not do so, though it has to diagnose it as a constraint-violation than:

6.7.2.1 Structure and union specifiers §5(constraints)

A bit-field shall have a type that is a qualified or unqualified version of _Bool, signed int, unsigned int, or some other implementation-defined type. It is implementation-defined whether atomic types are permitted.

If you want to be strictly conforming, define the bit-field of type signed or unsigned, not of your special enum type. Still, that only guarantees 16 bits length possible.

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
  • 1
    I though about that (and I should have included it in the question), but then we have this "dummy value" that is confusing if we assume that `color_t` is part of a public API (which is applicable to my problem). You would need to document that `FORCE_WIDTH` cannot be used as a valid value for the enum, etc. – user3120046 Jun 24 '14 at 22:51
  • Actually, the name should not be in the API documentation at all. If `FORCE_WIDTH` is not descriptive enough for you, what about prepending a dummy to say that it is not a valid value once again? Anyway, this usage of a member for forcing the size is quite well-established. – Deduplicator Jun 24 '14 at 23:00
  • I accepted your answer because it implies minimal implementation changes, but you are right that combining the enum type and the bitfield declaration is not standard C99 to start with. What I do not understand quite well is: does the C99 spec preclude bit-fields of type uint16_t or uint64_t then? – user3120046 Jun 24 '14 at 23:28
  • No, it says an implementation **may allow** them with the specified semantics and **otherwise must diagnose** them as an error. (See the quote above) – Deduplicator Jun 24 '14 at 23:30
  • So using a uint16_t bit-field is non-portable. Interesting. Thanks! – user3120046 Jun 24 '14 at 23:32
2

Strictly speaking, C language (C99) only guarantees support for int, signed int, unsigned int and _Bool types in bit-field declarations, although specific implementations are explicitly allowed to support other types as implementation-dependent behavior.

In other words, in this case we are dealing with implementation-specific behavior, since you declared your bit-fields with enum types. The best approach would be to consult your compiler documentation, but I'd expect it to either honor your request for 10 bits or generate a diagnostic message if the chosen storage unit is not capable of accommodating 10 bits.

In my practice I usually use the following approach

typedef enum color_t {
  RED = 0,
  // other values...
  BLUE,
  COLOR_COUNT
} color_t;

#define COLOR_BIT_WIDTH 8u

STATIC_ASSERT(COLOR_COUNT <= (1 << COLOR_BIT_WIDTH));
// Use your favorite implementation of `STATIC_ASSERT`

struct foo_t {
  unsigned color : COLOR_BIT_WIDTH;
  ...
};

Note that this implementation does not attempt to reserve additional bits for the future expansion of the color type (do you really have a reason to do so?). Neither it attempts to automatically calculate the width of the bit-field. Instead, it simply catches the situation when the width is no longer sufficient, thus forcing the code maintainer to notice the problem and fix it manually.

P.S. An additional simple STATIC_ASSERT check can be added to make use that the bit-field width is not excessively large, if you care to have it "just right".

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • If possible, I'd like the solution to be within the standard so I do not depend on a specific compiler. As it stands, the code "might" work in some compiler but it is definitely (as you point out) non-portable. – user3120046 Jun 24 '14 at 23:08
  • It might make the maintainer notice there is a problem, but it does not neccessarily highlight all the spots the broken assumptions were hard-coded. – Deduplicator Jun 24 '14 at 23:14
  • @Deduplicator: The idea is to write the code using `COLOR_BIT_WIDTH` everywhere where the bit-width is required. That fully covers the matter of bit-width. If someone "forgets" about it and relies on `8` instead, it is indeed a problem, but not really a "highlightable" one. If you are talking about some other kind of broken assumption, you need to be more specific. – AnT stands with Russia Jun 24 '14 at 23:23
0

You could combine all that data in an fixed 32bit int (uint32_t), defined in stdint.h, and access values by bitwise operators.

Example:

uint32_t data = (uint32_t)color<<22 | (uint32_t)some_field;
color=(color_t ) (data>>22);
some_field =(some_type_t ) (data & 0x3FFFFF);
ETFovac
  • 111
  • 5
  • 1
    Then I cannot longer set/get the field values without using functions (that encapsulate the bitwise operations) If possible, I would like to keep `foo_t` as a struct and rely on the compiler for writing/reading the necessary bits. – user3120046 Jun 24 '14 at 22:48
  • @user3120046, you make that sound like a bad thing. I think having functions that hide the bitwise operators is a better idea than using a struct with bitfields and/or using bitwise operators all over the code base. – R Sahu Jun 24 '14 at 23:08
  • I suppose color(myfoo) vs. myfoo.color is the price of having a reliable and portable representation. Maybe there is some way of making compiler to take fixed width, but if you don't find it this could also be usefull. The other solution I know would be to take fisrt type that fits (uint16_t for color, and uint32_t for somefiled), but that would destroy memoery-efficenty you were trying to gain.You could reserve efficiency for network transfer and file saving with my way,and simlcity of acces with my other way, if you combine these two aproches And just convert them between the two when needed – ETFovac Jun 24 '14 at 23:10
  • @RSahu: it is not a terrible thing :-) but if you can solve a problem within the language, that seems to be the right solution: since bit-fields are allowed within structs, why use a function?. If you look at Deduplicator's answer, the design of the struct remains the same, so it is a relatively clean change. – user3120046 Jun 24 '14 at 23:23