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