【问题标题】:How to raise warning if return value is disregarded?如果忽略返回值,如何发出警告?
【发布时间】:2011-01-03 19:23:06
【问题描述】:

我想查看我的代码 (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;
}

请注意:

  • 即使函数及其使用在不同的文件中,它也应该可以工作
  • 免费静态检查工具

【问题讨论】:

  • 如果你使用printf,这会打印很多警告。
  • 无法通过命令行执行它的“可能”原因是,如果您有正当理由忽略结果,那么您最终需要分配一个“未使用的变量”会产生警告。例如,您当然不希望T&amp; operator=(T rhs); 强迫您捕获结果;)
  • @Matthieu M:当你想忽略一个函数的返回值时,你不需要分配一个虚拟变量。只需将函数调用转换为 void,如(void) function_returning_a_val();。阅读代码时,这也更清楚地表明您是故意忽略返回值。
  • [[nodiscard]] 在 C++17 中。
  • 由于这看起来是关于这个主题的最古老和最受好评的问题,我添加了一个涵盖 C++17 的更新答案。

标签: c++ c gcc static-analysis


【解决方案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 kernel 中看到它的使用情况;他们有一个做同样事情的__must_check 宏;看起来你需要 GCC 3.4 或更高版本才能工作。然后你会发现内核头文件中使用了那个宏:

unsigned long __must_check copy_to_user(void __user *to,
                                        const void *from, unsigned long n);

【讨论】:

  • 在我的头文件中工作正常。我将带有 WARN_UNUSED 的函数原型放在一个新文件 lib.h 中,然后从 test.c 中包含它并得到相同的警告。另请注意,这就是 Linux 内核的工作方式。
  • 更多关于这个属性的细节可以在官方 gcc 文档中找到。 6.33.1 Common Function Attributes --------------------------------- 。安装 gcc-doc apt 包以便在 gcc 文档中轻松导航。拥有它后,只需使用 info 阅读 $ info gcc 并在索引中搜索 warn_unused_result
【解决方案2】:

对于 C++17,这个问题的答案发生了变化,因为我们现在有了 [[nodiscard]] 属性。覆盖在[dcl.attr.nodiscard]

attribute-token nodiscard 可以应用于函数声明中的 declarator-id 或类或枚举的声明。它在每个属性列表中最多出现一次,并且不应出现任何属性参数子句。

[ 示例:

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

 — 结束示例 ]

所以修改你的例子(see it live):

[[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
  ^ ~

【讨论】:

    【解决方案3】:

    据我所知,没有 GCC 选项可以发出此警告。但是,如果您对特定功能感兴趣,可以使用属性对其进行标记:

    int fn() __attribute__((warn_unused_result));
    

    如果未使用 fn() 的返回值,则会发出警告。警告:我自己从未使用过此功能。

    【讨论】:

      【解决方案4】:

      您可以使用这个方便的模板在运行时执行此操作。

      您不会返回错误代码(例如 HRESULT),而是返回一个 return_code,它会断言它是否超出范围而没有读取值。它不是一个静态分析工具,但它仍然很有用。

      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;
      };
      

      【讨论】:

      • 这是个好主意。不幸的是,它违反了“析构函数不得抛出”原则:parashift.com/c++-faq-lite/exceptions.html#faq-17.3,但对于“检查所有返回”测试模式来说,这可能是可以接受的(在最终版本构建中可能不需要检查)。跨度>
      • 我见过使用错误代码的最高位而不是 bool 的实现。
      【解决方案5】:

      任何静态分析代码(例如PC-Lint)都应该能够告诉您这一点。对于 PC-Lint,我知道是这样的。

      【讨论】:

        【解决方案6】:

        静态分析器会为您完成这项工作,但如果您的代码库过于繁琐,那就准备不堪重负;-)

        【讨论】:

          【解决方案7】:

          静态分析器将是您最好的选择。我们在这里使用 Coverity,但您也可以使用 free tools

          如果您需要一个快速而简单的解决方案,并且手边有一个 Linux 风格的 shell,您可以尝试以下方法:

          grep -rn "function_name" * | grep -v "="
          

          这将找到引用指定函数但不包含“=”的每一行。您可能会得到很多误报(可能还有一些误报),但如果您没有静态分析器,那么这是一个不错的起点。

          【讨论】:

            【解决方案8】:

            经典的 'lint' 程序曾经对返回被忽略的值的函数滔滔不绝。问题是,其中许多警告是不需要的——导致 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)' 来跟进。

            【讨论】:

            • 实际上,说“不,我真的不关心返回值”的典型方式是转换为void,例如(void)printf("Hello, world!\n"); 即使在最高警告级别,也不会发出任何警告,显式强制转换为 void。
            • 是的,'(void)' 演员表可以工作……但在代码中看起来很难看。而且我必须照顾用'VOID'代替那个丑陋的语法的代码;它映射到使用标准 C 编译器(现在所有 C 编译器)强制转换的“(void)”,但起源于它们成为标准之前的日子,然后将结果分配给文件静态变量(“#define VOID _void_ =”或附近)。啊啊;回忆,以及必须跳过的铁环。
            猜你喜欢
            • 1970-01-01
            • 2011-02-21
            • 2018-12-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-01-24
            • 1970-01-01
            相关资源
            最近更新 更多