I am just starting to get my feet wet with C; and am rather enjoying myself in doing so. This is sort of a follow up to my last question here. I am posting this ordinate to hopefully getting some feedback on the logic itself, and possibly also some feedback on coding style. I do, however insist on making everything explicit. I'm not a huge fan of implicit anything... Maybe that's something I need to get over?
#include <stdio.h>
typedef enum {
false = (int) 0, true = (int) 1
} bool;
inline void set_bit(register unsigned int *x,
register const unsigned short int offset, register const bool value) {
if (value)
*x |= (1 << offset);
else
*x &= ~(1 << offset);
}
inline bool get_bit(register const unsigned int x,
register const unsigned short int offset) {
return (x & (1 << offset)) != 0;
}
int main(void) {
unsigned int x = 0;
int count;
for (count = 0; count < 8; count += 2)
set_bit(&x, count, true);
for (count = 7; count >= 0; count--)
printf("%d ", (int) get_bit(x, count));
printf("\n");
return 0;
}
5 Answers 5
I do, however insist on making everything explicit. I'm not a huge fan of implicit anything... Maybe that's something I need to get over?
No, that is very good practice and a good habit. Particularly, there are many dangerous, implicit type promotions going on in C, that explicit type casts can prevent.
However, if you want to be explicit, you must actually have quite a deep understanding of what's going on between the C lines. Until you do, you will end up causing more problems than you solve with this coding practice. Also, you do a couple of pointless things that only clutter down the code.
Here are my comments on your code:
- Do not define your own bool type. Instead use the standard
bool
type in stdint.h. (int) 0
This cast is pointless and adds nothing of value. Integer literals are always of the typeint
. Enumeration constants are also always of the typeint
.register
is an obsolete keyword from the dark ages, when compilers were worthless at optimizing code. Today, a modern compiler does a much better job at optimizing the code than you do, so let it do its job. Also, theregister
keyword causes various side-effects: for example you can't take the address of a register variable. So never useregister
, because there is never a reason to do so. That keyword just remains in the language for backwards compatibility.- Similarly, it is often best to let the compiler handle inlining in most cases. Because while inlining makes the code faster, it also makes the executable much larger. The compiler is likely better than you at determining when to optimize for size and when to optimize for speed.
- Always use
{}
after each statement, even if there is only one line of code following it. Skipping{}
is dangerous practice that almost certainly leads to bugs sooner or later. You want to be explicit but miss a few cases where it actually matters.
1 << offset
means that you do left shift on a signed integer. Assuming 32 bit integers, the case of1 << 31
leads to undefined behavior. Generally, in 99.9% of the cases it doesn't make any sense whatsoever to use bitwise operators on signed integers. If you wish to write safe code, be explicit and type1u << offset
.Because of the above,
*x |= (1 << offset);
implicitly stores anint
into anunsigned int
, which is potentially dangerous in some cases.And also because of the above, you apply
~
to a signed int, which is always dangerous practice.You have no function declarations, only definitions. You also don't declare the local functions as
static
, which is proper practice for functions that are private to the specific module.
As others have commented, naming a bool "value" and using it to store values is confusing.
uint8_t
would have been a more suitable type to use in this case.In general, it is always better to use the types from stdint.h when you do bit manipulations.
-
\$\begingroup\$ "Always use {} after each statement" --> "Always use {} after each part of a branching statement"? \$\endgroup\$Martin F– Martin F2014年03月29日 00:56:14 +00:00Commented Mar 29, 2014 at 0:56
-
\$\begingroup\$ Also, i don't understand others have commented, naming a bool "value" and using it to store values is confusing. uint8_t would have been a more suitable. I understood Jamal's advice, but not your's. \$\endgroup\$Martin F– Martin F2014年03月29日 01:25:41 +00:00Commented Mar 29, 2014 at 1:25
-
1\$\begingroup\$ "Integer literals are always of the type int." I think depends on range. When
int
is 32 bits, 5000000000 is likely along long
and not anint
. \$\endgroup\$chux– chux2014年03月29日 05:34:07 +00:00Commented Mar 29, 2014 at 5:34 -
\$\begingroup\$ @martinf Regarding {}: after every if, else, else if, for, while, do-while and switch statement. Regarding the bool value: bit-wise arithmetic is usually done with integers. If you want to tell a function to set a bit to 1 or 0, it makes more sense to pass a 1 or 0 than true/false. \$\endgroup\$Lundin– Lundin2014年03月31日 06:29:02 +00:00Commented Mar 31, 2014 at 6:29
-
1\$\begingroup\$ @chux Indeed. They can even be of unsigned type in case of hex or octal literals. \$\endgroup\$Lundin– Lundin2014年03月31日 06:42:32 +00:00Commented Mar 31, 2014 at 6:42
I think set_bit
might be better as:
unsigned set_bit(unsigned x, unsigned offset, bool value)
{
return (value)
? x | (1 << offset)
: x & ~(1 << offset);
}
I'm not sure why I think so; my reasons are:
- It's closer to the macros I used to see for doing this kind of thing
- Pointers can be abused (e.g. you could pass a null pointer into your version)
- The compiler might find it easier to optimize (e.g. because taking the address of a local variable and passing it to a subroutine might make the compiler worry about aliasing).
You might get better performance by defining separate functions named set_bit
and clear_bit
(OTOH your compiler might optimize away the difference when you pass a hard-coded value
to your set_bit
).
I don't think that short
adds any value to the offset
parameter; shorts are often less efficient performance-wise; and even a short value is too long for your needs (you probably want offset
to have a value from 0 through 31 or 63).
Drop the inline
and register
keywords. The compiler will decide what to do better than you can, despite your personal preference for complete mastery over the machine.
Keep in mind that the default compiler optimization setting is set at -O0
. For this answer to work properly, you should also set your minimum compiler optimization level to -O3
(if you haven't already).
value
is not a useful name for a bool
variable; it sounds more like it's holding a number rather than a condition. Do you mean for it to be something like isBitValue
?
If by explicit, you mean defining your own bool
type, then that may be okay for a toy program in C. Otherwise, you should use <stdbool.h>
.
-
1\$\begingroup\$ Maybe
bitValue
. \$\endgroup\$ChrisW– ChrisW2014年03月28日 01:10:15 +00:00Commented Mar 28, 2014 at 1:10
Here is a branchless implementation of the set_bit
function:
return x & ~(1 << offset) | (value << offset);
-
\$\begingroup\$ You have a missing parenthesis. Should be
(x & ~(1 << offset))
\$\endgroup\$Mr.Mindor– Mr.Mindor2014年03月28日 17:08:38 +00:00Commented Mar 28, 2014 at 17:08 -
\$\begingroup\$ Fixed, thanks. As
&
has precedence over|
, parenthesis are optional. \$\endgroup\$Yves Daoust– Yves Daoust2014年03月28日 17:28:41 +00:00Commented Mar 28, 2014 at 17:28
bool
datatype.#include <stdbool.h>
\$\endgroup\$