我们正与朋友们就代码进行热烈的讨论:
#include#include 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 verbosity测试它(自定义ctors和dtors打印到std::cout
).为什么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)
每个i
in[0,size()]
.
由于字符串的内容是连续存储的([string.require]/4
),这实际上意味着"返回指向缓冲区起始处的指针".
显然当a 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;
),而不是调用一个临时对象的方法的结果.