简体   繁体   English

使用const char *的奇怪的std :: cout行为

[英]Strange std::cout behaviour with const char*

I have a method which returns a string to display as an error message. 我有一个方法,它返回一个字符串以显示为错误消息。 Depending on where this error occurs in the program, I might add a bit more of an explanation to the error message before displaying it. 根据程序中出现此错误的位置,我可能会在显示错误消息之前添加一些解释。

string errorMessage() {
    return "this is an error";
}

// somewhere in the program...
const char* message = ("Some extra info \n" + errorMessage()).c_str();
cout << message << endl;

(I am storing the message as a const char* since I will actually provide this error to another method which accepts const char* arguments) (我将消息存储为const char *,因为我实际上会将此错误提供给另一个接受const char *参数的方法)

At this point it outputs garbage (unprintable characters on the console). 此时它输出垃圾(控制台上的不可打印的字符)。

So I played with it and found that if instead I do: 所以我玩了它,发现如果相反我做:

// somewhere in the program...
const char* message = ("Some extra info \n" + errorMessage()).c_str();
cout << ("Some extra info \n" + errorMessage()).c_str() << endl << message << endl;

then it displays the message correctly twice. 然后它会正确显示两次消息。

Why does providing the extra argument to cout cause it to work as I intended? 为什么为cout提供额外的参数会导致它按照我的意图工作?

("Some extra info \\n" + errorMessage()) is a temporary std::string . ("Some extra info \\n" + errorMessage())是一个临时的 std::string That means, after the statement is finished, it's lifetime has ended. 这意味着,在声明完成后,它的生命周期已经结束。

cout << ("Some extra info \n" + errorMessage()).c_str() << endl

works because at the point std::cout uses the std::string its lifetime hasn't ended yet. 因为在std::cout使用std::string它的生命周期尚未结束。 The

<< message

part is undefined behavior, though. 但是,部分是未定义的行为。 Sheer luck it works. 纯粹的运气它有效。

To fix the problem, you need to extend the std::string 's lifetime with either a const std::string& or, since C++11, a std::string&& : 要解决这个问题,你需要使用const std::string&或扩展std::string的生命周期,因为C ++ 11, std::string&&

const std::string&  str_const_ref = "Some extra info \n" + errorMessage();
std::string&& str_rvalue = "Some extra info \n" + errorMessage();

Now you can operate on them as you want to. 现在您可以根据需要对它们进行操作。

Another way is to 另一种方式是

std::string str = "Some extra info \n" + errorMessage();

However, if the compiler doesn't do some Return Value Optimization , this will lead to a constructor and a copy constructor (< C++11, very bad ) or move constructor (>= C++11, better, but unnecessary) getting executed. 但是,如果编译器没有做一些返回值优化 ,这将导致构造函数复制构造函数(<C ++ 11, 非常糟糕 )或移动构造函数(> = C ++ 11,更好,但不必要)被执行。


BTW, this exact issue is even covered in "The C++ Programming Language" 4 th Edition! 顺便说一句,这个确切的问题甚至包含在“The C ++ Programming Language” 4版中!

In §10.3.4 "Temporary Objects", Mr Stroustrup writes: 在§10.3.4“临时对象”中,Stroustrup先生写道:

The standard-library string has a member c_str() (§36.3) that returns a C-style pointer to a zero-terminated array of characters (§2.2.5, §43.4). 标准库字符串有一个成员c_str() (第36.3节),它返回一个C样式指针,指向一个以零结尾的字符数组(§2.2.5,§43.4)。 Also, the operator + is defined to mean string concatenation. 此外,operator +被定义为表示字符串连接。 These are useful facilities for strings. 这些是字符串的有用工具。 However, in combination they can cause obscure problems. 但是,它们可能会导致模糊的问题。 For example: 例如:

 void f(string& s1, string& s2, string& s3) { const char* cs = (s1+s2).c_str(); cout << cs; if (strlen(cs=(s2+s3).c_str())<8 && cs[0]=='a') { // cs used here } } 

[...] A temporary string object is created to hold s1+s2 . [...]创建一个临时字符串对象来保存s1+s2 Next, a pointer to a C-style string is extracted from that object. 接下来,从该对象中提取指向C样式字符串的指针。 Then – at the end of the expression – the temporary object is deleted. 然后 - 在表达式的末尾 - 删除临时对象。 However, the C- style string returned by c_str() was allocated as part of the temporary object holding s1+s2 , and that storage is not guaranteed to exist after that temporary is destroyed. 但是, c_str()返回的C样式字符串被分配为保存s1+s2的临时对象的一部分,并且在该临时性被销毁之后不保证该存储存在。 Consequently, cs points to deallocated storage. 因此, cs指向解除分配的存储。 The output operation cout<<cs might work as expected, but that would be sheer luck. 输出操作cout<<cs可能会按预期工作,但这将是纯粹的运气。 A compiler can detect and warn against many variants of this problem. 编译器可以检测并警告此问题的许多变体。 The problem with the if -statement is a bit more subtle. if -statement的问题有点微妙。 The condition will work as expected because the full expression in which the temporary holding s2+s3 is created is the condition itself. 条件将按预期工作,因为创建临时保持s2+s3的完整表达式是条件本身。 However, that temporary is destroyed before the controlled statement is entered, so any use of cs there is not guaranteed to work. 但是,在输入受控语句之前,该临时文件已被销毁,因此无法保证在那里使用cs

So, do not worry about your C++ skills. 所以,不要担心你的C ++技能。 Even the C++ bible explains it. 甚至C ++圣经也解释了它。 ;-) ;-)

const char* message = ("Some extra info \n" + errorMessage()).c_str();
cout << message << endl;  

errorMessage() returns a temporary std::string object errorMessage()返回一个临时的std::string对象
Concatenating with "Some extra info \\n" + errorMessage() creates another temporary object. "Some extra info \\n" + errorMessage()会创建另一个临时对象。
Taking c_str of it returns a pointer to its internal buffer (not a copy). 以c_str为例,返回指向其内部缓冲区的指针(不是副本)。
And then the temporary object is deleted, and the pointer invalid. 然后删除临时对象,指针无效。
Everything else is undefined. 其他一切都未定义。 It may give the correct output, crash, or do anything else. 它可能会提供正确的输出,崩溃或做任何其他事情。

The problem is here: 问题出在这里:

const char* message = ("Some extra info \n" + errorMessage()).c_str();

errorMessage() will return a temporary std::string which will go out of scope before the next line runs. errorMessage()将返回一个临时的std :: string,它将在下一行运行之前超出范围。

I would suggest doing this instead: 我建议改为:

std::string message = "Some extra info \n" + errorMessage();

Then when you need to pass a pointer to the underlying buffer you can use: 然后,当您需要将指针传递给底层缓冲区时,您可以使用:

message.c_str();

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

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