【问题标题】:Late destruction of function parameters函数参数的后期销毁
【发布时间】:2018-07-05 08:28:08
【问题描述】:

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

(不完全清楚这与3.3.3“块范围”(n4659中的6.3.3)是如何一致的,这似乎暗示函数参数具有块范围,然后是3.7.3“自动存储”持续时间”(n4659 中的 6.7.3),它表示块范围变量的存储将持续到创建它们的块退出。但是让我们假设我在措辞中遗漏/误解了某些内容。 显然现在函数参数将有their own scope)

据我所知,ABI 要求 GCC 和 Clang 将函数参数的销毁延迟到完整表达式的末尾,即这是这些编译器的实现定义的行为。我猜想在这样的实现中应该可以返回对函数参数的引用/指针,只要这些引用/指针仅在调用表达式中使用。

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

#include <iostream>
#include <string>

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

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

两个编译器都会发出关于返回对局部变量的引用的警告,这在这里非常合适。对生成的代码的快速检查表明,两个编译器确实将参数的破坏延迟到表达式的末尾。但是,GCC 仍然故意从foo 返回一个“空引用”,从而导致崩溃。同时,Clang 的行为“如预期”,返回对其参数 s 的引用,该参数的生存时间足以产生预期的输出。

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

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

它修复了 GCC 下的段错误。)

在这种情况下,GCC 的行为是否合理,假设它保证“后期”破坏参数?我是否遗漏了标准中的其他段落,即返回对函数参数的引用始终未定义,即使实现延长了它们的生命周期?

【问题讨论】:

  • 我不是语言律师,虽然我在电视和工作中玩过。 "Hello world" 是一个参数; s 是一个参数。参数s的块作用域在foo最外层块作用域的返回处结束。
  • @Eljay:您的术语是正确的。但是 5.2.2 专门讨论了 parameters。文中相应的变化是由 DR#1880 (open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1880) 引起的,其中也专门谈到了parametersarguments 甚至不会出现这个问题。
  • “5.2.2/4”我猜你的意思是 N4659 中的 8.2.2/4 (expr.call/4)
  • 您能说明这个问题适用的标准版本吗?我假设是 C++17,但将来不会很明显。
  • 我猜 gcc 在返回本地/参数作为覆盖滥用的一揽子措施时返回空引用。

标签: c++ scope lifetime function-parameter destruction


【解决方案1】:

据我所知,ABI 需要 GCC 和 Clang 将函数参数的销毁延迟到完整表达式的末尾

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

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

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 docs 声明

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

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

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


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

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


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

【讨论】:

  • 在 c++ 标准中,(未指定的)术语context 通常用于指代访问控制或模板参数替换。后面的注释似乎说上下文只限制访问,而不是生命周期和存储持续时间。
【解决方案2】:

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

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

【讨论】:

【解决方案3】:

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

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

参数的生命周期是否结束由实现定义 当定义它的函数返回时或在 包含完整的表达式。每个的初始化和销毁 参数出现在调用函数的上下文中。

【讨论】:

  • @user167921 “假设 GCC 的行为在这种情况下是合理的,假设它保证参数的“延迟”销毁?“是一个假设。
猜你喜欢
  • 1970-01-01
  • 2016-08-27
  • 2022-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-28
相关资源
最近更新 更多