Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is returning a reference to a function local value not a compile error?

The following code invokes undefined behaviour.

int& foo()
{
  int bar = 1234;
  return bar;
}

g++ issues a warning:

warning: reference to local variable ‘bar’ returned [-Wreturn-local-addr]

clang++ too:

warning: reference to stack memory associated with local variable 'bar' returned [-Wreturn-stack-address]

Why is this not a compile error (ignoring -Werror)?

Is there a case where returning a ref to a local var is valid?

EDIT As pointed out, the spec mandates this be compilable. So, why does the spec not prohibit such code?

like image 686
Drew Noakes Avatar asked Oct 10 '14 09:10

Drew Noakes


People also ask

Why is it unsafe for a function to return a local variable by reference?

The return statement should not return a pointer that has the address of a local variable ( sum ) because, as soon as the function exits, all local variables are destroyed and your pointer will be pointing to someplace in the memory that you no longer own.

What is the advantage of returning a reference from the function?

The major advantage of return by address over return by reference is that we can have the function return nullptr if there is no valid object to return.

What do you mean by function returning reference?

Functions in C++ can return a reference as it's returns a pointer. When function returns a reference it means it returns a implicit pointer.

Can local variables be returned?

How to return a local variable from a function? But there is a way to access the local variables of a function using pointers, by creating another pointer variable that points to the variable to be returned and returning the pointer variable itself.


5 Answers

I would say that requiring this to make the program ill-formed (that is, make this a compilation error) would complicate the standard considerably for little benefit. You'd have to exactly spell out in the standard when such cases shall be diagnosed, and all compilers would have to implement them.

If you specify too little, it will not be too useful. And compilers probably already check for this to emit warnings, and real programmers compile with -Wall_you_can_give_me -Werror anyway.

If you specify too much, it will be difficult (or impossible) for compilers to implement the standard.

Consider this class (for which you only have the header and a library):

class Foo
{
  int x;

public:
  int& getInteger();
};

And this code:

int& bar()
{
  Foo f;
  return f.getInteger();
}

Now, should the standard be written to make this ill-formed or not? Probably not, what if Foo is implemented like this:

#include "Foo.h"

int global;

int& Foo::getInteger()
{
  return global;
}

At the same time, it could be implemented like this:

#include "Foo.h"

int& Foo::getInteger()
{
  return x;
}

Which of course would give you a dangling reference.

My point is that the compiler cannot really know whether returning a reference is OK or not, except for a few trivial cases (returning a reference to a function-scope automatic variable or parameter of non-reference type). I don't think it's worth it to complicate the standard for that. Especially as most compilers already warn about this as a quality-of-implementation matter.

like image 161
Angew is no longer proud of SO Avatar answered Sep 29 '22 23:09

Angew is no longer proud of SO


Also, because you may want to get the current stack pointer (whatever that means on your particular implementation).

This function:

 void* get_stack_pointer (void) { int x; return &x; };

AFAIK, it is not undefined behavior if you don't dereference the resulting pointer.

is much more portable than this one:

 void* get_stack_pointer (void) { 
    register void* sp asm ("%esp"); return sp; }

As to why you may want to get the stack pointer: well, there are cases where you have a valid reason to get it: for instance the conservative Boehm garbage collector needs to scan the stack (so wants the stack pointer and the stack bottom).

And if you returned a C++ reference on which you would only take its address using the & unary operator, getting such an address is IIUC legal (it is IMHO the only licit operation you can do on it).

Another reason to get the stack pointer would be to get a non-NULL pointer address (which you could e.g. hash) different of any heap, local or static data. However, you could use (void*)1 or (void*)-1 for that purpose.

So the compiler is right in only warning against this.

I guess that a C++ compiler should accept

int& get_sp_ref(void) { int x; return x; }

void show_sp(void) { 
   std::cout << (&(get_sp_ref())) << std::endl; }
like image 42
Basile Starynkevitch Avatar answered Sep 29 '22 23:09

Basile Starynkevitch


For the same reason C allows you to return a pointer to a memory block that's been freed.

It's valid according to the language specification. It's a horribly bad idea (and is nowhere close to being guaranteed to work) but it's still valid inasmuch as it's not forbidden.

If you're asking why the standard allows this, it's probably because, when references were introduced, that's the way they worked. Each iteration of the standard has certain guidelines to follow (such as minimising the possibility of "breaking changes", those that render existing well-formed programs invalid) and the standard is an agreement between user and implementer, with undoubtedly more implementers than users sitting on the committees :-)

It may be worth pushing that idea through as a potential change and seeing what ISO say but I suspect it would be considered one of those "breaking changes" and therefore very suspect.

like image 28
paxdiablo Avatar answered Sep 30 '22 01:09

paxdiablo


To expand on the earlier answers, the ISO C++ standard does not capture the distinction between warnings and errors to begin with; it simply uses the term 'diagnostic' when referring to what a compiler must emit upon seeing an ill-formed program. Quoting N3337, 1.4, paragraphs 1 and 2:

The set of diagnosable rules consists of all syntactic and semantic rules in this International Standard except for those rules containing an explicit notation that “no diagnostic is required” or which are described as resulting in “undefined behavior.”

Although this International Standard states only requirements on C++ implementations, those requirements are often easier to understand if they are phrased as requirements on programs, parts of programs, or execution of programs. Such requirements have the following meaning:

  • If a program contains no violations of the rules in this International Standard, a conforming implementation shall, within its resource limits, accept and correctly execute that program.

  • If a program contains a violation of any diagnosable rule or an occurrence of a construct described in this Standard as “conditionally-supported” when the implementation does not support that construct, a conforming implementation shall issue at least one diagnostic message.

  • If a program contains a violation of a rule for which no diagnostic is required, this International Standard places no requirement on implementations with respect to that program.

like image 35
LThode Avatar answered Sep 29 '22 23:09

LThode


Something not mentioned by other answers yet is that this code is OK if the function is never called.

The compiler isn't required to diagnose whether a function might ever be called or not. For example you might set up a program which looks for counterexamples to Fermat's Last Theorem, and calls this function if it finds one. It would be a mistake for the compiler to reject such a program.

like image 38
M.M Avatar answered Sep 29 '22 23:09

M.M