Im not that good at C, so go easy on me, but I wanted to be able to have individual bit access in C without using a ton of functions to do bit manipulation on a uint8_t
. Tell me where I could improve upon it
#include <stdio.h>
#pragma pack(1)
union bit_array {
struct {
unsigned char b8:1, b7:1, b6:1, b5:1, b4:1, b3:1, b2:1, b1:1;
} bits;
unsigned char value;
};
int main() {
// Creates char with binary value 11111111
union bit_array test = { 1, 1, 1, 1, 1, 1, 1, 1 };
// Displays 11111111 (255)
printf("%u\n", test.value);
// Sets 8th bit of binary value to 0
// 11111111 (255) -> 11111110 (254)
test.bits.b8 = 0;
// Displays 11111110 (254)
printf("%u\n", test.value);
return 0;
}
2 Answers 2
This code is not portable. The order of bit-fields within a word is completely compiler-dependent, so the test that appears to work on one platform may give completely different results on another.
You have avoided a common trap of using signed 1-bit fields (which can hold values 0
and -1
) - these unsigned ones are much better.
I don't think there's any need for the bits
member to be named - I would use an anonymous member there.
The numbering of bits is unconventional - most programmers would expect b0
to be the least-significant bit, and b7
the most significant (corresponding to 20 and 27 value of those bits in integers).
The test would be better if it were self-checking - return with non-zero status if any of the expectations are not met. For example:
int main(void)
{
int failures = 0;
{
/* First test */
union bit_array test = { {1, 1, 1, 1, 1, 1, 1, 1} };
if (test.value != 0xFF) {
fprintf(stderr, "Test 1 expected 0xff, got %#04hhx\n", test.value);
++failures;
}
}
{
/* Second test */
union bit_array test = { {1, 1, 1, 1, 1, 1, 1, 1} };
test.bits.b8 = 0;
if (test.value != 0xFE) {
fprintf(stderr, "Test 2 expected 0xfe, got %#04hhx\n", test.value);
++failures;
}
}
return failures != 0;
}
We need tests of the most-significant bit, and setting as well as resetting bits. I'll leave that as an exercise.
-
\$\begingroup\$ If the order of bit fields is dependant on system endianness, can I use a preprossecor directive to determine what order the struct's members are declared in? \$\endgroup\$QuestionLimitGoBrrrrr– QuestionLimitGoBrrrrr2021年04月19日 18:07:24 +00:00Commented Apr 19, 2021 at 18:07
-
\$\begingroup\$ It's not dependent on endianness; that's only tangentially related. I don't believe there's a reliable compile-time test - see this relevant Stack Overflow answer. \$\endgroup\$Toby Speight– Toby Speight2021年04月19日 19:38:57 +00:00Commented Apr 19, 2021 at 19:38
-
\$\begingroup\$ I thought that the bit field ordering was dependent on the endianness of the platform, and it was limited to high-order to low-order or low-order to high-order \$\endgroup\$QuestionLimitGoBrrrrr– QuestionLimitGoBrrrrr2021年04月19日 20:38:56 +00:00Commented Apr 19, 2021 at 20:38
-
1\$\begingroup\$ @QuestionLimitGoBrrrrr The bit field ordering is not specified to match the byte endianness. \$\endgroup\$chux– chux2021年04月20日 01:26:15 +00:00Commented Apr 20, 2021 at 1:26
-
1\$\begingroup\$ 4 then does make sense in
%#04hhx
. I find0x%02X
to deemphasize the prefix as lower case, but upper case digits clearer to read output and ditch the pesky#
with its 0 exemption. I expect%#04hhx\n", 0
to print0000
. \$\endgroup\$chux– chux2021年04月20日 07:40:07 +00:00Commented Apr 20, 2021 at 7:40
I wanted to be able to have individual bit access in C without using a ton of functions to do bit manipulation on a uint8_t
All things with bit-fields invite implementation defined behaviors. Even using unsigned char
is implementation defined.
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. C17dr § 6.7.2.1 5
Tell me where I could improve upon it
It really is not that hard to form portable set/get bit functions.
Only 2 functions are needed: BitGet()
and BitSet()
Sample unchecked code below uses unsigned
as uint8_t
itself is optional. Narrow uint8_t
is not really needed to make generic set/get
as much code will promote to unsigned/int
anyway.
#define UNSIGNED_BIT_WIDTH (CHAR_BIT * sizeof(unsigned))
unsigned BitGet(unsigned x, unsigned index) {
index %= UNSIGNED_BIT_WIDTH; // Likely just an 'and' operation
return (x >> index) & 1;
}
void BitSet(unsigned x, unsigned index, bool value) {
index %= UNSIGNED_BIT_WIDTH;
if (value) {
x |= 1u << index;
} else {
x &= ~(1u << index);
}
}
Other alternatives include macros, inline
, etc.
without using a ton of functions
I estimate the weight of the above function, using 0.2 GB/g, at about 6 pico-grams. 😉
-
\$\begingroup\$ 5.9847 pico-grams... \$\endgroup\$David C. Rankin– David C. Rankin2021年04月20日 03:59:49 +00:00Commented Apr 20, 2021 at 3:59
-
\$\begingroup\$ @DavidC.Rankin What's a few femto-grams between friends? \$\endgroup\$chux– chux2021年04月20日 07:27:37 +00:00Commented Apr 20, 2021 at 7:27
main
function should demonstrate exactly that, but in the code you posted, it doesn't. Without this information, we cannot tell you how to write really good code for this task. \$\endgroup\${a, b, c, d, e, f, g, h}
givinghgfedcba
(if I read your code right) is also less evident. You set b8 but the bit to the right is set. At least if 11111110 means 254. 4.uint8_t
is better thanunsigned char
. \$\endgroup\$