I'm convinced that all three functions have undefined behavior.
To people who insist that f3
is not UB (or even f1
/f2
): shall you please try to run this code:
#include <iostream>
const char* const& f1() { return "hello1"; }
const char* const& f2() { return static_cast<const char*>("hello2"); }
const char* const& f3() { const char* const& r = "hello3"; return r; }
int main()
{
using namespace std;
//#define F f1
//#define F f2
#define F f3
const char* const& ret = F();
cerr << ret;
cerr << ",";
cerr << ret;
return 0;
}
(I used cerr
rather than cout
to get immediate flushing. You can change cerr
to cout
and add a cout << flush;
after the second output of ret
.)
On my GCC here's what I got printed:
- with
f1
: hello1,8??q?
(some random chars after the comma)
- with
f2
: hello2,8j?y5
(some random chars after the comma)
- with
f3
: hello3,,
(a second comma after the comma)
That looks very much like UB to me...
(Note: If I remove either const&
then it "works". The const&
to really remove being the one in the return type of course.)
I think that's because what happens in f3
is something like this:
const char* const& f3()
{
const char* __tmp001 = &("hello3"[0]); // "array decaying"
const char* const& r = __tmp001;
return r;
}
Indeed the string literal "hello3"
is not a const char*
, it's a (static) const char [7]
. In the code const char* const& r = "hello3";
, the reference can't be bound to this char array directly because it has not the same type, so the compiler has to create a temporary char pointer (created on the stack) initialized by implicit conversion (array-to-pointer decaying) to which the reference is bound (demo). The lifetime of this temporary const char*
is "extended" to the lifetime of the reference r
, thus doesn't end at the first semicolon, but ends when the function returns (demo and output with all optimizations off). So f3
returns a "dangling reference". In my test output code, any subsequent operation which overwrites the stack makes the UB visible.
Edit after jalf's comment: I'm conscious that "it prints garbage on the second output" is not a proof of UB. A program with UB can as well work exactly as expected, or crash, or do nothing, or whatever. But, nonetheless, I don't believe that a well-defined program (with no UB) would print garbage like that...