奇怪的标准::带有常量字符*的cout行为

Strange std::cout behaviour with const char*

本文关键字:字符 cout 行为 常量 标准      更新时间:2023-10-16

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

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的生命周期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();

但是,如果编译器不执行一些返回值优化,这将导致构造函数复制构造函数(<C++11,>非常糟糕(或移动构造函数(>= C++11,更好,但不必要的(被执行。


顺便说一句,这个确切的问题甚至在"C++编程语言"第 4 版中都有涉及!

在§10.3.4"临时对象"中,Stroustrup先生写道:

标准库字符串具有一个成员c_str() (§36.3(,该成员返回指向以零结尾的字符数组的 C 样式指针 (§2.2.5, §43.4(。此外,运算符+定义为表示字符串 串联。这些是字符串的有用工具。然而,在 组合它们可能会导致模糊的问题。例如:

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
    }
}

[...]创建一个临时字符串对象来保存s1+s2 。接下来,指针 从该对象中提取 C 样式字符串。然后 - 在结束时 表达式 – 删除临时对象。然而,C- c_str()返回的样式字符串作为临时的一部分进行分配 对象保存s1+s2,并且该存储不保证在之后存在 那个临时被摧毁了。因此,cs点要解除分配 存储。输出操作cout<<cs可能按预期工作,但 那纯粹是运气。编译器可以检测并警告 此问题的许多变体。 if 语句的问题有点微妙。这 条件将按预期工作,因为其中的完整表达式 创建的临时持有s2+s3是条件本身。 但是,该临时在受控语句之前被销毁 输入,因此任何使用cs都不保证有效。

所以,不要担心你的C++技能。甚至C++圣经也解释了它。;-)

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

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

问题就在这里:

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

errorMessage(( 将返回一个临时的 std::string,在下一行运行之前,该字符串将超出范围。

我建议这样做:

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

然后,当您需要传递指向基础缓冲区的指针时,可以使用:

message.c_str();