使用暂时的寿命差异
Using of temporary lifetime difference
我们正在和我的朋友就代码进行激烈的讨论:
#include <iostream>
#include <string>
using namespace std;
string getString() {
return string("Hello, world!");
}
int main() {
char const * str = getString().c_str();
std::cout << str << "n";
return 0;
}
此代码在 g++、clang 和 vc++ 上生成不同的输出:
g++
和clang
输出相同:
世界您好!
但是vc++
不输出任何内容(或仅输出空格(:
什么行为是正确的?这可能是根据临时寿命的标准变化吗?
据我所知,通过阅读 IR 的 clang++
,它的工作原理如下:
store `getString()`'s return value in %1
std::cout << %1.c_str() << "n";
destruct %1
就个人而言,我认为gcc
也是这样工作的(我已经用 rvo/move 详细程度对其进行了测试(打印到 std::cout
的自定义 ctor 和 dtor(。为什么 vc++ 以其他方式工作?
clang = Apple LLVM 版本 6.1.0 (clang-602.0.53( (基于 LLVM 3.6.0svn(
g++ = gcc 版本 4.9.2 (Debian 4.9.2-10(
你的程序有未定义的行为!您正在"打印"一个悬空的指针。
getString()
的结果,一个临时字符串,不比该const char*
声明长;因此,在该临时字符串上调用c_str()
的结果也不会。
所以两个编译器都是"正确的";是你和你的朋友错了。
这就是为什么我们不会存储std::string::c_str()
的结果,除非我们真的,真的需要。
是对的,未定义的行为是未定义的。
char const * str = getString().c_str();
getString()
返回一个临时变量,该临时表达式将在包含它的完整表达式末尾销毁。因此,在该行完成后,str
是一个无效的指针,试图检查它会让你陷入不确定行为的境地。
一些标准报价,根据要求(来自 N4140(:
[class.temporary]/3:
临时对象在计算(词法上(包含创建它们的点的完整表达式的最后一步被销毁。
basic_string::c_str
指定如下:
[string.accessors]/1
:一个指针p
,以便p + i == &operator[](i)
[0,size()]
中的每个i
。
由于字符串的内容是连续存储的([string.require]/4
(,这实质上意味着"返回指向缓冲区开头的指针"。
显然,当std::string
被破坏时,它将回收分配的任何内存,使该指针无效(如果你的朋友不相信,他们还有其他问题(。
这是未定义的行为,因此任何事情都可能发生(包括"正确"打印字符串(。
无论如何,UB经常使事情"正常工作",除非该程序实际上在付费客户的计算机上运行,或者如果它显示在大屏幕上,在广大观众面前;-(
问题在于,您正在获取一个临时对象内部的const char *
,该对象在使用指针之前被销毁。
请注意,这种情况与以下情况不同:
const std::string& str = getString(); // Returns a temporary
std::cout << str << "n";
因为在这种情况下,C++标准中有一个关于绑定到临时引用的非常具体的规则。在这种情况下,临时的生存期将延长,直到引用str
也被销毁。该规则仅适用于引用,并且仅当直接绑定到临时对象或临时对象的子对象(如 const std::string& s = getObj().s;
(时,才不适用于调用临时对象的方法的结果。
- 没有找到相关文章