根据n4640中的5.2.2/4 "函数调用"(在n4659中的8.2.2/4),函数参数在调用者的上下文中创建和销毁。实现允许将函数参数的销毁延迟到封闭完整表达式的末尾(作为实现定义的特性)。注意,选择不是未指定的,而是实现定义的。
(3.3.3的意思似乎是暗示函数参数具有块作用域,然后3.7.3表示局部变量存储持续到创建它们的块退出。但假设我在措辞上遗漏/误解了一些内容。)显然现在函数参数将拥有它们自己的作用域。
据我所知,ABI要求GCC和Clang将函数参数的销毁延迟到完整表达式的末尾,即这是这些编译器的实现定义行为。我猜想,在这种实现中,只要这些引用/指针仅在调用表达式内使用,将函数参数的引用/指针返回应该是可以的。
但是,在GCC中以下示例会导致段错误,在Clang中正常工作。
(3.3.3的意思似乎是暗示函数参数具有块作用域,然后3.7.3表示局部变量存储持续到创建它们的块退出。但假设我在措辞上遗漏/误解了一些内容。)显然现在函数参数将拥有它们自己的作用域。
据我所知,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