函数参数的后期破坏

时间:2018-01-26 00:02:54

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

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

目前还不完全清楚这是如何与3.3.3" Block scope"(n4659中的6.3.3)一致,这似乎意味着函数参数具有块范围,然后3.7 .3"自动存储持续时间"(n4659中为6.7.3),表示块范围变量的存储持续到创建它们的块为止。但是让我们假设我&# 39; m。在措辞中缺少/误解了一些东西。显然现在函数参数将有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;
}

两个编译器都会发出关于返回对局部变量的引用的警告,这在这里非常合适。快速检查生成的代码表明,两个编译器确实将参数的破坏延迟到表达式的末尾。然而,海湾合作委员会仍故意返回一个&#34; null引用&#34;来自foo,导致崩溃。与此同时,Clang表达了&#34;正如所料&#34;,返回对其参数s的引用,该参数存活了足够长的时间以产生预期的输出。

(简单地说,GCC在这种情况下很容易被愚弄

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

修复了GCC下的段错误。)

GCC的行为在这种情况下是否合理,假设它保证&#34;晚期&#34;破坏参数?我是否遗漏了标准中的其他段落,该段落说明返回对函数参数的引用总是未定义的,即使它们的生命周期是由实现扩展的?

3 个答案:

答案 0 :(得分:5)

  

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

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

  

如果类型有一个非平凡的析构函数,调用程序在控制返回它之后调用该析构函数(包括调用者抛出异常时),在封闭full-expression的末尾。

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 docs请说明

  

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

因此,证明的行为可能被视为一个错误。

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

  

...表示块作用域变量的存储一直持续到创建它们的块为止。

事实上。但是您引用的[expr.call] / 4表示“功能参数已创建并在上下文中调用。因此,存储一直持续到函数调用块的结尾。似乎与存储持续时间没有冲突。

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

答案 1 :(得分:-2)

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

  

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

答案 2 :(得分:-2)

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

发件人:N4659 8.2.2 / 4函数调用[expr.call]

  

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