compiler-construction common mistakes - Why is it impossible to build a compiler that can determine if a C++ function will change the value of a particular variable?

6 Answers

Imagine such compiler exists. Let's also assume that for convenience it provides a library function that returns 1 if the passed function modifies a given variable and 0 when the function doesn't. Then what should this program print?

int variable = 0;

void f() {
    if (modifies_variable(f, variable)) {
        /* do nothing */
    } else {
        /* modify variable */
        variable = 1;

int main(int argc, char **argv) {
    if (modifies_variable(f, variable)) {
        printf("Modifies variable\n");
    } else {
        printf("Does not modify variable\n");

    return 0;
pitfalls how does

I read this line in a book:

It is provably impossible to build a compiler that can actually determine whether or not a C++ function will change the value of a particular variable.

The paragraph was talking about why the compiler is conservative when checking for const-ness.

Why is it impossible to build such a compiler?

The compiler can always check if a variable is reassigned, a non-const function is being invoked on it, or if it is being passed in as a non-const parameter...

I think the key word in "whether or not a C++ function will change the value of a particular variable" is "will". It is certainly possible to build a compiler that checks whether or not a C++ function is allowed to change the value of a particular variable, you cannot say with certainty that the change is going to happen:

void maybe(int& val) {
    cout << "Should I change value? [Y/N] >";
    string reply;
    cin >> reply;
    if (reply == "Y") {
        val = 42;

It can be done and compilers are doing it all the time for some functions, this is for instance a trivial optimisation for simple inline accessors or many pure functions.

What is impossible is to know it in the general case.

Whenever there is a system call or a function call coming from another module, or a call to a potentially overriden method, anything could happen, included hostile takeover from some hacker's use of a to change an unrelated variable.

However you should use const, avoid globals, prefer references to pointers, avoid reusing variables for unrelated tasks, etc. that will makes the compiler's life easier when performing aggressive optimisations.

Really surprised that there isn't an answer that using the halting problem directly! There's a very straightforward reduction from this problem to the halting problem.

Imagine that the compiler could tell whether or not a function changed the value of a variable. Then it would certainly be able to tell whether the following function changes the value of y or not, assuming that the value of x can be tracked in all the calls throughout the rest of the program:

foo(int x){

Now, for any program we like, let's rewrite it as:

int y;
    int x;
    run the program normally

Notice that, if, and only if, our program changes the value of y, does it then terminate - foo() is the last thing it does before exiting. This means we've solved the halting problem!

What the above reduction shows us is that the problem of determining whether a variable's value changes is at least as hard as the halting problem. The halting problem is known to be incomputable, so this one must be also.

It is impossible in general to for the compiler to determine if the variable will be changed, as have been pointed out.

When checking const-ness, the question of interest seems to be if the variable can be changed by a function. Even this is hard in languages that support pointers. You can't control what other code does with a pointer, it could even be read from an external source (though unlikely). In languages that restrict access to memory, these types of guarantees can be possible and allows for more aggressive optimization than C++ does.

Even if a variable is declared const, doesn't mean some badly written code can overwrite it.

//   g++ -o foo

#include <iostream>
void const_func(const int&a, int* b)
   b[0] = 2;
   b[1] = 2;

int main() {
   int a = 1;
   int b = 3;

   std::cout << a << std::endl;
   std::cout << a << std::endl;