I say that this is correct, based on my understanding of pure and const, but if someone has an exact definition of two, please speak. This becomes difficult because the GCC documentation does not indicate what exactly means that the function has “no effects other than the return value” (for pure) or “do not check the values except their arguments” (for the constant). Obviously, all functions have some effects (they use processor cycles, modify memory) and study some values (function code, constants).
“Side effects” should be defined in terms of the semantics of the C programming language, but we can guess what CVD people mean based on the purpose of these attributes, which should include additional optimizations (at least that I assume they are intended to )
Forgive me if some of the following are too simple ...
Pure functions may contribute to the elimination of common subexpression. Their feature is that they do not change the environment, so the compiler can call it less than once without changing the semantics of the program.
z = f(x); y = f(x);
becomes:
z = y = f(x);
Or completely excluded if z and y not used.
So, I think that the working definition of "pure" is "any function that can be called less than once without changing the semantics of the program." However, function calls cannot be moved, for example,
size_t l = strlen(str);
Const functions can be reordered because they are independent of the dynamic environment.
// Assuming x and y not aliased, sin can be moved anywhere *some_ptr = '\0'; double y = sin(x); *other_ptr = '\0';
So, I think that the working definition of "const" is "any function that can be called anywhere without changing the semantics of the program." However, there is a danger:
__attribute__((const)) double big_math_func(double x, double theta, double iota) { static double table[512]; static bool initted = false; if (!initted) { ... initted = true; } ... return result; }
Since it is const, the compiler can change its order ...
pthread_mutex_lock(&mutex); ... z = big_math_func(x, theta, iota); ... pthread_mutex_unlock(&mutex);
In this case, it can be called simultaneously from two processors, although it appears only inside the critical section of your code. The processor can then decide to postpone the changes to table after the change in initted has already passed, which is bad news. You can solve this with memory barriers or pthread_once .
I do not think that this error will ever appear on x86, and I do not think that it appears on many systems that do not have several physical processors (not cores). Thus, it will work perfectly for centuries, and then suddenly work on a computer with a dual POWER outlet.
Conclusion:. The advantage of these definitions is that they make it clear what changes the compiler is allowed to make in the presence of these attributes, which (I think) are somewhat vague in GCC Documents. The disadvantage is that it is not clear that these are the definitions used by the GCC team.
If you look at the Haskell language specification, for example, you will find a much more accurate definition of purity, since purity is so important for the Haskell language.
Edit: I was not able to force GCC or Clang to move a single __attribute__((const)) function call through another function call, but it is possible that something similar happens in the future. Remember when -fstrict-aliasing became a default and everyone suddenly got a lot of errors in their programs? It seems like it makes me wary.
It seems to me that when you mark the __attribute__((const)) function, you promise the compiler that the result of the function call is the same, regardless of when it is called during the execution of your program, if the parameters are the same.
However, I came up with a way to move the const function from the critical section, although the way I did it can be called a “trick” of some kind.
__attribute__((const)) extern int const_func(int x); int func(int x) { int y1, y2; y1 = const_func(x); pthread_mutex_lock(&mutex); y2 = const_func(x); pthread_mutex_unlock(&mutex); return y1 + y2; }
The compiler translates this into the following code (from the assembly):
int func(int x) { int y; y = const_func(x); pthread_mutex_lock(&mutex); pthread_mutex_unlock(&mutex); return y * 2; }
Note that this will not only happen with __attribute__((pure)) , the const attribute, and only the const attribute will cause this behavior.
As you can see, the call inside the critical section has disappeared. It seems rather arbitrary that the previous call was saved, and I would not like to argue that the compiler in some future version will not make another decision about which call to save or whether it can move the function call somewhere else completely.
Conclusion 2: Test carefully, because if you do not know what promises you are doing in the compiler, a future version of the compiler may surprise you.