📜 ⬆️ ⬇️

How to reduce the likelihood of errors at the stage of writing code. Note N2

Operator?:
This is the second article on how to avoid a number of errors at the stage of writing code. In the previous article, we mentioned the advice to avoid multiple calculations in one expression. However, this issue requires more attention. Consider the danger of difficult conditions, and how you can prevent many logical errors.



Introduction


The previous article can be found here . This time, examples of errors will be taken from various well-known projects to emphasize their prevalence. The errors that will be demonstrated were found by me with the help of the PVS-Studio analyzer over a relatively long period of time. Practically I wrote about everything to the developers and therefore it is possible to hope that they are corrected in new versions of the code. This is what I write in the introduction, because after the articles I always receive letters asking “write about the errors you found to the developers of the project”.
')

1. Do not use the ternary operation '?:' In compound expressions.


The ternary conditional operation is written in C / C ++ using the '?:' Operator. An operation that returns its second or third operand depending on the value of the logical expression specified by the first operand. Example:
  int minValue = A <B?  A: B; 

Ternary operation has a very low priority (see table). This is often forgotten, and it is because of this that the ternary operation is extremely dangerous.
Figure 1 - C / C ++ operations, in order of decreasing priority
Figure 1 - C / C ++ operations, in order of decreasing priority

Note that the '?:' Operation has a lower priority than addition, multiplication, the bitwise OR operator, and so on. Consider the code:

int Z = X + (A == B)? 12;

This code does not work as it may seem at first glance. The programmer most likely wanted to add the value of X with the number 1 or with the number 2, depending on the condition (A == B). But in fact, the condition is the expression "X + (A == B)". In fact, it says:

int Z = (X + (A == B))? 12;

I wanted to:

int Z = X + (A == B? 1: 2);

The first thought is that you need to know the priority of operations. And the programmers know them, but this ternary operation is very cunning! Not only beginners make mistakes with it, but also experienced programmers. It can often be found even in the highest quality code. Here are a couple of examples.
Newton game dynamics
V502 Perhaps the '?:' Operator was different. The '?:' Operator has a lower limit than the '*' operator. physics dgminkowskiconv.cpp 1061
  dgInt32 CalculateConvexShapeIntersection (...)
 {
   ...
   den = dgFloat32 (1.0e-24f) *
         (den> dgFloat32 (0.0f))? 
            dgFloat32 (1.0f): dgFloat32 (-1.0f);
   ...
 } 

Chromium
V502 Perhaps the '?:' Operator was different. The '?:' Operator has a lower limit than the '-' operator. views custom_frame_view.cc 400
  static const int kClientEdgeThickness;
 int height () const;
 bool ShouldShowClientEdge () const;
 void CustomFrameView :: PaintMaximizedFrameBorder (gfx :: Canvas * canvas) {
   ...
   int edge_height = titlebar_bottom-> height () -
                     ShouldShowClientEdge ()?  kClientEdgeThickness: 0;
   ...
 } 

IPP Sample
V502 Perhaps the '?:' Operator was different. The '?:' Operator has a lower than the '|' operator. vm vm_file_win.c 393
  #define FILE_ATTRIBUTE_NORMAL 0x00000080
 #define FILE_FLAG_NO_BUFFERING 0x20000000
 vm_file * vm_file_fopen (...)
 {
   ...
   mds [3] = FILE_ATTRIBUTE_NORMAL |
            (islog == 0)?  0: FILE_FLAG_NO_BUFFERING;
   ...
 } 

As you can see, errors of this type deserve attention. And it was not by chance that I highlighted their description in a separate paragraph. They are very, very common. Other examples can be cited, but they are all of the same type.

You can avoid such errors if you do not strive to put several operations in a single line of code. And if you put, then do not be greedy to put additional brackets. We will talk about brackets below. For now, let's just try to prevent potential errors when using '?:'.

Of course, the '?:' Operator is syntactic sugar and can almost always be replaced by if. Rare exceptions include such tasks as link initialization:
  MyObject & ref = X?  A: B; 

Of course, there are no problems with this, but creating a link to A or B without the '?:' Operator will be much more verbose:
  MyObject * tmpPtr;
 If (x)
   tmpPtr = & A;
 else
   tmpPtr = & B;
 MyObject & ref = * tmpPtr; 

So, to refuse the operator '?:' Is not rational. But it is easy to make a mistake with him. Therefore, I personally developed the following rule for myself. The result '?:' Should immediately be placed somewhere and not combined with other actions. That is, to the left of the condition of the operator '?:' There must be an assignment operation. Let's return to the original example:
  int Z = X + (A == B)?  12; 

I propose to write like this:
  int Z = X;
 Z + = A == B?  12; 

In the case of an example related to IPP Samples, I would write this:
  mds [3] = FILE_ATTRIBUTE_NORMAL;
 mds [3] | = (islog == 0)?  0: FILE_FLAG_NO_BUFFERING; 

You can disagree with the recommendation. I will not defend it. For example, I do not really like that instead of one line we get two or more. A good alternative would be to necessarily put the '?:' Operator in parentheses. I set the main task to show the error patterns. The choice of the error protection pattern depends on the preferences of the programmer.

2. Feel free to use brackets


For some reason, it so happened that the use of extra brackets when programming in C / C ++ is considered to be some kind of shameful action. Perhaps this comes from the fact that the question about the priority of operations is very much asked to ask at the interview. And a person subconsciously postpones the desire to always make the most of the mechanism of priorities. After all, if he puts extra brackets, then suddenly someone will think that he is a novice, and not a true Jedi.

I even met a discussion on the Internet where a person categorically argued that using extra brackets is a bad form. And if a person is not sure how the expression will be calculated, then he needs to learn, not to write programs. Unfortunately, I could not find this discussion, but I do not share such points of view. Of course, you need to know the priorities, but if heterogeneous operations are used in the expression, then it is better to secure yourself with brackets. This will not only protect against potential errors, but also make the code more readable for other developers.

Errors with the confusion of priorities also arise not only among beginners, but also among professionals. In this case, the expression does not have to be very confusing and long. The error can be made in relatively simple expressions. Consider some examples.
Wolfenstein
V564 The '&' operator is applied to bool type value. You have probably forgotten to include the operator. game g_client.c 1534
  #define SVF_CASTAI 0x00000010
 char * ClientConnect (...) {
   ...
   if (! ent-> r.svFlags & SVF_CASTAI) {
   ...
 } 

Dosbox
V564 The '&' operator is applied to bool type value. You have probably forgotten to include the operator. dosbox sdlmain.cpp 519
  static SDL_Surface * GFX_SetupSurfaceScaled (Bit32u sdl_flags, 
                                             Bit32u bpp) {
   ...
   if (! sdl.blit.surface || (! sdl.blit.surface-> flags & SDL_HWSURFACE)) {
   ...
 } 

Well, another example from Chromium:

V564 The '&' operator is applied to bool type value. You have probably forgotten to include the operator. base platform_file_win.cc 216
  #define FILE_ATTRIBUTE_DIRECTORY 0x00000010
 bool GetPlatformFileInfo (PlatformFile file, PlatformFileInfo * info) {
   ...
   info-> is_directory =
     file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY! = 0;
   ...
 } 

The expressions are simple. The developers are great. But errors still exist. So the use of brackets in slippery places will not be superfluous.

Probably worth doing so. If operations are simple and familiar, then additional brackets are not needed. Examples:
  if (A == B && X! = Y)
 if (A - B <Foo () * 2) 

But if more rare operators are used (~, ^, &, |, <<, >>,? :), then explicit brackets will not damage. This will make it easier to read the code and protect against potential errors. Examples:
  If (! (A & B))
 x = A |  B |  (z <1? 2: 3); 

Feel free to use brackets when using rare operations will also help for the "?:" Operator discussed earlier. What exactly is better to do with "?:" - a matter of taste. I prefer the simplified version.

Conclusion


Write simple and clear code. By breaking long and complex expressions into several lines, you get a longer code. But such code is much easier to understand and read. It is less likely to make a mistake. Do not be afraid to have an extra variable. The compiler perfectly optimizes everything.

Do not be greedy on brackets in expressions where rare operators are used or where bitwise and logical operations are mixed.

A programmer who in the future will read your code with brackets will only say thank you for that.

Source: https://habr.com/ru/post/116397/


All Articles