Ответ 1
Должен ли я использовать "если маловероятно" для жестких сбоев?
В таких случаях я предпочел бы переместить код, который выдает автономную функцию extern, которая помечена как 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 (еще 2 команды в случае сборки x64) генерируется больше кода, но общий размер на самом деле меньше, так как сохраненный размер в постоянных строках больше, чем размер дополнительного кода.
Кроме того, обратите внимание, что эти примеры кода хороши для того, чтобы показать, что ваша функция "жесткого сбоя" является внешним. Потребность в 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))
, и вы увидите полностью другую сгенерированную сборку!
В качестве последней точки (что очевидно, ИМО и опущено). Вам необходимо определить
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] {^