Как выдать предупреждение, если возвращаемое значение игнорируется?
-
19-09-2019 - |
Вопрос
Я хотел бы увидеть все места в моем коде (C++), которые игнорируют возвращаемое значение функции.Как я могу это сделать - с помощью gcc или инструмента статического анализа кода?
Пример плохого кода:
int f(int z) {
return z + (z*2) + z/3 + z*z + 23;
}
int main()
{
int i = 7;
f(i); ///// <<----- here I disregard the return value
return 1;
}
Обратите внимание:
- это должно работать, даже если функция и ее использование находятся в разных файлах
- бесплатно инструмент статической проверки
Решение
Вы хотите GCC warn_unused_result
атрибут:
#define WARN_UNUSED __attribute__((warn_unused_result))
int WARN_UNUSED f(int z) {
return z + (z*2) + z/3 + z*z + 23;
}
int main()
{
int i = 7;
f(i); ///// <<----- here i disregard the return value
return 1;
}
Попытка скомпилировать этот код дает:
$ gcc test.c
test.c: In function `main':
test.c:16: warning: ignoring return value of `f', declared with
attribute warn_unused_result
Вы можете увидеть это в использовании в Ядро Linux;у них есть __must_check
макрос, который делает то же самое;похоже, вам нужен GCC 3.4 или более поздняя версия, чтобы это работало.Затем вы обнаружите этот макрос, используемый в файлах заголовков ядра:
unsigned long __must_check copy_to_user(void __user *to,
const void *from, unsigned long n);
Другие советы
Насколько мне известно, у GCC нет возможности выдать это предупреждение.Однако, если вас интересуют конкретные функции, вы можете пометить их атрибутом:
int fn() __attribute__((warn_unused_result));
что выдаст предупреждение, если возвращаемое значение fn() не будет использовано.Предостережение:Сам я этой функцией никогда не пользовался.
Вы можете использовать этот удобный шаблон, чтобы сделать это во время выполнения.
Вместо возврата кода ошибки (например.HRESULT) вы возвращаете return_code<HRESULT>, который подтверждает, выходит ли он за пределы области действия без считывания значения.Это не инструмент статического анализа, но тем не менее он полезен.
class return_value
{
public:
explicit return_value(T value)
:value(value), checked(false)
{
}
return_value(const return_value& other)
:value(other.value), checked(other.checked)
{
other.checked = true;
}
return_value& operator=(const return_value& other)
{
if( this != &other )
{
assert(checked);
value = other.value;
checked = other.checked;
other.checked = true;
}
}
~return_value(const return_value& other)
{
assert(checked);
}
T get_value()const {
checked = true;
return value;
}
private:
mutable bool checked;
T value;
};
Для C++17 ответ на этот вопрос меняется, поскольку теперь у нас есть [[ноотбросить]] атрибут.Покрыт [dcl.attr.nodiscard]:
Nodiscard атрибута-токена может быть применен к идентификатору-декларатору в объявлении функции или к объявлению класса или перечисления.Он должен появляться не более одного раза в каждом списке атрибутов, и никакое предложение аргумента атрибута не должно присутствовать.
и
[ Пример:
struct [[nodiscard]] error_info { /* ... */ }; error_info enable_missile_safety_mode(); void launch_missiles(); void test_missiles() { enable_missile_safety_mode(); // warning encouraged launch_missiles(); } error_info &foo(); void f() { foo(); } // warning not encouraged: not a nodiscard call, because neither // the (reference) return type nor the function is declared nodiscard
— конец примера ]
Итак, изменив ваш пример (увидеть это вживую):
[[nodiscard]] int f(int z) {
return z + (z*2) + z/3 + z*z + 23;
}
int main()
{
int i = 7;
f(i); // now we obtain a diagnostic
return 1;
}
Теперь мы получаем диагностику как с помощью gcc, так и с clang, например.
warning: ignoring return value of function declared with 'nodiscard' attribute [-Wunused-result]
f(i); // now we obtain a diagnostic
^ ~
Любой код статического анализа (например. ПК-Линт) должен быть в состоянии сказать вам это.Что касается PC-Lint, я знаю, что это так.
статический анализатор сделает всю работу за вас, но если ваша кодовая база более чем тривиальна, приготовьтесь к перегрузке ;-)
Статический анализатор будет здесь лучшим выбором.Здесь мы используем Coverity, но есть бесплатные инструменты доступен, который вы также можете использовать.
Если вам нужно быстрое решение и у вас есть под рукой оболочка в стиле Linux, вы можете попробовать что-то вроде:
grep -rn "function_name" * | grep -v "="
Это позволит найти каждую строку, которая ссылается на указанную функцию, но не содержит «=".Вы можете получить много ложноположительных результатов (и, возможно, несколько ложноотрицательных результатов), но если у вас нет статического анализатора, это хорошее место для начала.
Классическая программа «lint» раньше очень многословно рассказывала о функциях, возвращавших значение, которое игнорировалось.Проблема заключалась в том, что многие из этих предупреждений были нежелательными, что приводило к чрезмерному шуму на выходе ворса (он собирал кусочки пуха, которые вы хотели игнорировать).Вероятно, поэтому у GCC нет стандартного предупреждения об этом.
Другая проблема (обратная сторона) заключается в том, «как подавить предупреждение, если вы знаете, что игнорируете результат, но на самом деле вас это не волнует».Классический сценарий для этого:
if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
signal(SIGHUP, sighandler);
Вам важен первый результат от signal()
;вы знаете, что вторым будет SIG_IGN (поскольку вы только что установили его).Чтобы уйти от предупреждений, я иногда использую какой-нибудь вариант:
if ((old = signal(SIGHUP, SIG_IGN)) != SIG_IGN)
old = signal(SIGHUP, sighandler);
Это присваивает old
оба раза.Вы можете следить за этим с помощью «assert(old == SIG_IGN)».