我看过几个类似的代码片段,如下所示:
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
的结果并中止程序。那还是未定义的行为吗?
答案 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()
默认返回,从而避免了悬空问题。