简体   繁体   English

功能参数的延迟破坏

[英]Late destruction of function parameters

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. 根据n4640中的5.2.2 / 4“函数调用”(n4659中的8.2.2 / 4 ),在调用者的上下文中创建和销毁函数参数。 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 ) 目前还不完全清楚这与3.3.3“块范围”(n4659中的6.3.3)是否一致,这似乎意味着函数参数具有块范围,然后是3.7.3“自动存储持续时间”(6.7.3)在n4659)中,它表示块作用域变量的存储一直存在,直到它们被创建的块退出。但是我们假设我在措辞中缺少/误解了某些东西。 显然现在函数参数将有自己的范围

As far as I know, ABI requires GCC and Clang to delay the destruction of function parameters to the end of full expression, ie this is the implementation-defined behavior of these compilers. 据我所知,ABI要求GCC和Clang将函数参数的销毁延迟到完全表达式的末尾,即这是这些编译器的实现定义行为。 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 但是,以下示例在GCC中的段错误并且在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. 但是,GCC仍然故意从foo返回一个“空引用”,这会导致崩溃。 Meanwhile, Clang behaves "as expected", returning a reference to its parameter s , which survives long enough to produce the expected output. 同时,Clang表现为“按预期”,返回对其参数s的引用,该参数存活足够长以产生预期输出。

(GCC is easy to fool in this case by simply doing (简单来说,GCC在这种情况下很容易愚弄

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

which fixes the segfault under GCC.) 它修复了GCC下的段错误。)

Is GCC's behavior justified in this case, under assumption that it guarantees "late" destruction of parameters? 在这种情况下GCC的行为是否合理,假设它保证参数的“晚期”破坏? 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? 我是否遗漏了标准中的其他段落,即返回对函数参数的引用始终未定义,即使它们的生命周期是由实现扩展的?

As far as I know, ABI requires GCC and Clang to delay the destruction of function parameters to the end of full expression 据我所知,ABI要求GCC和Clang将函数参数的破坏延迟到完整表达式的末尾

The question relies heavily on this assumption. 这个问题在很大程度上依赖于这个假设。 Let's see if it's correct. 让我们看看它是否正确。 Itanium C++ ABI draft 3.1.1 Value Parameters says Itanium C ++ ABI草案3.1.1值参数

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. 如果类型具有非平凡的析构函数,则在封闭full-expression结束时,调用者在控制返回之后调用该析构函数(包括调用者抛出异常时)。

The ABI doesn't define lifetime , so let us check C++ standard draft N4659 [basic.life] ABI没有定义生命周期 ,所以让我们检查一下C ++标准草案N4659 [basic.life]

1.2 ... The lifetime of an object o of type T ends when: 1.2 ...类型T的对象o的生命周期结束时:

1.3 if T is a class type with a non-trivial destructor (15.4), the destructor call starts, or ... 1.3如果T是具有非平凡析构函数(15.4)的类类型,则析构函数调用开始,或者......

1.4 the storage which the object occupies is released, or is reused by an object that is not nested within o ([intro.object]). 1.4对象占用的存储被释放,或被未嵌套在o中的对象重用([intro.object])。

The C++ standard says that lifetime ends in this case when the destructor is called. C ++标准表示,在调用析构函数时,生命周期结束。 As such, the ABI does indeed require that the lifetime of a function parameter extends the full expression of the function call. 因此,ABI确实要求函数参数的生命周期扩展函数调用的完整表达式。

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. 假设实现定义了需求,我在示例程序中看不到UB,因此它应该在任何保证遵循Itanium C ++ ABI的实现上具有预期的行为。 GCC appears to violate that. 海湾合作委员会似乎违反了这一点。

GCC docs do state that GCC文档确实说明了这一点

From GCC version 3 onwards the GNU C++ compiler uses an industry-standard C++ ABI, the Itanium C++ ABI. 从GCC版本3开始,GNU C ++编译器使用行业标准的C ++ ABI,即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. 另一方面,不清楚[expr.call]改变措辞的后果是否是故意的。 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 " . 但是你引用的[expr.call] / 4表示“函数参数是 调用者上下文创建和销毁 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. 请注意,C ++标准链接指向从当前草稿定期生成的站点,因此可能与我引用的N4659不同。

From 5.2.2/4 Function call [expr.call], seems to me GCC is correct: 从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. 每个参数的初始化和销毁​​发生在调用函数的上下文中。

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: 好吧,我从前C ++ 14标准中给出以下答案,阅读C ++ 17,我认为GCC和Clang都是正确的:

From: N4659 8.2.2/4 Function call [expr.call] 来自: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. 每个参数的初始化和销毁​​发生在调用函数的上下文中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM