C is a finicky language that takes some extra care to avoid shooting yourself in the foot. This is part I in a series covering some tips, best practices and common pitfalls when working with C.
This post focuses on how to construct constant and volatile pointers, and how to avoid a common pitfall when dealing with floating point constants.
Const and volatile pointers demystified:
Have you ever been confused by const pointers in C? A constant integer is easy, but when you want a pointer to a constant value, or a constant pointer to a mutable value things can get a bit more confusing.
There are just two things you need to know:
1. You can put the const
keyword on either side of the type it modifies without changing the meaning of the type. So
const uint32_t x = ...;
is equivalent to:
uint32_t const x = ...;
Which is annoying, but it’s C, what do you expect?
2. C types “read” from right to left.
so the type:
const uint32_t * const
reads as “a constant pointer to a uint32_t that is constant“, but that sounds awkward, so let’s switch where the const goes:
uint32_t const * const
which reads: “a constant pointer to a constant uint32_t.” This is the same type but it reads much better :)
So if you want a mutable pointer to a constant uint32_t:
uint32_t const * x = ...;
Reads as “pointer to constant uint32_t“
Here we can change the value of x, but not *x:
x = something; -> O.K.
*x = something; -> error!>
or constant pointer to a mutable uint32_t:
uint32_t * const x = ...;
Reads as “constant pointer to uint32_t”
Here we can’t change x, but we can change *x:
x = something; -> error!
*x = something; -> O.K.
or a constant pointer to a constant uint32_t:
uint32_t const * const x = ...;
Reads as “constant pointer to a constant uint32_t“
Here we can’t change x, or *x.
x = something; -> error!
*x = something; -> error!
Most of the syntactic rules for
const
apply to the volatile
keyword as well. So the type:
uint32_t const * volatile
reads as “volatile pointer to a constant (but non-volatile) uint32_t” and
uint32_t const volatile * volatile
reads as “volatile pointer to a volatile constant uint32_t“
Hopefully that should help clear things up :)
Avoid macros for floating point constants
In C, you should never use a macro to define a floating point constant. There is no advantage in doing so (over a plain constant) and it can cause unexpected behavior.
For example, consider the following snippet of code:
/* set default control setting */ float control_setting = THIRTY_PERCENT; if (THIRTY_PERCENT == control_setting) { printf("Control is still at the default setting.\n"); }
One would normally expect this to print “Control is still at the default setting.” Unfortunately this isn’t what happens.
The trouble here is that 0.3 is not fully representable in a binary. Decimal literals in C have type double
by default. When we store THIRTY_PERCENT (0.3) in control_setting
the double
approximation of 0.3 gets rounded down to fit in float
. When we do the comparison C’s implicit conversion rules cause control_setting
to get promoted to double
, but control_setting
already contains a less precise approximation of 0.3 and so the comparison fails.
One possible alternative is to append the appropriate type specifier to the end of the literal like so:
#define THIRTY_PERCENT 0.3f
However, it’s better to avoid the preprocessor whenever it’s reasonable to do so. In this case (and for most constants) I prefer to use static const
instead.
static const float THIRTY_PERCENT = 0.3;
Technically this doesn’t really solve the problem. You might actually want the type of THIRTY_PERCENT
to be a double, in which case you’d run into the same problem! It’s part of the inherent dangers when doing floating point comparisons, especially in a language that performs implicit type coercions, but at least now the type of THIRTY_PERCENT
is more obvious, which can help a lot when debugging.
Thanks for taking the time to explain in this well presented post. I am learning c/c++ as I go, and this will go in my file of examples that I refer to for help.
I’m glad it was useful! Also, if you’re doing C++, note that const has internal linkage by default, so the “static” keyword can be omitted.
[…] Why you really should put const on the right side of your uint32_tIn my last installment of this series, I mentioned how C types “read” from right to left, and that const/volatile types are […]