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();
我有一个方法,其中 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 byc_str()
was allocated as part of the temporary object holdings1+s2
, and that storage is not guaranteed to exist after that temporary is destroyed. Consequently,cs
points to deallocated storage. The output operationcout<<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 theif
-statement is a bit more subtle. The condition will work as expected because the full expression in which the temporary holdings2+s3
is created is the condition itself. However, that temporary is destroyed before the controlled statement is entered, so any use ofcs
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();