C is a great language. When someone asks “what would you change about C?” it is not easy for me to think of something that I think it just plain got wrong. And while C++ is large and complicated, I generally feel that, for what it is trying to be it is pretty well done too.
I wanted to preface this entry with that, because the word “gripe” could be taken to mean that I am a C or C++ hater. Not the case; they are my favorite languages that I use regularly. But with the benefit of hindsight, I think it’s worth mentioning a few of their design choices here and there that make life difficult, where a genuinely better alternative exists. Which brings me to this entry.
C’s integer types are well-explained in the Wikipedia entry C data
types. The types
long long and
their unsigned equivalents are defined without specifying their exact
size, but instead according to a loose set of rules which, in my
experience, are rarely useful.
Until C99 there was no standard way of declaring integers of a
specified size (ie. a 16-bit signed integer). You could declare a
signed short; this is guaranteed to be at least 16 bits, but it
could be larger. The lack of fixed-width types led to every project
reinventing the same typedefs for these, over and over. You can have
from wxWidgets, or qint32
from Qt or gint32
from glib. Almost every C or C++ library would eventually find itself
defining these same typedefs. But thankfully in C99 we got stdint.h
which gives us fixed width types like
int32_t in the standard
library! Problem solved, no?
Well, not quite unfortunately. Since
int32_t is just a typedef,
there can be multiple primitive types that are 32 bits wide. For
example, in the ILP32 programming model, both
long are 32
bits. So it’s totally arbitrary which of these typedefs is in
And the really unfortunate part is that
long are still
distinct, incompatible types, which means that this program won’t
This code will fail to compile because
void mycallback(long) is not
void f(int) even though both
int are 32
bits! And in this case both were used through fixed-width typedefs,
so the code looks like it should work.
What would have been better is if the primitive types had been defined
in terms of the fixed-width types (
uint32_t, etc). Then,
if desired, the more loosely-defined types like
could be the typedefs. If things were defined this way, then you
would never run into this problem where two integer types are the same
size, yet are incompatible.
A possible idea for improving the status quo would be to make primitive types compatible if they are the same size. That would make it legal to convert between the two function pointer types, and would probably require basically no work in real-world compilers to enable.
However that doesn’t solve a similar problem in C++, which happens
when you partially specialize on
a_int32_t (for example) only to
find that your partial specialization doesn’t apply to
Fixing this is not as easy, because some users could have code that
depends on these two types having different specializations, even
though they are the same size.
In closing: if you invent a new language, please make the primitive integer types fixed-width. Your users will thank you.