函数参数的后期销毁

Late destruction of function parameters

根据 n4640 中的 5.2.2/4 "Function call"(n4659 中的8.2.2/4)函数参数在调用者的上下文中创建和销毁。并且允许实现将函数参数的销毁延迟到封闭的完整表达式的末尾(作为实现定义的功能)。请注意,选择不是未指定,而是实现定义

(不完全清楚这与 3.3.3 "Block scope"(n4659 中的 6.3.3)如何一致,这似乎暗示函数参数具有块作用域,然后是 3.7 .3 "Automatic storage duration"(n4659 中的 6.7.3),它表示块作用域变量的存储持续到创建它们的块退出。但是让我们假设我是 missing/misunderstanding 中的某个东西措辞。 显然现在函数参数将具有 their own scope)

据我所知,ABI 要求 GCC 和 Clang 将函数参数的销毁延迟到完整表达式的末尾,即这是这些编译器的实现定义行为。我猜想在这样的实现中,只要这些 references/pointers 仅在调用表达式中使用,return references/pointers 函数参数应该没问题。

但是,以下示例在 GCC 中会出现段错误,但在 Clang 中可以正常工作

#include <iostream>
#include <string>

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

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

两个编译器都会发出关于 return 对局部变量的引用的警告,这在此处非常合适。快速检查生成的代码表明,两个编译器确实将参数的破坏延迟到表达式的末尾。但是,GCC 仍然故意从 foo 中 return 发送一个 "null reference",这导致了崩溃。同时,Clang 表现出 "as expected"、return 对其参数 s 的引用,该参数存在足够长的时间以产生预期的输出。

(在这种情况下,GCC 很容易被愚弄

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

修复了 GCC 下的段错误。)

假设 GCC 保证 "late" 销毁参数,在这种情况下 GCC 的行为是否合理?我是否遗漏了标准中的其他一些段落,这些段落说 return 对函数参数的引用始终未定义,即使它们的生命周期被实现延长了?

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

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.

好吧,我不好从以前的 C++14 之前的标准中给出以下答案,阅读 C++17,在我看来 GCC 和 Clang 都是正确的:

来自:N4659 8.2.2/4 函数调用 [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.

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

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

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.

ABI 没有定义 lifetime,所以让我们检查一下 C++ 标准草案 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]).

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

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

GCC docs 说明

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

因此,所展示的行为可被视为错误。

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


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

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


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