问题描述
前几天我看到了一个奇怪的行为。 所以我想将行(存在于向量中)存储在一个字符数组中,并想使用 '\n' 作为分隔符。
我知道字符串类中的 c_str() 方法返回一个指向以 '\0' 结尾的字符数组的指针。
基于我对 C++ 的经验/理解。(请参阅 greet0 和 greet2 函数)。 我认为它应该可以工作,但它没有。
谁能解释一下三个greet函数的不同行为?每个greet函数中提到的对象的作用域是什么? (我还猜测字符串对象在 greet1 函数中被销毁,但如果是这种情况, cout
//The snippet that where i first encountered the issue.
const char* concatinated_str(std::vector<std::string> lines,const char *delimiter)
{
std::stringstream buf;
std::copy(lines.begin(),lines.end(),std::ostream_iterator<std::string>(buf,delimiter));
string w = buf.str();
const char *ret = w.c_str();
return ret;
}
//Implementation 0
string greet0(){
string msg = "hello";
return msg;
}
//Implementation 1
const char* greet1(){
string msg = "hello";
cout<<&msg<<endl;
return msg.c_str();
}
//Implementation 2
const char* greet2(){
const char* msg = "hello";
return msg;
}
int main(){
auto w0 = greet0();
cout<<&w0<<endl;
cout<<"greet0:"<<w0<<endl;
auto w1 = greet1();
cout<<"greet1:"<<w1<<endl;
const char* w2 = greet2();
cout<<"greet2:"<<w2<<endl;
}
输出:
0x7fff0ff3e8e0
0x7fff0ff3e8e0
greet0:hello
greet1:
greet2:hello
解决方法
按值返回 std::string
或指向字符串字面量的指针完全没问题。
使用 greet1()
的返回值虽然具有未定义行为,因为您尝试打印其元素的 std::string
在其封闭函数结束时死亡,从而使返回的指针悬空。
如果未定义对悬空指针的引用,会发生什么情况,就像您有一个指向空字符串的指针一样,因为存储被重用是更良性的可能性之一。
顺便说一句,std::string
的地址对于执行您的程序的人来说很少有兴趣,尽管打印它非常好。
在语句cout<<&w0<<endl
中; cout<<&msg<<endl
;您正在输出指向 std::string
的指针。删除 & 以实际打印字符串,而不是其地址。如果您对两个不同对象的相同结果感到困惑,那可能是因为它们是局部变量的地址。内存可以重复使用,因为这些对象在其生命周期内是有限的,不必具有唯一的位置。
在 greet0
中,技术上 msg
是一个局部变量,在退出函数时停止存在,但编译器可能会优化返回值,而不是将 msg
复制到外部,实际代码将形成一个目标 w0
处的正确对象。使用较新的编译器可以保证返回值优化。
在函数中
const char* greet1(){
string msg = "hello";
cout<<&msg<<endl;
return msg.c_str();
}
msg
这里是一个函数局部变量,所以它代表一个在包含它的范围结束时停止存在的对象,即在函数返回之后。在 return
行之后,从 c_str()
获取的指针是悬空的,因为该方法返回一个指向 std::string
内部存储的指针。 msg
的存储被破坏,您正在通过访问它来调用 Undefined Behaviour。分段错误(顺便说一下,这纯粹是 Linux 事件,Windows 中的机制不同)是可能的结果,但不是必需的。
在第三个函数中
const char* greet2(){
const char* msg = "hello";
return msg;
}
msg
指向一个包含常量字符串“hello”的数组。由字符串文字创建的常量字符串与全局静态对象具有相同的生命周期。这些字符串是在编译期间形成的。退出函数不会使指针失效,您仍然可以取消引用它,因为字符串仍然存在。
调用未定义行为的唯一代码与此函数相关
#Implementation 1
const char* greet1(){
string msg = "hello";
cout<<&msg<<endl;
return msg.c_str();
}
类型为 msg
的本地对象 std::string
在退出函数后将不存在。它会被摧毁。所以函数返回一个无效的指针。
在这个函数实现中
#Implementation 2
const char* greet2(){
const char* msg = "hello";
return msg;
}
返回一个指向字符串文字 "hello"
的第一个字符的指针,该字符具有静态存储持续时间。这意味着退出函数后字符串文字将处于活动状态。因此该函数返回一个有效的指针。
这个功能
#Implementation 0
string greet0(){
string msg = "hello";
return msg;
}
返回一个 std::string
类型的临时对象,该对象被移动(可能带有移动省略)到 main 中的变量 w0
auto w0 = greet0();
所以这个函数是正确的。