Home > Enterprise >  Why this program will never terminate with flag `-O3`?
Why this program will never terminate with flag `-O3`?

Time:02-05

The program below has different behaviors with different option levels. When I compile it with -O3, it will never terminate. when I compile it with -O0, it will always terminate very soon.

#include<stdio.h>

#include<pthread.h>


void *f(void *v) {
    int *i = (int *) v;
    *i = 0;
    printf("set to 0!\n");
    return NULL;
}

int main() {
    const int c = 1;
    int i = 0;
    pthread_t thread;
    void *ptr = (void *) &c;
    while (c) {
        i  ;
        if (i == 1000) {
            pthread_create(&thread, NULL, &f, ptr);
        }
    }
    printf("done\n");
}

This is the result of running it with different optimization level flags.

username@hostname:/src$  gcc -O0 main.c -o main
username@hostname:/src$  ./main 
done
set to 0!
set to 0!
username@hostname:/src$  gcc -O3 main.c -o main
username@hostname:/src$  ./main 
set to 0!
set to 0!
set to 0!
set to 0!
set to 0!
set to 0!
^C
username@hostname:/src$ 

The answer given by the professor's slide is like this:

  • Will it always terminate?

*Depends of gcc options

  • With –o3 (all optimisations): no

Why?

  • The variable c is likely to stay local in a register, hence it will not be shared.

Solution « volatile »

CodePudding user response:

The professor's explanation is not quite right.

The initial value of c is 1, which is truthy. It's declared as a constant, so its value can't change. Thus, the condition in while (c) is guaranteed to always be true, so there's no need to test the variable at all when the program is running. Just generate code for an infinite loop.

This optimization of not reading the variable is not done when optimization is disabled. In practice, declaring the variable volatile also forces it to be read whenever the variable is referenced in code.

Note that optimizations are implementation-dependent. Assigning to a const variable by accessing it through a non-const pointer results in undefined behavior, so any result is possible.

The typical use of a const volatile variable is for variables that reference read-only hardware registers that can be changed asynchronously (e.g. I/O ports on microcontrollers). This allows the application to read the register but code that tries to assign to the variable will not compile.

CodePudding user response:

This is undefined behavior. Per 6.7.3 Type qualifiers, paragraph 6 of the (draft) C11 standard:

If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined.

There's no requirement for any particular behavior on the program. How it behaves is literally outside the specifications of the C language.

Your professor's observation of how it behaves may be correct. But he goes off the rails. There is no "why" for undefined behavior. What happens can change with changes to compiler options, particulars of the source code, time of day, or phase of the moon. Anything. Any expectation for any particular behavior is unfounded.

And

Solution « volatile »

is flat-out WRONG.

volatile does not provide sufficient guarantees for multithreaded access. See Why is volatile not considered useful in multithreaded C or C programming?.

volatile can appear to "work" because of particulars of the system, or just because any race conditions just don't happen to be triggered in an observable manner, but that doesn't make it correct. It doesn't "work" - you just didn't observe any failure. "I didn't see it break" does not mean "it works".

Note that some C implementations do define volatile much more extensively than the C standard requires. Microsoft in particular defines volatile much more expansively, making volatile much more effective and even useful and correct in multithreaded programs.

But that does not apply to all C implementations. And if you read that link, you'll find it doesn't even apply to Microsoft-compiled code running on ARM hardware...

CodePudding user response:

The explanation of "The variable c is likely to stay local in a register, hence it will not be shared." is not quite right. Or I'm having trouble parsing its precise meaning.

Once you take a pointer to it, the compiler has to put it into memory, unless it can convince itself that the pointer will not be used.

Here https://godbolt.org/z/YavbYxqoE

mov     DWORD PTR [rsp 4], 1

and

lea     rcx, [rsp 4]

suggest to me that the compiler has put the variable on the stack.

It's just that the while loop is not checking it for changes due to it being advertised as const.

  • Related