Inconsistent gcc behavior for __attribute ((const))

I came across very strange behavior in gcc regarding operators and functions marked with __attribute((const)) . Logical and arithmetic operators lead to various optimizations, and I don’t understand why.

Actually, this is not a mistake, since __attribute((const)) is just a hint, and there is no guarantee of its influence, but still it is very surprising. Anyone have any explanation?

Here is the code. Therefore, I define the __attribute((const)) function:

 int f(int & counter) __attribute((const)); int f(int & counter) { ++counter; return 0; } 

Then I define a statement validation macro. This is done using macros rather than templates / functors to present simple code to the compiler and simplify optimization:

 int global = 0; // forces results to be computed #define TestOp(OP) \ { \ int n = 0; \ global += (f(n) OP f(n)); \ std::cout << "op" #OP " calls f " << n << " times" << std::endl; \ } 

And finally, I test the different operators as follows. Comments correspond to the output of g++-4.8 -std=c++11 -O2 -Wall -pedantic the same output in -O3 and -Ofast

 int main() { // all calls optimized away TestOp(^) // 0 TestOp(-) // 0 // one call is optimized away TestOp(|) // 1 TestOp(&) // 1 TestOp(||) // 1 TestOp(&&) // 1 // no optimization TestOp(+) // 2 TestOp(*) // 2 return global; } 

My question is: why do arithmetic operators give two calls? Why can't f()+f() be optimized like 2*f() ? Is there any way to help / force this optimization? At first I thought that multiplication could be expensive, but I tried using f()+....+f() , and 10 additions are still not reduced to 10*f() . In addition, since arithmetic is int , the order of operations does not matter (unlike float s).

I also checked asm, but that does not help: all ints seem to be pre-computed at compile time.

+6
source share
1 answer

The compiler does not trust you. Since you have a reference argument, the compiler does not seem to trust your const attribute - the const function should only consider the values ​​passed through the arguments (not links or dereference pointers).

Another way to check this is to break the const function in a separate compilation unit:

test1.cpp:

 #include <stdio.h> int global = 0; // forces results to be computed int f(int i) __attribute((const)); void print_count(void); #define TestOp(OP) \ { \ int n = 0; \ global += (f(n) OP f(n)); \ printf("op %s ", #OP);\ print_count();\ } int main() { // all calls optimized away TestOp(^) // 0 TestOp(-) // 0 // one call is optimized away TestOp(|) // 1 TestOp(&) // 1 TestOp(||) // 1 TestOp(&&) // 1 // no optimization TestOp(+) // 2 TestOp(*) // 2 return global; } 

counter.cpp:

 #include <stdio.h> static int counter = 0; int f(int i) { ++counter; return 0; } void print_count(void) { printf("counter %d\n", counter); counter = 0; } 

Now the compiler finds out that there is no need to call f(0) to f(0) | f(0) f(0) | f(0) , and the result of this call to f(0) reused for other cases.

 $ g++ -O2 -c counter.cpp && g++ -O2 -c test.cpp && g++ counter.o test.o && ./a.out op ^ counter 0 op - counter 0 op | counter 1 op & counter 0 op || counter 0 op && counter 0 op + counter 0 op * counter 0 
+5
source

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


All Articles