这是使用 c_str 异常未定义的行为吗?

Is this use of c_str with exception undefined behavior?

我见过几个类似的代码片段,如下所示:

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;
    }
}

在注释 Is this okay? 之后的行中,我们使用 std::string 读取了在 foo 函数中分配的 c 风格字符串。由于字符串随着堆栈展开而被破坏,这是未定义的行为吗?


如果它确实是未定义的行为,如果我们用这个函数替换 main 函数呢?

int main() {
    foo();
}

因为没有catch,编译器并没有强制展开堆栈,而是在控制台输出what()的结果并中止程序。那么它仍然是未定义的行为吗?

是的,这是未定义的行为。您正在使用悬空指针。

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 yet output the result of what() in the console and abort the program. So is it still undefined behavior?

由于默认实现将使用 std::terminate 并依次调用 std::abort() 这可能仍然是未定义的行为,因为大多数标准处理程序实现将尝试取消引用 what().

您可以安装自己的处理程序来避免这种情况。

您的第一个代码段有未定义的行为。 [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.

这里,析构函数或error被调用,导致c_str()成为悬空指针。稍后取消引用它,例如,当您使用 std::cout 时,是未定义的行为。

你的第二个片段非常好。没有理由认为这是未定义的行为。你永远不会真正调用 what,或做任何其他可能导致未定义行为的事情。标准唯一没有定义的是堆栈展开是否发生,[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.

如其他人所述,代码具有未定义的行为,因为分配给 message 的指针悬空。

std::runtime_error 已经提供了这个问题的解决方案。调用以 std::string 作为输入的构造函数,并且根本不覆盖 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 有一个内部 std::string 其数据默认为 what() returns,从而避免了悬空问题。