Skip to content

95 Advanced The Hundred

The Function With No Return

the code that worked fine until someone turned the optimizer on

int sign(int x) {
    if (x > 0)
        return 1;
    if (x < 0)
        return -1;
}   // and zero? zero, obviously

int main() {
    volatile int input = 0;
    int x = input;
    std::cout << "x       = " << x << "\n";
    std::cout << "sign(x) = " << sign(x) << "\n";
}

Compile at -O0, then at -O2. What does each build print?

Answer

They disagree — and the debug build is the one that looks right. Typical output (GCC 11 on x86-64; Clang 18 at -O0 instead traps, dying with SIGILL before it can print the sign line):

$ g++ -std=c++17 -O0 main.cpp -o demo && ./demo
x       = 0
sign(x) = 0

$ g++ -std=c++17 -O2 main.cpp -o demo && ./demo
x       = 0
sign(x) = -1

Why

Falling off the end of a value-returning function other than main is undefined behavior: the standard promises nothing, least of all 0. (main is the exception: flowing off its end means return 0;, so this demo's main is legal.) So the compiler may assume that end is unreachable — that x is never zero. At -O0 it assumes nothing: the missing path runs ret, handing back whatever was in eax — here x. At -O2 the assumption runs backwards: if x can't be zero, x < 0 is just !(x > 0), so that test is deleted and sign becomes branchless 2 * (x > 0) - 1, calling zero negative.

The fix

Return on every path. -Wreturn-type is on by default in C++ and folded into -Wall; -Werror=return-type makes it error: control reaches end of non-void function.

int sign(int x) {
    if (x > 0)
        return 1;
    if (x < 0)
        return -1;
    return 0;   // the case you forgot
}

Takeaway: outside main, a missing return promises the end is unreachable, not 0.

Try it: g++ -std=c++17 -O0 main.cpp -o demo && ./demo, then again with -O2

Open in Compiler Explorer ↗ Quiz this entry