Search code examples
c++exceptionc++14undefined-behaviorstack-unwinding

Is this use of c_str with exception undefined behavior?


I've seen several similar snippets of code that looked like this:

struct MyExcept : std::exception {
    explicit MyExcept(const char* m) noexcept : message{m} {}

    const char* what() const noexcept override {
        return message;
    }

    const char* message;
};

void foo() {
    std::string error;

    error += "Some";
    error += " Error";

    throw MyExcept{error.c_str()};
}

int main() {
    try {
        foo();
    } catch (const MyExcept& e) {
        // Is this okay?
        std::cout << e.message << std::endl;
    }
}

In the line following the comment Is this okay?, we read the c-style string that was allocated in the foo function using std::string. Since the string is destructed with stack unwinding, is this undefined behavior?


If it's indeed undefined behavior, what if we replace the main function with this one?

int main() {
    foo();
}

Since there is no catch, the compiler is not forced to unwind the stack, and yet output the result of what() in the console and abort the program. So is it still undefined behavior?


Solution

  • Your first snippet has undefined behavior. [exception.ctor]/1:

    As control passes from the point where an exception is thrown to a handler, destructors are invoked by a process, specified in this section, called stack unwinding.

    Here, the destructor or error is called, causing the c_str() to become a dangling pointer. Later dereferencing it, when you use std::cout for instance, is undefined behavior.

    Your second snippet is perfectly fine. There is no reason why it would be undefined behavior. You never actually call what, or do anything else that might result in undefined behavior. The only thing not defined by the Standard is if stack unwinding happens or not, [except.terminate]/2:

    In the situation where no matching handler is found, it is implementation-defined whether or not the stack is unwound before std​::​terminate() is called.