我经常发现自己写的代码看起来像这样:

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函数中只有三项检查):

c&#43;&#43; - 如果不太可能发生硬崩溃错误,是否应该使用?-LMLPHP

第一项改进:将其移至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并减少对执行检查的实际代码所生成的指令的影响:
c&#43;&#43; - 如果不太可能发生硬崩溃错误,是否应该使用?-LMLPHP

仍有改进的空间。因为您知道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时,您会看到生成的代码要小得多,并减少了对实际代码生成的指令的影响:

c&#43;&#43; - 如果不太可能发生硬崩溃错误,是否应该使用?-LMLPHP

如您所见,通过这种方式,编译器不需要在调用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);
}

生成的程序集:
c&#43;&#43; - 如果不太可能发生硬崩溃错误,是否应该使用?-LMLPHP

尝试删除NORETURN,或将__attribute__((noreturn))更改为__attribute__((cold)),您将看到completely different generated assembly!
c&#43;&#43; - 如果不太可能发生硬崩溃错误,是否应该使用?-LMLPHP

最后一点(这是明显的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-noreturnwarns about it中受益:

10-08 11:24