Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Late destruction of function parameters

According to 5.2.2/4 "Function call" in n4640 (8.2.2/4 in n4659) function parameters are created and destroyed in the context of the caller. And implementations are allowed to delay the destruction of function parameters to the end of the enclosing full expression (as an implementation-defined feature). Note that the choice is not unspecified, but rather implementation-defined.

(It is not entirely clear how this agrees with 3.3.3 "Block scope" (6.3.3 in n4659), which seems to imply that function parameters have block scope, and then 3.7.3 "Automatic storage duration" (6.7.3 in n4659), which says that the storage for block scope variables lasts until the block in which they are created exits. But let's assume that I'm missing/misunderstanding something in the wording. Apparently now function parameters will have their own scope)

As far as I know, ABI requires GCC and Clang to delay the destruction of function parameters to the end of full expression, i.e. this is the implementation-defined behavior of these compilers. I would guess that in implementations like that it should be OK to return references/pointers to function parameters as long as these references/pointers are used within the calling expression only.

However, the following example segfaults in GCC and works fine in Clang

#include <iostream>
#include <string>

std::string &foo(std::string s)
{
  return s;
}

int main()
{
   std::cout << foo("Hello World!") << std::endl;
}

Both compilers issue a warning about returning a reference to a local variable, which is perfectly appropriate here. A quick inspection of the generated code shows that both compilers do indeed delay the destruction of the parameter to the end of the expression. However, GCC still deliberately returns a "null reference" from foo, which causes the crash. Meanwhile, Clang behaves "as expected", returning a reference to its parameter s, which survives long enough to produce the expected output.

(GCC is easy to fool in this case by simply doing

std::string &foo(std::string s)
{
  std::string *p = &s;
  return *p;
}

which fixes the segfault under GCC.)

Is GCC's behavior justified in this case, under assumption that it guarantees "late" destruction of parameters? Am I missing some other passage in the standard that says that returning references to function parameters is always undefined, even if their lifetimes are extended by the implementation?

like image 997
AnT Avatar asked Jan 26 '18 00:01

AnT


1 Answers

As far as I know, ABI requires GCC and Clang to delay the destruction of function parameters to the end of full expression

The question relies heavily on this assumption. Let's see if it's correct. Itanium C++ ABI draft 3.1.1 Value Parameters says

If the type has a non-trivial destructor, the caller calls that destructor after control returns to it (including when the caller throws an exception), at the end of enclosing full-expression.

The ABI doesn't define lifetime, so let us check C++ standard draft N4659 [basic.life]

1.2 ... The lifetime of an object o of type T ends when:

1.3 if T is a class type with a non-trivial destructor (15.4), the destructor call starts, or ...

1.4 the storage which the object occupies is released, or is reused by an object that is not nested within o ([intro.object]).

The C++ standard says that lifetime ends in this case when the destructor is called. As such, the ABI does indeed require that the lifetime of a function parameter extends the full expression of the function call.

Assuming that implementation defined requirement, I see no UB in the example program, so it should have expected behaviour on any implementation that guarantees to follow the Itanium C++ ABI. GCC appears to violate that.

GCC docs do state that

From GCC version 3 onwards the GNU C++ compiler uses an industry-standard C++ ABI, the Itanium C++ ABI.

As such, the demonstrated behaviour could be considered a bug.

On the other hand, it is unclear whether this consequence of the changed wording of [expr.call] is intentional. The consequence might be considered to be a defect.


... which says that the storage for block scope variables lasts until the block in which they are created exits.

Indeed. But the [expr.call]/4 that you quoted says "function parameters are created and destroyed in the context of the caller". As such, the storage lasts until the end of the block of the function call. There appears to be no conflict with the storage duration.


Note that the C++ standard links point to a site that is periodically generated from the current draft and therefore may differ from N4659 that I've quoted.

like image 146
eerorika Avatar answered Nov 10 '22 19:11

eerorika