【问题标题】:Strange std::cout behaviour with const char*带有 const char* 的奇怪 std::cout 行为
【发布时间】:2023-03-14 22:03:01
【问题描述】:

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

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 提供额外的参数会导致它按我的预期工作?

【问题讨论】:

    标签: c++ string iostream cout c-strings


    【解决方案1】:

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

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

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

    << message
    

    part 是未定义的行为。纯粹是运气好。

    要解决此问题,您需要使用 const std::string&amp; 或从 C++11 开始使用 std::string&amp;&amp; 来延长 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++ 编程语言”第 4th 版中都有介绍!

    在第 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 指向 deallocated 贮存。输出操作cout&lt;&lt;cs 可能会按预期工作,但是 那将是纯粹的运气。编译器可以检测并警告 这个问题的许多变种。 if-statement 的问题有点微妙。这 条件将按预期工作,因为其中的完整表达式 创建的临时持有s2+s3 是条件本身。 但是,该临时文件在受控语句之前被销毁 已输入,因此无法保证对 cs 的任何使用都有效。

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

    【讨论】:

    • 啊,这很有道理。事实上,它总是在一种情况下有效,而在另一种情况下却从来没有真正让我感到震惊
    • @rbennett485 这样的事情几乎总是未定义的行为或放错位置的宏。 :-)
    • 当您说“类似这样的事情”时,您的意思是某个程序有时按预期工作,有时不按预期工作?
    • @rbennett485 是的。未定义的行为使机器可以做任何事情。工作,不工作,段错误等
    • ("Some extra info \n" + errorMessage()) 没有范围,因为它没有名称。不要将作用域(名称的编译时属性)与生存期(对象的运行时属性)混淆。
    【解决方案2】:
    const char* message = ("Some extra info \n" + errorMessage()).c_str();
    cout << message << endl;  
    

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

    【讨论】:

      【解决方案3】:

      问题出在这里:

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

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

      我建议改为这样做:

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

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

      message.c_str();
      

      【讨论】:

        猜你喜欢
        • 2012-10-20
        • 1970-01-01
        • 1970-01-01
        • 2011-08-07
        • 1970-01-01
        • 1970-01-01
        • 2022-01-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多