Four Tips For C++ Programmers |
Written by Andrey Karpov | |||
Monday, 27 June 2016 | |||
Page 2 of 2
Why incorrect code worksWith this example I would like to raise a different thought-provoking topic for discussion. Sometimes we see that totally incorrect code happens, against all odds, to work just fine! Now, for experienced programmers this really comes as no surprise (another story), but for those that have recently started learning C/C++, well, it might be a little baffling. The code contains an error that PVS-Studio analyzer diagnoses in the following way: V502 Perhaps the '?:' operator works in a different way than was expected. The '?:' operator has a lower priority than the '|' operator..
Explanation Here we need to call CheckMenuItem() with certain flags set; and, on first glance we see that if bShowAvatar is true, then we need to bitwise OR MF_BYCOMMAND with MF_CHECKED - and conversely, with MF_UNCHECKED if it's false. Simple! The programmers have chosen the very natural ternary operator to express this (the operator is a convenient short version of if-then-else): MF_BYCOMMAND | dat->bShowAvatar ? MF_CHECKED : MF_UNCHECKED The thing is that the priority of |operator is higher than of ?: operator. (see Operation priorities in C/C++). As a result, there are two errors at once. The first error is that the condition has changed. It is no longer - as one might read it - "dat->bShowAvatar", but "MF_BYCOMMAND | dat->bShowAvatar". The second error - only one flag gets chosen - either MF_CHECKED or MF_UNCHECKED. The flag MF_BYCOMMAND is lost. But despite these errors the code works correctly! Reason - sheer stroke of luck. The programmer was just lucky that the MF_BYCOMMAND flag is equal to 0x00000000L. As the MF_BYCOMMAND flag is equal to 0, then it doesn't affect the code in any way. Probably some experienced programmers have already gotten the idea, but I'll still give some comments for beginners. First let's have a look at a correct expression with additional parenthesis: MF_BYCOMMAND | (dat->bShowAvatar ? MF_CHECKED : MF_UNCHECKED) Replace macros with numeric values: 0x00000000L | (dat->bShowAvatar ? 0x00000008L : 0x00000000L) If one of the operator operands | is 0, then we can simplify the expression: dat->bShowAvatar ? 0x00000008L : 0x00000000L Now let's have a closer look at an incorrect code variant: MF_BYCOMMAND | dat->bShowAvatar ? MF_CHECKED : MF_UNCHECKED Replace macros with numeric values: 0x00000000L | dat->bShowAvatar ? 0x00000008L : 0x00000000L In the subexpression "0x00000000L | dat->bShowAvatar" one of the operator operands | is 0. Let's simplify the expression: dat->bShowAvatar ? 0x00000008L : 0x00000000L As a result we have the same expression, this is why the erroneous code works correctly; another programming miracle has occurred. Correct codeThere are various ways to correct the code. One of them is to add parentheses, another - to add an intermediate variable. A good old if operator could also be of help here:
I really don't insist on using this exact way to correct the code. It might be easier to read it, but it's slightly lengthy, so it's more a matter of preferences. RecommendationMy recommendation is simple - try to avoid complex expressions, especially with ternary operators. Also don't forget about parentheses. The ?: is very dangerous. Sometimes it just slips your mind that it has a very low priority and it's easy to write an incorrect expression. People tend to use it when they want to clog up a string, so try not to do that.
A good compiler and coding style aren't always enoughWe have already spoken about good styles of coding, but this time we'll have a look at an anti-example. It's not enough to write good code: there can be various errors and a good programming style isn't always a cure-all. The fragment is taken from PostgreSQL. The error is detected by the following PVS-Studio diagnostic: V575 The 'memcmp' function processes '0' elements. Inspect the third argument. Cppcheck analyzer can also detect such errors. It issues a warning: Invalid memcmp() argument nr 3. A non-boolean value is required.
ExplanationA closing parenthesis is put in a wrong place. It's just a typo, but unfortunately it completely alters the meaning of the code. The sizeof(zero_clientaddr) == 0 expression always evaluates to 'false' as the size of any object is always larger than 0. The false value turns to 0, which results in the memcmp() function comparing 0 bytes. Having done so, the function assumes that the arrays are equal and returns 0. It means that the condition in this code sample can be reduced to if (false). Correct code
RecommendationI can't suggest any safe coding technique to avoid typos in a case like this. The only thing I can think of is "Yoda conditions", when constants are written to the left of the comparison operator:
But I won't recommend this style. I don't like and don't use it for two reasons: First, it makes conditions less readable. I don't know how to put it exactly, but it's not without reason that this style is called after Yoda. Second, they don't help anyway if we are dealing with parentheses put in a wrong place. There are lots of ways you can make a mistake. Here's an example of code where using the Yoda conditions didn't prevent the incorrect arrangement of parentheses:
This fragment is taken from the ReactOS project. The error is difficult to notice, so let me point it out for you: sizeof(UnknownError[0] - 20). So Yoda conditions are useless here. We could invent some artificial style to ensure that every closing parenthesis stands under the opening one. But it will make the code too bulky and ugly, and no one will be willing to write it that way. So, again, there is no coding style I could recommend to avoid writing closing parentheses in wrong places. And here's where the compiler should come in handy and warn us about such a strange construct, shouldn't it? Well, it should but it doesn't. I run Visual Studio 2015, specify the /Wall switch... and don't get any warning. But we can't blame the compiler for that, it has enough work to do as it is. The most important conclusion for us to draw here is that good coding style and a compiler (and I do like the compiler in VS2015) do not always suffice. I sometimes hear statements like, "You only need to set the compiler warnings at the highest level and use good style, and everything's going to be OK" No, it's not like that. I don't mean to say some programmers are bad at coding; it's just that every programmer makes mistakes. Everyone, no exceptions. Many of your typos are going to sneak past the compiler and good coding style. So the combo of good style + compiler warnings is important, but not sufficient. That's why we need to use a variety of bug search methods. There's no silver bullet; the high quality of code can be only achieved through a combination of several techniques. The error we are discussing here can be found by means of the following methods:
I suppose you have already guessed that I am personally interested in the static code analysis methodology most of all. By the way, it is most appropriate for solving this particular issue because it can detect errors at the earliest stage, i.e. right after the code has been written. Indeed, this error can be easily found by such tools as Cppcheck or PVS-Studio. ConclusionSome people don't get it that having skill isn't enough to avoid mistakes. Everyone makes them - it's inevitable. Even a super-guru make silly typos every now and then. And since it's inevitable, it doesn't make sense blaming programmers, bad compilers, or bad style. It's just not going to help. Instead, we should use a combination of various software quality improving techniques.
For the rest of the tips see The Ultimate Question of Programming, Refactoring, and Everything
Andrey Karpov is a co-founder and CTO of Program Verification Systems, a company whose main activity is the development of the static code analysis tool, PVS-Studio. Having been a Microsoft MVP in Visual C++ for five years he has won several awards.
Also by Andrey Karpov on IProgrammer:Test Your C++ Static Analysis Skills Finding Bugs In The First C++ Compiler - What does Bjarne Think! Related ArticlesTo be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to our RSS feed and follow us on, Twitter, Facebook, Google+ or Linkedin.
Comments
or email your comment to: comments@i-programmer.info
|
|||
Last Updated ( Monday, 27 June 2016 ) |