Reputation: 53
I have difficulty to understand the portability of bit fields in C. Imagine I have a shared library composed of only two files, libfoobar.h
(the public header) and libfoobar.c
, with the following simple content:
libfoobar.h
:
typedef struct some_bitfield_T {
unsigned char foo:3;
unsigned char bar:2;
unsigned char tree:2;
unsigned char window:1;
} some_bitfield;
extern unsigned int some_function (some_bitfield input);
libfoobar.c
:
#include "libfoobar.h"
unsigned int some_function (some_bitfield input) {
return input.foo * 3 + input.bar + input.tree + 1 - input.window;
}
After having compiled and installed the library, I test it with a program named test
.
test.c
:
#include <stdio.h>
#include <libfoobar.h>
int main () {
some_bitfield my_attempt = {
.foo = 6,
.bar = 3,
.tree = 1,
.window = 1
};
unsigned int some_number = some_function(my_attempt);
printf("Here is the result: %u\n", some_number);
return 0;
}
Is there any possibility that the test
program above will produce anything different than the following output?
Here is the result: 22
If yes, when? What if the library is compiled by someone else other than me? What if I use different compilers for the library and the test
program?
Upvotes: 0
Views: 604
Reputation: 141603
I have difficulty to understand the portability of bit fields in C
Well, there's nothing much understand - bitfields are not portable.
Is there any possibility that the test program above will produce anything different than the following output?
The most common case is communication between user space and kernel space. Sometimes the communication uses pointers to structures. Headers written by library implementators, like glibc
, that wrap kernel syscalls sometimes duplicate the same structures that are defined inside kernel source tree. For proper communication, the padding in those structures must be the same on both sides - on the kernel side when kernel is compiled and on user space side when we happy compile our own project even years after kernel was compiled.
On most architectures + operating systems there exists a "ABI" - a set of rules that also determine how structures should be padded and how bit-fields should be packed. A compiler may adhere to that ABI or not. When gcc is used to cross-compile for windows from linux, ex. __attribute__ ((ms_struct))
needs be used to ensure that proper structure packing is used that is compatible with the shenanigans microsoft compilers do.
So to answer: Is there any possibility
- sure there is, different compiler flags or settings may cause different packing or padding between structure members So I can compile the program with gcc -fpack-struct=100
and you compiled your shared library with gcc -fpack-struct=20
and oops. But this isn't limited to structure padding - someone else can compile your program with unsigned int
having 64-bits instead of 32-bit, so the return value of the function may be unexpected.
If yes, when?
When incompatible code generation methods are used to create machine code that depend on specified ABI to communicate. From the practical side, does this ever happen? Each linux system has ton of shared libraries in /usr/lib
.
What if the library is compiled by someone else other than me?
Then he can do whatever he wants. But you can communicate that your shared library follows some common ABI standard, if you really want to.
What if I use different compilers for the library and the test program?
Then be sure to read that compiler documentation to make sure it follows the common ABI that you need.
Read: How to Write Shared Libraries. Ulrich Drepper - section 3 seems to be related.
Upvotes: 1
Reputation: 6994
Bitfields are implementation defined and not not portable. But for most relevant platforms, their extraction/packing is well specified by the ABI so that they can be safely used in shared libraries.
E.g.:
Upvotes: 1
Reputation: 86671
Here is the relevant section of the C11 standard:
An implementation may allocate any addressable storage unit large enough to hold a bit- field. If enough space remains, a bit-field that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit. If insufficient space remains, whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is implementation-defined. The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined. The alignment of the addressable storage unit is unspecified.
§6.7.2.1 point 11.
This means that the compiler can use any suitable type it likes to hold the bitfields and that they will be adjacent to each and in the order they were defined.
However, the compiler can choose for itself whether to order them from high to low or from low to high. It can also choose to overlap bitfields if it run out of space or to allocate a new storage unit (not a problem in your case, you only have 8 bits).
With the above in mind, we can answer your question. You can only guarantee that your test program will give the right answer if the program and the library were compiled with the same compiler implementation. If you use two different compilers, even if they both use (say) unsigned char
to store the bit fields in, one could start from the top of the byte and the other could start from the bottom.
In practice, as ensc says above, a platform ABI might define a bit field packing and ordering standard, that makes it OK between compilers on the same platform, but this is not guaranteed in principle.
Upvotes: 2