我经常发现自己写的代码看起来像这样:
if(a == nullptr) throw std::runtime_error("error at " __FILE__ ":" S__LINE__);
我是否应该使用
if unlikely
处理错误?if unlikely(a == nullptr) throw std::runtime_error("error at " __FILE__ ":" S__LINE__);
编译器会自动推断出应缓存代码的哪一部分,或者这实际上是有用的事情吗?为什么我看不到有很多人在处理这样的错误?
最佳答案
对于这种情况,我更喜欢将代码移到标记为noreturn
的独立extern函数中。这样,您的实际代码就不会被大量与异常相关的代码(或任何您的“硬崩溃”代码)“污染”。与已接受的答案相反,您不需要将其标记为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__));
}
编译器将生成lots of instructions来处理构造并引发此异常。您还介绍了对
std::runtime_error
的依赖。 Check out how generated code will look like(如果您在test
函数中只有三项检查):第一项改进:将其移至a standalone function:
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__));
}
这样,您可以避免在函数内部生成所有与异常相关的代码。立即生成的指令become simpler and cleaner并减少对执行检查的实际代码所生成的指令的影响:
仍有改进的空间。因为您知道
my_runtime_error
不会返回,所以应该让编译器知道它,以便它wouldn't need to preserve registers before calling 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);
...
当您在代码中use it multiple times时,您会看到生成的代码要小得多,并减少了对实际代码生成的指令的影响:
如您所见,通过这种方式,编译器不需要在调用
my_runtime_error
之前保留寄存器。我还建议不要将带有
__FILE__
和__LINE__
的错误字符串连接到整体错误消息字符串中。 Pass them as standalone parameters,只需创建一个宏即可将它们传递!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条指令),但是总的大小实际上较小,因为在常量字符串上保存的大小远大于额外的代码大小。
另外,请注意,这些代码示例很好地显示了使“硬崩溃”函数成为外部函数的好处。在实际代码for example中,对
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))
,您将看到completely different generated assembly!最后一点(这是明显的IMO,已被省略)。您需要定义您的
某些cpp文件中的
my_runtime_error
函数。由于它将仅是一个副本,因此您可以在该函数中放入所需的任何代码。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
和warns about it中受益: