3

In the following code a function is declared/defined as int setYear(int year_h){year = year_h;} (instead of void setYear(...), leading to run-time crash in gcc 8 AND with the flag -O[X] only.

Specific questions:

  1. What have changed in gcc 8, as it worked in gcc 7 ?
  2. Which flags can I use (if any) to generate a compilation error (rather than a warning) in gcc8 ?

main.cpp:

#include <iostream>

using namespace std;

int year = 2000;
int setYear(int year_h){year = year_h;}

int main()
{
    cout << "Hello World!" << endl;
    setYear(2019);
    cout << "Year: " << year << endl;
    return 0;
}

Run-time crash with:

g++-8 -O2  -o main main.cpp
./main
Hello World!
Hello World!
Segmentation fault (core dumped)

Works with:

g++-7 -O2  -o main main.cpp

or

g++-8 -o main main.cpp

EDIT: The question Omitting return statement in C++ answers my second question, but not the first one (on the difference between gcc 7 and gcc 8).

Antonello
  • 6,092
  • 3
  • 31
  • 56
  • 2
    `-Werror=return-type` – Ruslan Sep 18 '19 at 12:13
  • 5
    As the old saying goes, undefined behaviour is undefined. – molbdnilo Sep 18 '19 at 12:13
  • The GCC-7 man page states it's also activated by `-Wall`, which is recommended to be **always** enabled. – Murphy Sep 18 '19 at 12:15
  • @Murphy you can enable `-Wall -Werror=return-type`. It's not a good idea to treat all warnings as errors (at least if your code may be compiled by users), thus the selective `-Werror=` option. – Ruslan Sep 18 '19 at 12:17
  • 1
    Possible duplicate of [Omitting return statement in C++](https://stackoverflow.com/questions/3402178/omitting-return-statement-in-c) – Daniel Langr Sep 18 '19 at 12:17
  • 1
    [Related](https://stackoverflow.com/questions/57842756/why-should-i-always-enable-compiler-warnings). – n. m. could be an AI Sep 18 '19 at 12:18
  • @molbdnilo I do see that that is undefined behavior, but I wonder how different compilers handle it. I could've sworn I read somewhere that functions that are declared to return a value but otherwise don't are technically still defined as long as the (would be) return value is always discarded (in gcc at least). I've seen this in production code (unfortunately) but it was on an old environment and compiler, and the relevant assembly was fully conforming. I wonder if gcc explicitly defines it somewhere. – jfh Sep 18 '19 at 14:33
  • 1
    @jfh "flowing off the end of a function other than `main` results in undefined behavior" [stmt.return]. The [list of gcc extensions](https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html) does not mention defining it. – molbdnilo Sep 18 '19 at 15:08
  • 2
    @jfh This wouldn't be undefined in C (Reaching the end of a function without returning and then not using the return value is not UB). [It's UB in C++](http://eel.is/c++draft/stmt.return#2.sentence-8) (probably because of destructors). – Artyer Sep 18 '19 at 16:30
  • 1
    @Artyer Ahh that's probably why it's all over that codebase, as it was ported from C. Many thanks – jfh Sep 18 '19 at 16:33

1 Answers1

4

Starting with GCC 8, your setYear function simply doesn't have the RET instruction when your source is compiled with -Og (at higher levels the function is inlined, making understanding what's going on harder), and the main where the function is called also lacks any continuation.

See for comparison the original code at Compiler Explorer:

<...>
setYear(int):
        mov     DWORD PTR year[rip], edi
.LC0:
        .string "Hello World!"
main:
<...>

And the code where return type int has been changed to void (link):

<...>
setYear(int):
        mov     DWORD PTR year[rip], edi
        ret
.LC0:
        .string "Hello World!"
.LC1:
        .string "Year: "
main:
<...>

This omission alone is enough for execution flow to bump into main (the .strings are declared in another section), executing it again instead of returning to the point of call. Apparenly, gcc doesn't consider it worth adding the RET instruction when there's no return statement in a function other than main returning non-void.

Of course, in many cases the problem is easily detectable by the compiler at the compilation stage. I'd recommend using -Werror=return-type option, which makes this an error, unconditionally (unlike -Werror in general). It's very rare when you'd want to avoid this option, and it's extremely useful.

Ruslan
  • 18,162
  • 8
  • 67
  • 136