Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)

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?

like image 626
rbennett485 Avatar asked Dec 01 '22 13:12

rbennett485


2 Answers

("Some extra info \n" + errorMessage()) is a temporary 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. 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&  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.


BTW, this exact issue is even covered in "The C++ Programming Language" 4th Edition!

In §10.3.4 "Temporary Objects", Mr Stroustrup writes:

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.

So, do not worry about your C++ skills. Even the C++ bible explains it. ;-)

like image 173
cadaniluk Avatar answered Dec 09 '22 22:12

cadaniluk


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

errorMessage() returns a temporary std::string object
Concatenating with "Some extra info \n" + errorMessage() creates another temporary object.
Taking c_str of it returns a pointer to its internal buffer (not a copy).
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.

like image 28
deviantfan Avatar answered Dec 09 '22 22:12

deviantfan