Those of you who’ve read my Pontifications over the years know that the things that annoy me are truly countless in number. But most of the things that annoy me do so because I simply cannot understand why they are the way they are.
Take, for one example, how arithmetic is performed in C. Now, I’m not annoyed by the way statements are formed, or even by the precedence order (which I readily admit to not knowing or understanding or even caring much about). No, I’m annoyed (and recently plagued with bugs) about the way the length of the result of an arithmetic operation is determined.
Take the following as an example of the kind of problem that I’m talking about:
ULONGLONG tableOffset; tableOffset = (l1Index * L1_TABLE_GRANULARITY) + (l2Index * L2_TABLE_GRANULARITY) + startingL3->StartingOffset;
This is real code from a project I’m working on. Now, let’s play a game: How many bits wide will the result be?
You don’t know, do you? DO you!?! Of course you don’t know. Because it’s C. And C, in its infinite stupidity, generates the result based on the number of bits in the operands of the arithmetic statement (on the right side of the equals sign) instead of the number of bits where the result is intended to be stored (on the left side of the equals sign). So, like in my case, if l1Index, l2Index, and StartingOffset are all ULONGs… the result will be 32-bits wide. Yes… obviously what I intended. I always want to multiple a 32-bit value by another 32-bit value and then add another 32-bit value, and get a 32-bit value as the result. Right. Clearly. Arrrgh.
I ask you: On what planet is this reasonable? Why would you ever want your arithmetic statements to work with way… ever… never mind have them work this way by default. Why should I have to remember this? Better yet, why should my not remembering this cause me to have to work all day Saturday? And even better than that, how can this be – like – the nineteenth time I’ve made the same stupid mistake?
Somehow, C wants me to believe the follow code is better:
tableOffset = ((ULONGLONG)l1Index * (ULONGLONG)L1_TABLE_GRANULARITY) + ((ULONGLONG)l2Index * (ULONGLONG)L2_TABLE_GRANULARITY) + (ULONGLONG)startingL3->StartingOffset;
Arrrgh. It’s not better. It’s harder to read. It’s more obscure. And it’s supremely annoying. And, no… don’t write to me to tell me that I only have to cast one of those operands. I know that. Well, I think I know that. In any case, I figure if I’m going to start casting stuff, I’m going all in. And don’t complain about how I parenthesize my arithmetic statements. I already mentioned precedence order. All those parens are the result of yet another lesson I learned to avoid working weekends.
I ran into the “how many bits is my result” problem in a Big Way a year or two back when I wrote my first program that dealt extensively with floating pointer numbers. Stop laughing! I write drivers for a living, not scientific or statistical analysis software. During this project, I quickly learn that casting everything to (double) was my friend. When in doubt, stick a (double) in front of it and test it again. In the end, the code worked pretty well. The customer was happy. We got paid.
So, I ask again: Why does C have to do this? Yes, I know it’s always been this way so we can’t change it now simply because I hate it. But why can’t it at least give a warning at level 4 that we’re doing something stupid? When I write:
NTSTATUS status; status == STATUS_SUCCESS;
The compiler is smart enough to warn me:
1>DumbAss.cpp(1017): error C4553: ‘==’: operator has no effect; did you intend ‘=’?
Just like in the case of my bug, the syntax is legal… it’s just obviously not what the programmer who wrote it intended.
OK. I feel better now. Think I’ll get a beer and finish coding up the IOCTL handlers for this driver. And maybe I’ll add a few more gratuitous casts just for good measure.