Is this the use of c_str with undefined exception behavior?

I saw several similar code snippets 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;
    }
}

      

On the line following the comment Is this okay?

, we read the c style line that was highlighted in the function foo

using std::string

. Since the string is destroyed by stack erase is this behavior undefined?


If this is indeed undefined behavior, what if we replace the function main

with this?

int main() {
    foo();
}

      

Since there is no catch, the compiler is not forced to unwind the stack and still prints the result what()

to the console and aborts the program. Is this the behavior of undefined?

+3


source to share


3 answers


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

:

When control returns from the point at which the exception is dispatched to the handler, the destructors are invoked by a process named in this section called stack unpacking.

Here, the destructor or is called error

, resulting in c_str()

a dangling pointer. Dereferencing it later when you use std::cout

, for example, undefined behavior.



Your second snippet is great. There is no reason why there would be undefined behavior. You never call what

or do anything else that could lead to undefined behavior. The only thing that is not defined by the Standard is whether the stack unwinds or not [except.terminate]/2

:

In a situation where no match handler is found, it is implementation-defined whether or not the stack is unplugged before being called std​::​terminate()

.

+2


source


Yes, this behavior is undefined. You are working with a dangling pointer.

void foo() {
    std::string error;

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

    throw MyExcept{error.c_str()};
} // <<  error goes out of scope here and so does the pointer returned
  //     from c_str()

      




Since there is no catch, the compiler is not forced to unwind the stack and still prints the result what()

to the console and aborts the program. Is this the behavior of undefined?

Since the default implementation will use std::terminate

and in turn call std::abort()

, this may be undefined behavior, because most standard handler implementations will try to dereference what()

.

You can install your own handlers to avoid this.

+5


source


As pointed out in other words, the code is undefined, since the pointer assigned to message

remains dangling.

std::runtime_error

already provides a solution to this problem. Call its constructor, which takes input std::string

and doesn't override what()

:

struct MyExcept : std::runtime_error {
    explicit MyExcept(const std::string & m) noexcept : std::runtime_error(m) {}
};

void foo() {
    std::string error;

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

    throw MyExcept(error);
}

int main() {
    try {
        foo();
    }
    catch (const MyExcept& e) {
        std::cout << e.what() << std::endl;
    }
}

      

std::runtime_error

has an internal one std::string

, the data of which is what()

returned by default, thus avoiding the problem with dangling messages.

+1


source







All Articles