In that example we can just quote the standard:
12.2 Temporary objects [class.temporary]
Temporary objects are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created. This is true even if that evaluation ends in throwing an exception. The value computations and side effects of destroying a temporary object are associated only with the full-expression, not with any specific subexpression.
That is after the semicolon of your line:
const char * s = (s1+s2).c_str(); // <- Here
So here:
printf("%s\n",s); // This line will now cause undefined behaviour.
Why? Because as your object is destructed, you don't know anymore what is at this place now...
The bad thing here is that, with Undefined behaviour, your program may seem to work at the first time, but... It will crash for sure at the worst time...
You can do:
printf( "%s\n", (s1+s2).c_str() );
It will work because the object is not destructed yet (remember, after the semicolon...).