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

Strange std::cout behaviour with const char*

我有一个方法,其中 returns 一个字符串显示为错误消息。根据此错误在程序中发生的位置,我可能会在显示之前对错误消息添加更多解释。

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

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

(我将消息存储为 const char*,因为我实际上会将此错误提供给另一个接受 const char* 参数的方法)

此时它输出垃圾(控制台上无法打印的字符)。

所以我玩了一下,发现如果我这样做:

// 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;

然后它会正确显示消息两次。

为什么向 cout 提供额外的参数会导致它按我的预期工作?

("Some extra info \n" + errorMessage()) 是一个临时 std::string。这意味着,语句完成后,它的生命周期就结束了。

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

有效是因为在 std::cout 使用 std::string 时它的生命周期尚未结束。

<< message

不过,部分是未定义的行为。运气真好。

要解决此问题,您需要使用 const std::string& 或自 C++11 起的 std::string&&:[=34= 来延长 std::string 的生命周期]

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

现在你可以随心所欲地对它们进行操作了。

另一种方法是

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

但是,如果编译器不执行某些操作 Return Value Optimization,这将导致构造函数 复制构造函数(< C++11,非常糟糕) 或移动构造函数(>= C++11,更好,但不必要)被执行。


顺便说一句,这个确切的问题甚至在 "The C++ Programming Language" 4th 版中都有涉及!

在 §10.3.4 "Temporary Objects" 中,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). Also, the operator + is defined to mean string concatenation. 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. Next, a pointer to a C-style string is extracted from that object. 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. Consequently, cs points to deallocated storage. The output operation cout<<cs might work as expected, but that would be sheer luck. A compiler can detect and warn against many variants of this problem. The problem with the if-statement is a bit more subtle. The condition will work as expected because the full expression in which the temporary holding s2+s3 is created is the condition itself. However, that temporary is destroyed before the controlled statement is entered, so any use of cs there is not guaranteed to work.

所以,不用担心您的 C++ 技能。甚至 C++ 圣经也对此进行了解释。 ;-)

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

errorMessage() returns 临时 std::string 对象
"Some extra info \n" + errorMessage() 连接创建另一个临时对象。
取 c_str 的 returns 指向其内部缓冲区的指针(不是副本)。
然后临时对象被删除,指针失效。
其他一切都是未定义的。它可能会给出正确的输出、崩溃或执行任何其他操作。

问题出在这里:

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

errorMessage() 将 return 一个临时的 std::string,它将在下一行运行之前超出范围。

我建议改为这样做:

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

然后当你需要传递一个指向底层缓冲区的指针时,你可以使用:

message.c_str();