Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does c_str() return the same value for two different strings?

Tags:

c++

string

Given a simple file loading function,

std::string load_file(const std::string &filename) {
    std::ifstream     file(filename);
    std::string       line;
    std::stringstream stream;
    while (std::getline(file, line)) {
        stream << line << "\n";
    }
    return stream.str();
}

Why does the following print the contents of another_file twice?

const char *some_file = load_file("some_file").c_str();
const char *another_file = load_file("another_file").c_str();
printf("%s", some_file);
printf("%s", another_file);
like image 937
nucleartide Avatar asked Dec 02 '22 20:12

nucleartide


2 Answers

The code is broken. You are calling c_str() on a temporary object that is immediately destroyed. Which means that the values returned by c_str() are invalid.

You need to make sure that the std::string objects returned survive at least as long as you hold on to the pointer returned by the call to c_str(). For example:

std::string some_file = load_file("some_file");
std::string another_file = load_file("another_file");
printf("%s", some_file.c_str());
printf("%s", another_file.c_str());
like image 117
David Heffernan Avatar answered May 18 '23 15:05

David Heffernan


In a line like this:

const char *some_file = load_file("some_file").c_str();

load_file() returns a temporary std::string, and then .c_str() is called on this temporary.

When the temporary is alive, the pointer returned by .c_str() points to some meaningful string. But when the temporary "evaporates" (at the semicolon), then that same pointer is pointing to garbage.

The "garbage" may be the same string that the previous call to load_file() returned, so you have the effect that both raw pointers point to the same string. But this is just a coincidence.
And your code has a bug.

String classes like std::string were invented as a convenient way to simplify the C++ programmer's life instead of using raw C string pointers. So, just use std::strings if you want to safely manage strings in C++.

Consider using .c_str() just at the boundary with C functions (including printf()).

So, you can refactor your code like this:

// load_file() returns a std::string, so just keep using std::string.
// Note that returning std::string is efficient thanks to RVO/NRVO 
// and C++11 move semantics. 
std::string some_file = load_file("some_file");

// Idem for this:
std::string another_file = load_file("another_file");

// Convert from std::string to raw C string pointers at the C boundary
printf("%s\n", some_file.c_str());
printf("%s\n", another_file.c_str());

Even some code like this would work fine:

printf("%s\n", load_file("some_file").c_str());
printf("%s\n", load_file("another_file").c_str());

In fact, note that in this case, even if you are using a temporary (i.e. the strings returned by load_file() are not copied to named std::string variables), the temporary is valid during the printf() call, so the raw pointer returned by .c_str() points to a valid string while printf() is doing its printing job.

like image 38
Mr.C64 Avatar answered May 18 '23 15:05

Mr.C64