Jeff Attwood said recently in a StackOverflow podcast that if you can’t think of five things you hate about your favourite programming language, then you don’t know it well enough. I started writing C code in about 1988 and C++ in about 1992, so I think I can say I’m familiar with them. I know that C and C++ are different languages, but there’s enough overlap that I’m going to group them together. Here are five things I hate about C and C++.
- Lack of portability. Pure C or C++ code is generally portable, but we continually run into thing like “standard” libraries that aren’t standard. Libraries for things like file I/O and threading can be vastly different on different platforms so if your application has to run on multiple platforms, you have to write the same code several times in slightly different ways. There are functions that are defined in a different header file on one platform than another. Preprocessor macros that have a leading underscore on one platform and not on another. There are functions that exist on one platform that don’t exist – or work differently – on another. The C language has been around almost forty years, and we still have to have #defines in our code to cover stricmp on one platform and strcasecmp on another. We don’t use exceptions in our code because different compilers deal with them differently, and we just started using templates because all the compilers we use finally support them in a similar enough way that they’re usable. I suppose technically these are problems with the implementations rather than the language itself.
- Undetectable number errors. How many times have you done x-- on an unsigned type only to find that you “decremented” it from 0 to 4294967295, and everything went haywire? Doing this is completely legal and the only way to prevent it is to manually check for 0 before you decrement, and make sure you do it in a thread-safe way. PITA.
- Lack of memory checking. If you allocate fifty bytes and then access fifty-one of them, that’s totally fine. Accessing that fifty-first byte may work, giving you random data, or it may crash. Writing that byte may work, overwriting some other variable and creating a terribly hard-to-find bug, or it may crash. Or even worse: it may overwrite some unused piece of memory, thus having no effect, most of the time (i.e. during development and testing) but then crash or overwrite memory occasionally (i.e. in customer deployments).
- Braces aren’t required for if statements. (and while statements, and for statements) This is just asking for trouble. I’ve trained myself to see and fix things like this:
and some editors and IDEs will automatically re-indent, making the problem obvious, but you can still miss them sometimes. In my code, I almost always put braces anyway, except for the occasional thing like this:
- Named structures and typedefs are different. This has confused me for years. You can have a structure with a name, and also typedef it to another name, or you can typedef a structure without a name. For example, all of these are legal:
The second and third examples are exactly the same, though I remember having to go through a bunch of code and change typedefs of the third type to have a name after struct because the debugger (CodeWarrior, if I remember correctly) didn’t understand them unless the struct had a name.