Memory Error with std:ostringstream and -std=c++11

2020-02-15 06:37发布

问题:

EDIT: Thanks to everyone who pointed out the problem, and that it was discussed on Stack Overflow. I cast the last close vote myself.

A related question: neither CPP Reference on ostringstream or ostringstream::str state its a temporary. How did so many people know? Or is there different documentation I should have consulted?


I'm having a lot of trouble with memory errors under Debian 7.3 (x64) with GCC 4.7.2, -std=c++11 and std::ostringstream. Its leading to bizaare results like https://stackoverflow.com/questions/21260815/which-r-in-this-create-table-error-message.

The full blown program uses Sqlite. Running a reduced case from the command line vs. Valgrind prints 2 different errors. The entire reduced case program is available at Code Viewer (I thought it was kind of long to post the entire sample here). All it does is initializes Sqlite, opens a database, creates a table, closes the database, and unitializes the database. And it reports errors if they occur. There's nothing else occurring.

Here's part of the reduced case program that simply tries to create a table. If the table exists, it should produce an error (which it does):

ostringstream qs;
qs.str().reserve(96);

qs << "CREATE TABLE test";
qs << "(";
qs << "  userid INTEGER PRIMARY KEY AUTOINCREMENT,";
qs << "  username TEXT,";
qs << "  salt BLOB,";
qs << "  hmac BLOB";
qs << ");";

const char* stmt = qs.str().c_str();
AC_ASSERT(NULL != stmt);

rc = sqlite3_exec(db, stmt, NULL, NULL, &err);
AC_ASSERT(rc == SQLITE_OK);

if(rc != SQLITE_OK)
{
    ostringstream oss;
    oss.str().reserve(96);

    oss << "sqlite3_exec failed, error " << rc;
    LogError(oss.str().c_str());

    oss.clear(), oss.str("");
    oss << "Sqlite error: " << err;
    LogError(oss.str().c_str());

    // Break, handle error
}

However, under command line, the message is:

sqlite3_exec failed, error 1
Sqlite error: table 

Under Valgrind, the message is:

sqlite3_exec failed, error 1
Sqlite error: table test already exists

The program makes heavy use of ostringstream, and Valgrind produces nearly 13 issues centered around them, 9 of which include operator delete(void*) on the underlying basic_string. For example, one is shown below (and line 76 from t.cpp is const char* stmt = qs.str().c_str();):

==14318== Invalid read of size 1
==14318==    at 0x45ACC8: sqlite3_exec (sqlite3.c:94542)
==14318==    by 0x405D07: main (t.cpp:79)
==14318==  Address 0x5d89728 is 24 bytes inside a block of size 127 free'd
==14318==    at 0x4C27870: operator delete(void*) (vg_replace_malloc.c:502)
==14318==    by 0x530EB1F: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.17)
==14318==    by 0x405CF1: main (t.cpp:76)

Does anyone have any ideas of what's going on here? Is it ostringstream? Or perhaps GCC 4.7.2? Or maybe Debian's port?

(And sorry for the open ended question. I've run out of things to do).

回答1:

const char* stmt = qs.str().c_str();

That extracts a temporary string from qs, takes a pointer to its content, then destroys the temporary leaving the pointer dangling. Using the pointer after this will give undefined behaviour.

To fix it, you could either assign the result of str() to a variable, so that it's no longer temporary, or use this expression as the argument to sqlite3_exec, so that the temporary survives until after that function call. (In the second case, you'd have to remove the first assertion; but that assertion is rather pointless anyway).



回答2:

Take a closer look at this line: const char* stmt = qs.str().c_str();

qs.str() provides you a temporary std::string object from which you take a pointer to inner char* array. Once this line completes its execution, your pointer is no longer valid and something else may (and probably is) stored there.

Try doing this instead:

std::string strstmt(qs.str());
const char* stmt = strstmt.c_str();