4

I have inherited some code that makes use of bitfields in a struct:

typedef _my_flags {
    unsigned int x_ida:1;
    unsigned int x_foo:6;
    unsigned int x_bar:6;
    unsigned int x_bonzo:6;
    unsigned int x_pizza:6;
    unsigned int x_jack:1;
    unsigned int x_flashed:1;
    unsigned int x_flabberghasted:1;
} t_my_flags;

typdef _my_struct {
   short cat;
   int foo, bar, bla;
   t_my_flags flags; /* ... */
   char* name;
}

Both structures are part of the public API.

Now i'm currently in dire need of adding some additional flags (well, actually only one), so I was wondering whether it is safe to add extend the t_my_flags struct:

typedef _my_flags {
    unsigned int x_ida:1;
    unsigned int x_foo:6;
    unsigned int x_bar:6;
    unsigned int x_bonzo:6;
    unsigned int x_pizza:6;
    unsigned int x_jack:1;
    unsigned int x_flashed:1;
    unsigned int x_flabberghasted:1;
    unsigned int x_ready:1; /* new member */
} t_my_flags;

EDIT these structs are used in a dynamic library (so don't worry about persisting the data on a filesystem or sending it over a network)

I'm worried about breaking binary-compatibility.

My change will grow the t_my_flags struct from 28bit to 29bit, so I assume that it will live in a 32bit integer anyhow. So the total size of the structs is not going to change.

OTOH, I don't know whether the ordering of the flags-fields is going to change...

Of course, this ought to run on a variety of architectures (x86_64, i386, arm32, arm64, s390x, ppc) on all major OSs (Linux, macOS, Windows) with unknown compilers (obviously gcc/clang and MSVC, dunno about others; we stick to C89 for a reason...).

EDIT We can probably assume that on any given OS/architecture the same compiler is used (well, for Windows, we use gcc's -mms-bitfields flag which should give us bitfield compatibility with MSVC). The architecture/OS/compiler list given above is merely to indicate that I'm interested in how my use-case behaves in different environments. I'm less concerned about the interoperability between different environments).

So: how save is adding a new bitfield without changing the overall size in terms of binary compatibility?

umläute
  • 28,885
  • 9
  • 68
  • 122
  • 2
    There are no guarantees regarding the ordering of bitfields. How exactly is it being used? Is it being read/written to disk or the network, and if so how? – dbush May 09 '23 at 15:53
  • @dbush no implementation is allowed shuffle the bitfields :). Your remark does not apply here. It is always high-order to low-order or low-order to high-order) – 0___________ May 09 '23 at 16:00
  • @0___________ There's nothing posted that states the code is always compiled with the same compiler on the same platform. – Andrew Henle May 09 '23 at 16:07
  • IMO the names of the bit-fields strongly imply misuse. Something like `x_locked` implies atomic access is necessary. That's pretty much impossible with bit-fields. You'd have to lock the entire `struct` to access a field like that atomically. – Andrew Henle May 09 '23 at 16:08
  • @AndrewHenle It does not change anything as the original implementation will also be broken. Adding the additional bitfield will not change anythind – 0___________ May 09 '23 at 16:09
  • @AndrewHenle not necessarily. For example, cortex cores have atomic bit access memory regions (it is called bit banding). – 0___________ May 09 '23 at 16:11
  • @0___________ The implementation remains unidentified, and likely not guaranteed to never change. – Andrew Henle May 09 '23 at 16:12
  • @AndrewHenle they use bitfields - so they probably know what they are doing. You know nothing about their needs, implementation, portability needs and hardware – 0___________ May 09 '23 at 16:14
  • 1
    @0___________ *they use bitfields - so they probably know what they are doing.* **LOL** IME use of bit-fields is negatively correlated with that. *You know nothing about their needs, implementation, portability needs and hardware* Neither do you. – Andrew Henle May 09 '23 at 16:15
  • @AndrewHenle I know that they use them. And I do not think that they are ignorant. It implies that they know what they are doing. Your opinion about them is much more negative then – 0___________ May 09 '23 at 16:18
  • If portability is a key feature, consider using `uint32_t` or `uint64_t` and little functions that access the underlying type as public API. – Bob__ May 09 '23 at 16:19
  • 1
    You're [probably] okay. Adding a bit field is [unlikely] to change the bit ordering. So, you're probably not going to break the [binary] ABI. But, you're perilously close to overflowing a 32 bit int. If so, maybe you should "take the pain" now and convert to `uint64_t`. This _would_ break the ABI, but doing this now rather than later may be the right time to do it. But, since you're providing [partial, at least] source code, IMO, it isn't unreasonable to require the clients to rebuild. – Craig Estey May 09 '23 at 17:42
  • i think it is irrelevant whether or not we *know what we are doing*. esp. please do not assume that the existence of an `x_locked` member is in any way related to something like multithreading (it is not). i've changed the member name to something more general. – umläute May 10 '23 at 05:59

1 Answers1

3

will adding a new bit-field to my C-struct break ABI?

ABI is a property of the environment, not of your program or library. I guess you mean to ask whether your change will break binary compatibility, which is something different.

Since you say ...

Both structures are part of the public API.

... I infer that the code is part of a shared library (not a static one). This is indeed one of the primary cases where binary compatibility is relevant, because for most other purposes, binary compatibility is mooted by the fact that other code won't see any such changes unless you're recompiling it anyway.

The basic answer is "it depends", but a more honest one is "probably so", which is most safely interpreted as "yes".

My change will grow the t_my_flags struct from 28bit to 29bit, so I assume that it will live in a 32bit integer anyhow. So the total size of the structs is not going to change.

Generally speaking, that is a likely outcome, but in no way a guaranteed one. This is among the things that would be addressed by target systems' ABIs.

OTOH, I don't know whether the ordering of the flags-fields is going to change...

Again, the relevant ABI would tell you about details of structure layout, including bitfields. The C language spec doesn't say much about it, but does afford the possibility that the positions of the other flags would change within their addressible storage unit. Again, that question would be addressed by targets' ABIs -- and not necessarily in the same way by different ABIs.

In any case, if being part of the public API means that the definitions of these types are exposed to library users, and library clients are allowed / expected to access the members directly, then you have to assume that any change to the members of a structure breaks binary compatibility.

Even if the layout does not change with respect to the original members, you now have an additional member that old clients don't know about. You cannot then rely on the library's users to set that new member to an appropriate value when they allocate their own instances. That is mitigated somewhat if you (already) insist that users rely on some kind of initialization function provided by the library, but even if you do, "our update broke your program because it was using our library incorrectly" is a hard sell, no matter how true.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • apart from the rest of the answer: thanks for the clarification on "ABI" (I obviously have been using this terms wrongly for ages...) – umläute May 10 '23 at 05:55