我应该使用“如果不太可能”来解决硬崩溃错误吗?
对于这样的情况,我更喜欢将抛出的代码移动到标记为noreturn
. 这样,您的实际代码不会被大量与异常相关的代码(或任何您的“硬崩溃”代码)“污染”。与接受的答案相反,您不需要将其标记为cold
,但您确实需要noreturn
让编译器不要尝试生成代码来保留寄存器或任何状态,并且基本上假设在去那里之后没有办法回来。
例如,如果您以这种方式编写代码:
#include <stdexcept>
#define _STR(x) #x
#define STR(x) _STR(x)
void test(const char* a)
{
if(a == nullptr)
throw std::runtime_error("error at " __FILE__ ":" STR(__LINE__));
}
编译器将生成大量指令来处理构造和抛出此异常。您还引入了对std::runtime_error
. 如果您的函数中只有三个类似的检查,请查看生成的代码的外观test
:
第一个改进:将其移至独立功能:
void my_runtime_error(const char* message);
#define _STR(x) #x
#define STR(x) _STR(x)
void test(const char* a)
{
if (a == nullptr)
my_runtime_error("error at " __FILE__ ":" STR(__LINE__));
}
这样您就可以避免在函数中生成所有与异常相关的代码。立即生成的指令变得更简单、更清晰,并减少对执行检查的实际代码生成的指令的影响:
仍有改进的余地。既然你知道你my_runtime_error
不会返回,你应该让编译器知道它,这样它就不需要在调用之前保留寄存器my_runtime_error
:
#if defined(_MSC_VER)
#define NORETURN __declspec(noreturn)
#else
#define NORETURN __attribute__((__noreturn__))
#endif
void NORETURN my_runtime_error(const char* message);
...
当您在代码中多次使用它时,您会看到生成的代码要小得多,并减少对实际代码生成的指令的影响:
如您所见,这种方式编译器在调用您的my_runtime_error
.
我还建议不要将错误字符串与__FILE__
整体__LINE__
错误消息字符串连接起来。将它们作为独立参数传递,并简单地创建一个宏来传递它们!
void NORETURN my_runtime_error(const char* message, const char* file, int line);
#define MY_ERROR(msg) my_runtime_error(msg, __FILE__, __LINE__)
void test(const char* a)
{
if (a == nullptr)
MY_ERROR("error");
if (a[0] == 'a')
MY_ERROR("first letter is 'a'");
if (a[0] == 'b')
MY_ERROR("first letter is 'b'");
}
似乎每个 my_runtime_error 调用都会生成更多代码(在 x64 构建的情况下多 2 条指令),但总大小实际上更小,因为常量字符串上保存的大小远大于额外代码大小。
另外,请注意,这些代码示例有助于展示将“硬崩溃”函数设为外部函数的好处。需要noreturn
在实际代码中变得更加明显,例如:
#include <math.h>
#if defined(_MSC_VER)
#define NORETURN __declspec(noreturn)
#else
#define NORETURN __attribute__((noreturn))
#endif
void NORETURN my_runtime_error(const char* message, const char* file, int line);
#define MY_ERROR(msg) my_runtime_error(msg, __FILE__, __LINE__)
double test(double x)
{
int i = floor(x);
if (i < 10)
MY_ERROR("error!");
return 1.0*sqrt(i);
}
生成的程序集:
尝试删除NORETURN
或更改__attribute__((noreturn))
为__attribute__((cold))
,您将看到完全不同的生成程序集!
作为最后一点(这很明显是 IMO 并且被省略了)。你需要
my_runtime_error
在一些 cpp 文件中定义你的函数。由于它只会是一份副本,因此您可以在此函数中放置您想要的任何代码。
void NORETURN my_runtime_error(const char* message, const char* file, int line)
{
// you can log the message over network,
// save it to a file and finally you can throw it an error:
std::string msg = message;
msg += " at ";
msg += file;
msg += ":";
msg += std::to_string(line);
throw std::runtime_error(msg);
}
还有一点:clang 实际上认识到,如果启用了警告,这种类型的功能会从中受益noreturn
并发出-Wmissing-noreturn
警告:
警告:函数 'my_runtime_error' 可以用属性 'noreturn' [-Wmissing-noreturn] { ^