
C, C++, x86/x64 assembly: The case of forgotten return - ingve
https://yurichev.com/blog/no_return/
======
wmu
"-Wall -Wextra -Werror" are you allies :)

~~~
mort96
Please, no, don't use -Werror with -Wall or -Wextra. When you write your code
and compile with -Werror on your old version of GCC (either because you're
behind on GCC releases, or because you wrote the code in the past), and I want
to compile it with my new version which has added some warnings, I don't want
to have to mess around in your build configuration and remove -Werror.

Instead, do any of the following:

* Compile normally without -Werror, but have a test suite which compiles all your tests with -Werror. Make sure to only accept patches which don't break the test suite (probably using some CI system). If you don't want actual tests, a "test suite" which just makes sure `make clean && make CFLAGS=-Werror` doesn't fail would suffice.

* Use -Werror for regular builds, but enable only a fixed set of warnings, not -Wall or -Werror.

* Make your build system record warnings, and complain about them for every single build, not just the builds which happen to recompile the files which trigger warnings. This doesn't _guarantee_ that your project is warning-free like the other two do, but I imagine a lot of the reason projects end up with warnings is that people just see the warning once, and then never again until the file has to be recompiled.

I can't count the number of times I have wanted to use some project, but then
had to dig through the project's Makefile or autotools m4/shell soup or gn
files or meson files or cmake files or ad-hoc compile script or some other
build configuration, just because my compiler is newer than the author's.

~~~
craftyguy
> Please, no, don't use ... when distributing your code

FTFY. It's fine to use them for development, but a major pain in the ass if
you are distributing your code for others to compile (looking at you, google).

~~~
ctrl_freak
I actually don't even like -Werror for development. When I'm
developing/testing/debugging, I frequently want to run partially implemented
functions (a good example of this is repeatedly commenting out blocks of code
to bisect a problem). Trying to deal with warnings about unused
variables/parameters is extremely cumbersome.

~~~
MaulingMonkey
I want to opt out of bad warnings-as-errors, rather than failing to opt into
good ones. -Wall -Wextra -pedantic -Werror for me - followed by a bunch of
-Wno-xyz like -Wno-unused-variable.

You could also use -Wno-error=unused-variable, but I find warnings as warnings
almost completely pointless outside of extremely niche circumstances. I _will_
lose them in a sea of other errors when a template goes awry, and after fixing
that and building again the warning will disappear because the TU with said
warning didn't need rebuilding.

------
marssaxman
It's a little bit awkward to watch a new generation rediscover things which
used to be common knowledge. It's a good thing, though; this sort of thing
shouldn't be as common a practice as it once was.

------
akkartik
My mind is blown that g++ (7.3.0) doesn't warn on this.

~~~
gumby
That is the correct behavior for c.

But you mentioned g++: not sure when this became invalid in c++ but for me g++
-std=17 warns by default (though we compile everything with maximum possible
warnings already)

~~~
kazinator
From an ISO C conformance viewpoint, it is neither "correct" nor "incorrect"
to fail to generate a diagnostic that is not required by ISO C.

In the ISO C++ language, I vaguely seem to recall ("I could have sworn that")
that failing to return a value from a function is a diagnosable semantic rule
violation. And that it has been for a very long time.

So I fully sympathize with the "mind is blown" comment in grandparent.

Hey, g++ -pedantic catches some important semicolon trespasses, though:

    
    
      noreturn.cc:19:2: warning: extra ‘;’ [-pedantic]
      noreturn.cc:25:2: warning: extra ‘;’ [-pedantic]
    

:)

------
teddyh
> _Non-optimizing GCC 5.4 silently compiles this with no warnings_

GCC 5.4 is more than two years old; the current version is 8.2¹.

1\.
[https://www.gnu.org/software/gcc/releases.html](https://www.gnu.org/software/gcc/releases.html)

~~~
pard68
But when you are an enterprise too afraid to upgrade your version of RHEL then
GCC 5.4 <= may be what you have.

I know a company that paid a considerable amount of money to am outside team
to get the current release of GCC running on their no longer supported version
of RHEL.

------
Iv
I am intrigued as to why the compiler feels it is allowed to simplify a
function returning nothing as a no-op. After all this function allocates
memory and stores things in there. This is a side effect that the optimization
suppresses.

~~~
jchw
The C attitude is simple. If your code invokes undefined behavior, the
compiler is allowed to do whatever it wants. Not returning a value from a
function that is not void is definitely a footgun.

The C language itself does not prevent you from invoking these footguns. Why,
I don't know for sure; I wasn't around for the genesis of C. I would guess
that if C were designed today, most of the undefined behavior that can be
detected statically would just be errors.

Of course, compilers are allowed to optimize assuming your code does not
utilize undefined behavior. This is reasonable in my opinion. If the compiler
were _not_ allowed to optimize in ways that broke undefined behavior, it would
be nearly impossible to write meaningful compiler optimizations. Most compiler
optimizations change the semantics of the execution even though they keep the
semantics of the program itself identical according to the rules of the C
language. Where would you draw the line? If I monkey patch a function that my
compiler decided to inline, would I have reason to complain?

In the face of C seeing your completely unused side-effects, it knows there is
no possible way your program could ever actually touch those side-effects in
the bounds of defined behavior. Therefore, it is free to throw them away. This
may seem unreasonable, but in my opinion it's nearly the only reasonable
choice.

To me the real solution is something like -Wall -Werror. For most statically
detectable invocations of undefined behavior, this will save you, throwing an
error in this case because "not all codepaths return a value." Then, you can
sleep easy without sacrificing decent dead-code elimination.

~~~
Iv
That actually made sense.

I just wanted to point out that for me, C is more for embedded code, where
modifying random parts of memory is a common task. So saying that a function
that filled a pointer, used it to reset or set some parts of memory can be
removed just because it does not have a return seems a bit extreme, but I
agree that it is allowed.

I often see people pushing for -Wall and -Werror. In most professional
projects I have been in, though, we have to use black boxes (or, currently,
non-standard compilers) that prevent correcting many warnings. I wish we had a
finer granularity there but well...

------
emilfihlman
>And this is also yet another demonstration, how C/C++ places return value
into EAX/RAX register.

No, it's a compiler implementation detail, not a C/++ specification.

~~~
ajross
It's specified by the relevant ABI standard, FWIW.

------
zygotic12
'If you don't eat yer meat, you can't have any pudding.' No one mentions the
change in attitude towards programming. If it wasn't correct YOU were not good
enough.

~~~
slededit
It was always that way. The only difference is before the machines were so
primitive that it wasn't an explicit decision on the designers part.

~~~
zygotic12
Being the designer I often wonder if that was my real fascination.

------
denkmoon
This gives me nightmares. I could spend weeks debugging this and not think of
that.

~~~
tom_mellior
You could spend two seconds turning on -Wall instead ;-)

------
lalaithion
What does clang do?

~~~
petermcneeley
clang "<source>:19:1: warning: control reaches end of non-void function
[-Wreturn-type]"

Given that no return is undefined behavior (6.6.3 c++ standard) this probably
should just be treated as an error. (it would be nice if a manifested return
was value/default init but it is not)

The reason why these are not errors in the compiler is for backwards
compatibility.

~~~
chartreusek
Since the article is talking about C/C++

In C reaching the end of a non-void function is not undefined behaviour
though. It's equivalent to ending with a return; However attempting to use the
return value of that function is undefined behaviour. It'd be fine to have
that warning be an error when compiling for C++, but not for C.

C89 3.6.6.4
([http://port70.net/~nsz/c/c89/c89-draft.html#3.6.6.4](http://port70.net/~nsz/c/c89/c89-draft.html#3.6.6.4)):
"If a return statement without an expression is executed, and the value of the
function call is used by the caller, the behavior is undefined. Reaching the }
that terminates a function is equivalent to executing a return statement
without an expression."

~~~
maxlybbert
I’m sure that rule comes from the time when “void” didn’t exist, so functions
were defined to return int, but nobody ever bothered to actually return
anything.

~~~
chartreusek
It's definitely there to retain some backwards compatibility with existing K&R
C programs. Though it does still somewhat exist in the C11 standards.

From 6.9.1 - "If the } that terminates a function is reached, and the value of
the function call is used by the caller, the behavior is undefined"

Though 6.8.6.4 also says "A return statement without an expression shall only
appear in a function whose return type is void"

So it seems like a non-void function hitting the end of the block without a
return statement is allowed (provided the value isn't used). But having a
"return;" in that function would not be in C11.

