Generally, these things are best left to the compiler's optimizer.
But why do you need functions for such trivial tasks? A C programmer should not get shocked when they encounter basic stuff like this:
x |= 1<<n; // set bit
x &= ~(1<<n); // clear bit
x ^= 1<<n; // toggle bit
y = x & (1<<n); // read bit
There is no real reason to hide simple things like these behind functions. You won't make the code more readable, because you can always assume that the reader of your code knows C. It rather seems like pointless wrapper functions to hide away "scary" operators that the programmer isn't familiar with.
That being said, the introduction of the functions may cause a lot of overhead code. To turn your functions back into the core operations shown above, the optimizer would have to be quite good.
If you for some reason persists in using the functions, any attempt of manual optimization is going to be questionable practice. The use of inline
, register
and such keywords are likely superfluous. The compiler with optimizer enabled should be far more capable to make the decision when to inline and when to put things in registers than the programmer.
As usual, it doesn't make sense to manually optimize code, unless you know more about the given CPU than the person who wrote the compiler port for it. Most often this is not the case.
What you can harmlessly do as manual optimization, is to get rid of unsigned char (you shouldn't be using the native C types for this anyhow). Instead use the uint_fast8_t
type from stdint.h. Using this type means: "I would like to have an uint8_t
, but if the CPU prefers a larger type for alignment/performance reasons, it can use that instead".
EDIT
There are different ways to set a bit to either 1 or 0. For maximum readability, you would write this:
uint8_t val = either_1_or_0;
...
if(val == 1)
byte |= 1<<n;
else
byte &= ~(1<<n);
This does however include a branch. Let's assume we know that the branch is a known performance bottleneck on the given system, to justify the otherwise questionable practice of manual optimization. We could then set the bit to either 1 or 0 without a branch, in the following manner:
byte = (byte & ~(1<<n)) | (val<<n);
And this is where the code is turning a bit unreadable. Read the above as:
- Take the byte and preserve everything in it, except for the bit we want to set to 1 or 0.
- Clear this bit.
- Then set it to either 1 or 0.
Note that the whole right side sub-expression is pointless if val is zero. So on a "generic system" this code is possibly slower than the readable version. So before writing code like this, we would have to know that our CPU is very good at bit-flipping and not-so-good at branch prediction.