📜 ⬆️ ⬇️

Two paradoxes in C programs

I want to talk about two oddities that I had to face while programming computer algorithms in C.

So, the first unexpected behavior for some programmers. Here is a little prog.

#include <stdio.h> int main() { unsigned char a = 1, b; b = ~a >> 1; printf("%u\n", b); return 0; } 

')
We analyze it. The bitwise operation ~ inverts the state of each bit of a byte a in which the unit is originally written. As a result, should receive 11111110b , that is, 254 . Shifting this byte to the right by one bit should get 127 . However, the code that gives, for example, the gcc compiler, displays the number 255 in the console?!

At first, I thought that the matter is in priority - what if the compiler has a priority of operations “to mow”? That is, it is as if a shift is first made, and then an inversion (and that is logical ...). So what's the deal?

After some deliberation, I came up with another hypothesis that by inversion, bytes are brought to the word (well, or to a double word), and then the inverted word shifts. That's where it turns out 255 - the high bits in the word zeros, inverting them, we have ones. Then, making the word shift to the right by one bit, there will be one in its low byte in all bits.

This is confirmed by the following code.

 #include <stdio.h> int main() { unsigned char a = 1, b; b = (unsigned char)~a >> 1; printf("%u\n", b); return 0; } 

Now we get the correct result. But I finally became convinced of this by disassembling the ELF file, which is provided by gcc . I will give a fragment of the received assembly code.

 mov [ebp+var_6], 1 movzx eax, [ebp+var_6] not eax sar eax, 1 mov [ebp+var_5], al 

First, through the stack, the unit enters the 32-bit eax register. Further it is inverted, and then moves. The result comes from the younger part of the register ax - register al . This justifies my hypothesis - the units that were beyond the desired byte, when a double word is shifted into it.

As it turned out, this situation is called Integer Promotion and is described in Section 6.3.1.1 of the C99 standard . You can download it from here www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf

The second unexpected behavior for some programmers is associated with real numbers. We have the following code.

 #include <stdio.h> int main() { float a = 1.005, b = 1000; int c = a*b; printf("%d\n", c); return 0; } 

Compiling it with gcc 4.1.1 , I get 1004 . Again the question is where does the strange result come from? Even this

 int c = (float)(a*b); 

also does not give the correct result.

Climbing on the C89 standard, it turned out that he did not regulate anything about how to work with real numbers. After all, when the SSE extension appeared, the compilers began to be considered in a mixed way - as one would think faster: something on the FPU , something on the SSE . In the new C99 standard, there is some certainty. The compiler should set the FLT_EVAL_METHOD macro (header file float.h ) to 0 , 1 , 2 for the way it counts. So, 0 - all count as written; 1 - float to actually count in double and then convert back to float ; 2 - count everything in long double , converting to float or double at the end of calculations, respectively.

Now, in order to make the prog count as it should, you need to collect it

 gcc proga.c -msse 

Only after that I got the number 1005 in the console. It turned out that my version of the gcc compiler does not support the FLT_EVAL_METHOD macro. By the way, with double gcc gives the code that outputs 1004 . Only Intel C 9.0 made normal code with double , but when I wrote

 int c = (float)(a*b); 

(here, a and b are of double type). Without a cast, the code gives 1004 there .

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


All Articles