64 Intermediate The Hundred
The C String From a Dead String
the greeting that only works for people with short names
struct User {
std::string first;
std::string last;
std::string getName() const { return first + " " + last; }
};
void greet(const char* name) { std::printf("Welcome, %s!\n", name); }
int main() {
User intern{"Ada", "King"};
User founder{"Augusta Ada", "Byron King, Countess of Lovelace"};
const char* name = intern.getName().c_str();
greet(name);
name = founder.getName().c_str();
greet(name);
}
Run it. Who gets greeted?
Answer
Only the intern. Typical output (GCC 11.5 on x86-64, glibc 2.34): Welcome, Ada King!, then
Welcome, plus two junk bytes and a ! — different bytes nearly every run, occasionally a
newline that splits the line. Undefined behavior, including the line that worked.
Why¶
c_str() is the border crossing into C, and it hands you the type C uses for every string: a
bare const char* — no length, no ownership, nothing that whispers borrowed. So it looks as
storable as an int, and you park it in a variable or a struct field before feeding it to
printf, open, strcmp. Its lifetime is really the string's, and that string is a temporary
here — getName() returns by value — dead at the closing semicolon, so every later read is
undefined behavior. The founder's 44-character name sat on the heap, and freeing it let
glibc write a mangled free-list pointer over the first bytes (the junk); "Ada King" is 8
characters, short enough for the small-string optimization to keep it inside the temporary
itself (libstdc++ inlines up to 15 — implementation-defined), where the dead bytes still read
correctly.
GCC 11.5 and 13.3 stay silent even with -Wall -Wextra; Clang 18 needs no flags — object
backing the pointer will be destroyed at the end of the full-expression [-Wdangling-gsl] —
but flags only the initialization, never the identical assignment below. -fsanitize=address
aborts on the first greet with stack-use-after-scope: that line was never safe either.
The fix¶
std::string name = founder.getName(); // the characters are yours now
greet(name.c_str()); // valid for as long as `name` is
Give the characters a named owner and cross into C at the point of use. Handing one straight
over, greet(founder.getName().c_str()), is fine too — the temporary outlives the call.
Entry 65 is the same wound from std::string_view, which at least admits that it borrows.
Takeaway: c_str() lends you a pointer on the string's lifetime — const char* never says so.
Try it: g++ -std=c++17 main.cpp -o demo && ./demo