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?

4

3 回答 3

5

据我所知,ABI需要GCC和Clang将函数参数的销毁延迟到全表达式结束

这个问题很大程度上依赖于这个假设。让我们看看它是否正确。Itanium C++ ABI 草案3.1.1 值参数

如果该类型有一个非平凡的析构函数,则调用者在控制权返回给它之后(包括调用者抛出异常时)调用该析构函数,在封闭完整表达式的末尾。

ABI 没有定义生命周期,所以让我们检查一下 C++ 标准草案 N4659 [basic.life]

1.2 ... T 类型的对象 o 的生命周期在以下情况下结束:

1.3 如果 T 是具有非平凡析构函数(15.4)的类类型,则析构函数调用开始,或者...

1.4 对象占用的存储被释放,或者被没有嵌套在o([intro.object])中的对象重用。

C++ 标准规定,在这种情况下,当调用析构函数时生命周期结束。因此,ABI 确实需要函数参数的生命周期扩展函数调用的完整表达式。

假设实现定义的要求,我在示例程序中看不到 UB,因此它应该在任何保证遵循 Itanium C++ ABI 的实现上具有预期的行为。GCC 似乎违反了这一点。

GCC 文档确实指出

从 GCC 版本 3 开始,GNU C++ 编译器使用行业标准 C++ ABI,即 Itanium C++ ABI。

因此,所展示的行为可以被认为是一个错误。

另一方面,尚不清楚 [expr.call] 的措辞改变后的这种结果是否是故意的。结果可能被认为是缺陷。


...这表示块范围变量的存储将持续到创建它们的块退出。

的确。但是您引用的[expr.call] /4 说“函数参数是在调用者的上下文中创建和销毁的”。因此,存储一直持续到函数调用块的末尾。似乎与存储期限没有冲突。


请注意,C++ 标准链接指向从当前草案定期生成的站点,因此可能与我引用的 N4659 不同。

于 2018-01-26T01:05:30.263 回答
-2

从 5.2.2/4 函数调用 [expr.call] 开始,在我看来 GCC 是正确的:

参数的生命周期在定义它的函数返回时结束。每个参数的初始化和销毁​​发生在调用函数的上下文中。

于 2018-01-26T00:33:47.903 回答
-2

好吧,从前 C++14 标准给出以下答案,阅读 C++17,我觉得 GCC 和 Clang 都是正确的:

来自:N4659 8.2.2/4 函数调用 [expr.call]

参数的生命周期是在定义它的函数返回时结束,还是在封闭的完整表达式结束时结束,这是实现定义的。每个参数的初始化和销毁​​发生在调用函数的上下文中。

于 2018-01-26T00:46:36.530 回答