I have some binary data that contains structures that are defined as follows:
s1:
a - 1B
b - 4B
c - 2B
d - 8B
s2:
a - 1B
b - 3B
c - 2B
d - 6B
It uses Big Endian byte order.
I parse s1
with the following code:
#include <endian.h>
#include <stdint.h>
#include <string.h>
struct [[gnu::packed]] s1 {
uint8_t a;
uint32_t b;
uint16_t c;
uint64_t d;
};
void parse_s1(struct s1 *parsed, const unsigned char buf[static restrict sizeof(*parsed)])
{
memcpy(parsed, buf, sizeof(*parsed));
parsed->b = be32toh(parsed->b);
parsed->c = be16toh(parsed->c);
parsed->d = be64toh(parsed->d);
}
Which AFAIK, is defined behavior.
And for the second structure, I'm considering the following code, but am not sure if I may be hitting Undefined Behavior at some point (I was lucky that the fields are byte-aligned (i.e., no 3-bit field) and that the non-power-of-two byte fields are not consecutive, but a complete answer might want to consider what would happen if two non-power-of-two byte integers are consecutive (i.e., if d
went before c
)):
#include <endian.h>
#include <stdint.h>
#include <string.h>
struct [[gnu::packed]] s2 {
uint8_t a;
uintmax_t b : 24;
uint16_t c;
uintmax_t d : 48;
};
void parse_s2(struct s2 *parsed, const unsigned char buf[static restrict sizeof(*parsed)])
{
memcpy(parsed, buf, sizeof(*parsed));
parsed->b = be32toh(parsed->b) >> 8;
parsed->c = be16toh(parsed->c);
parsed->d = be64toh(parsed->d) >> 16;
}
I also assume this code is C++-compatible (or more specifically gnu++-compatible) (except for the actual prototype of the function, which in C++ would use __restrict__
and a pointer instead of a VLA).
Is the code above correct in both C and C++ (GNU dialects)? Or does it rely on Undefined Behavior?
EDIT:
The following is an answer to @Anaconda's comment, as this doesn't format well in a comment:
//bitfields.c
#include <stdint.h>
#include <stdio.h>
struct [[gnu::packed]] s2 {
uint8_t a;
uintmax_t b : 24;
uint16_t c;
uintmax_t d : 48;
};
int main(void)
{
printf("%zu\n", sizeof(struct s2));
return 0;
}
$ cc -Wall -Wextra -pedantic -std=c2x bitfields.c
$ ./a.out
12
It seems (experimentally) that GCC understands [[gnu::packed]]
so that it compresses bitfields as much as it can (at least byte-wise; I don't deal with integer widths that aren't multiple of 8, so I'm fine with this).
I'm not sure if there's any UB here, but I guess I'd have to be really unlucky, and GCC really evil, to break the code above.