14

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?

AnT
  • 291,388
  • 39
  • 487
  • 734
  • I'm not a language lawyer, although I play one on TV and at work. The `"Hello world"` is an argument; `s` is a parameter. The block scope of the parameter `s` ends at the return of `foo` outermost block scope. – Eljay Jan 26 '18 at 00:31
  • @Eljay: Your terminology is correct. But 5.2.2 talks about *parameters* specifically. The corresponding change in the text was caused by DR#1880 (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1880), which also talks about *parameters* specifically. The issue would not even arise with *arguments*. – AnT Jan 26 '18 at 00:38
  • By "5.2.2/4" I guess you mean 8.2.2/4 in N4659 (expr.call/4) – M.M Jan 26 '18 at 00:40
  • Would you state the standard version for which this question applies? I assume C++17, but it won't be obvious in the future. – eerorika Jan 26 '18 at 00:42
  • I'd guess that gcc returns a null reference when returning a local/parameter as a blanket measure to cover misuse. – kmdreko Jan 26 '18 at 01:01
  • Core issue 1880 is still open. This change was made by P0135R1 as part of the guaranteed copy elision changes. Note that under 1880's direction (unspecified), the code has UB per [intro.abstract]/5 even if the compiler otherwise always destroys the parameter at the end of the full-expression. – T.C. Jan 26 '18 at 03:01

3 Answers3

5

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.

Community
  • 1
  • 1
eerorika
  • 181,943
  • 10
  • 144
  • 256
  • In the c++ standard, the (unspecified) term*context* is usualy used when refering to access control or template argument substitution. The note that follow the seems to say that the context only constrains the access, not the lifet time neither the storage duration. – Oliv Jan 26 '18 at 11:34
-2

From 5.2.2/4 Function call [expr.call], seems to me GCC is correct:

The lifetime of a parameter ends when the function in which it is defined returns. The initialization and destruction of each parameter occurs within the context of the calling function.

AdvSphere
  • 816
  • 6
  • 13
  • Which version of the standard are you quoting? Also, please mention the name of the chapter or the bracketed shorthand as the numbering varies across versions. – eerorika Jan 26 '18 at 00:37
  • N3690, http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3690.pdf – AdvSphere Jan 26 '18 at 00:38
  • Please see http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1880 and the corresponding changes made in the text of 5.2.2/4. – AnT Jan 26 '18 at 00:39
  • N3690 is a pre-C++14 draft. We're past C++17 now – M.M Jan 26 '18 at 00:40
  • In N4659/8.2.2/4 the text is nearly the same. "It is implementation-defined whether the lifetime of a parameter ends when the function in which it is defined returns or at the end of the enclosing full-expression. The initialization and destruction of each parameter occurs within the context of the calling function." – Eljay Jan 26 '18 at 00:46
  • @Eljay You want to be using N4700, not N4659 for a stable draft. – user167921 Jan 26 '18 at 01:09
  • 1
    @user167921 N4659 is the same as C++17 as far as I know, whereas N4700 is a post-C++17 draft. So I would recommend using N4659 – M.M Jan 26 '18 at 01:11
-2

Ok my bad from giving the below answer from former pre C++14 standard, reading the C++17, seems to me both GCC and Clang are correct:

From: N4659 8.2.2/4 Function call [expr.call]

It is implementation-defined whether the lifetime of a parameter ends when the function in which it is defined returns or at the end of the enclosing full-expression. The initialization and destruction of each parameter occurs within the context of the calling function.

AdvSphere
  • 816
  • 6
  • 13
  • @user167921 "Is GCC's behavior justified in this case, under assumption that it guarantees "late" destruction of parameters? " Is an assumption. – AdvSphere Jan 26 '18 at 01:16