是否使用c_str具有异常未定义的行为?

时间:2017-07-20 16:11:12

标签: c++ exception c++14 undefined-behavior stack-unwinding

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

anActivity

在评论aNEWActivty之后的行中,我们使用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?函数中分配的c样式字符串。由于字符串是通过堆栈展开来破坏的,这是不确定的行为吗?

如果它确实是未定义的行为,如果我们用这个替换foo函数怎么办?

std::string

由于没有catch,编译器不会被强制解除堆栈,而是在控制台中输出main的结果并中止程序。那还是未定义的行为吗?

3 个答案:

答案 0 :(得分:5)

是的,这是未定义的行为。你正在使用一个悬垂的指针。

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()
  

由于没有catch,编译器不会被强制解除堆栈,而是在控制台中输出what()的结果并中止程序。那还是未定义的行为吗?

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

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

答案 1 :(得分:2)

您的第一个代码段有未定义的行为。 [exception.ctor]/1

  

当控件从抛出异常的位置传递给处理程序时,析构函数由本节中指定的进程调用,称为堆栈展开。

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

你的第二个片段非常好。没有理由说它是未定义的行为。你永远不会实际调用what,或做任何可能导致未定义行为的事情。标准没有定义的唯一事情是堆栈展开是否发生,[except.terminate]/2

  

在没有找到匹配处理程序的情况下,无论是否在调用std​::​terminate()之前展开堆栈,都是实现定义的。

答案 2 :(得分:1)

正如其他人所述,代码未定义,因为分配给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()默认返回,从而避免了悬空问题。