在 C 和 C++ 中比较不同大小的无符号整数时如何收到警告?

nh2*_*nh2 37 c c++ gcc clang compiler-warnings

C 或 C++ 中错误的常见来源是这样的代码:

size_t n = // ...

for (unsigned int i = 0; i < n; i++) // ...
Run Code Online (Sandbox Code Playgroud)

当溢出时可以无限循环unsigned int

例如,在 Linux 上,unsigned int是 32 位,而size_t是 64 位,因此 if n = 5000000000,我们会得到一个无限循环。

我如何使用 GCC 或 Clang 获得有关此问题的警告?

GCC不这样做:-Wall -Wextra

#include <stdint.h>

void f(uint64_t n)
{
    for (uint32_t i = 0; i < n; ++i) {
    }
}
Run Code Online (Sandbox Code Playgroud)
gcc-13 -std=c17 \
       -Wall -Wextra -Wpedantic \
       -Warray-bounds -Wconversion \
       -fanalyzer \
       -c -o 76840686.o 76840686.c
Run Code Online (Sandbox Code Playgroud)

(无输出)


  • 我正在寻找一种不需要n成为编译时常量的解决方案。
  • 理想情况下,该解决方案适用于现有的 C/C++ 项目,而无需完全重写它们。
  • 建议除编译器警告之外的其他工具也很有用,但编译器警告本身会更好

编辑:上游编译器功能请求

答案表明当前编译器中不存在此类警告。我已经开始提交上游功能请求:

Sco*_*eak 24

似乎没有内置警告选项gccclang执行所要求的操作。不过,我们可以用 clang-query 它来代替。

下面的clang-query命令将报告 32 位和 64 位整数的比较,假设int是 32 位和 long64 位。(下面有更多相关内容。)

#!/bin/sh

PATH=$HOME/opt/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04/bin:$PATH

# In this query, the comments are ignored because clang-query (not the
# shell) recognizes and discards them.
query='m
  binaryOperator(                            # Find a binary operator expression
    anyOf(                                   #  such that any of:
      hasOperatorName("<"),                  #   is operator <, or
      hasOperatorName("<="),                 #   is operator <=, or
      hasOperatorName(">"),                  #   is operator >, or
      hasOperatorName(">="),                 #   is operator >=, or
      hasOperatorName("=="),                 #   is operator ==, or
      hasOperatorName("!=")                  #   is operator !=;
    ),

    hasEitherOperand(                        #  and where either operand
      implicitCastExpr(                      #   is an implicit cast
        has(                                 #    from
          expr(                              #     an expression
            hasType(                         #      whose type
              hasCanonicalType(              #       after resolving typedefs
                anyOf(                       #        is either
                  asString("int"),           #         int or
                  asString("unsigned int")   #         unsigned int,
                )
              )
            ),
            unless(                          #      unless that expression
              integerLiteral()               #       is an integer literal,
            )
          )
        ),
        hasImplicitDestinationType(          #    and to a type
          hasCanonicalType(                  #     that after typedefs
            anyOf(                           #      is either
              asString("long"),              #       long or
              asString("unsigned long")      #       unsigned long.
            )
          )
        )
      ).bind("operand")
    )
  )
'

# Run the query on test.c.
clang-query \
  -c="set bind-root false" \
  -c="$query" \
  test.c -- -w

# EOF
Run Code Online (Sandbox Code Playgroud)

当在以下情况下运行时,test.c它会报告所有指定的情况:

// test.c
// Demonstrate reporting comparisons of different-size operands.

#include <stddef.h>          // size_t
#include <stdint.h>          // int32_t, etc.

void test(int32_t i32, int64_t i64, uint32_t u32, uint64_t u64)
{
  i32 < i32;                 // Not reported: same sizes.
  i32 < i64;                 // reported
  i64 < i64;

  u32 < u32;
  u32 < u64;                 // reported
  u64 < u64;

  i32 < u64;                 // reported
  u32 < i64;                 // reported

  i32 <= i64;                // reported

  i64 > i32;                 // reported
  i64 >= i32;                // reported

  i32 == i64;                // reported
  u64 != u32;                // reported

  i32 + i64;                 // Not reported: not a comparison operator.

  ((int64_t)i32) < i64;      // Not reported: explicit cast.

  u64 < 3;                   // Not reported: comparison with integer literal.

  // Example #1 in question.
  size_t n = 0;
  for (unsigned int i = 0; i < n; i++) {}        // reported
}

// Example #2 in question.
void f(uint64_t n)
{
  for (uint32_t i = 0; i < n; ++i) {             // reported
  }
}

// EOF
Run Code Online (Sandbox Code Playgroud)

有关该命令的一些详细信息clang-query

  • 该命令传递-wclang-query以抑制其他警告。那只是因为我编写测试的方式会引发有关未使用值的警告,而对于正常代码来说这是不必要的。

  • 它通过了set bind-root false,因此唯一报告的站点是感兴趣的操作数,而不是报告整个表达式。

  • 不幸的是,不可能让查询也打印所涉及类型的名称。尝试使用绑定来执行此操作会导致clang-query抱怨“Matcher 不支持绑定”。

该查询令人不满意的方面是它显式列出了源类型和目标类型。不幸的是,clang-query没有匹配器来报告任何 32 位类型,因此必须单独列出它们。您可能想[unsigned] long long在目标端添加。[unsigned] long如果使用针对 Windows 等 IL32 平台的编译器选项运行此代码,您可能还需要删除。

相关地,请注意clang-query接受 后的编译器选项--,或者在 compile_commands.json 文件中接受编译器选项。不幸的是,没有专门的clang-query 命令行文档,甚至它也--help没有提到--命令行选项。我可以链接的最好的内容是 libtooling 的文档,因为clang-query在内部使用该库进行命令行处理。

最后,我要指出的是,我没有在实际代码上对此查询进行任何“调整”。它可能会产生大量噪音,需要进一步调整。有关如何使用 的教程,我推荐 Stephen Kelly 撰写的clang-query博客文章《 探索 Clang 工具第 2 部分:使用 clang-query 检查 Clang AST 》。还有 AST Matcher Reference,但那里的文档非常简洁。


brh*_*ans 9

这并没有直接回答问题(提供警告),但是您会考虑一种完全避免该问题的替代方案吗?

    size_t n = // ...

    for (typeof(n) i = 0; i < n; i++) // ...
Run Code Online (Sandbox Code Playgroud)

现在 n 是什么类型并不重要,因为它i总是与 相同的类型,因此您永远不会遇到因类型较小或范围小于 而n导致的无限循环问题。in

  • `typeof` 是编译器特定的,但 `decltype` 应该在任何地方都可以工作。另外,“for(auto i = n - n; ...)”应该可以工作。 (8认同)
  • @SimonRichter 请注意,[`typeof`](https://www.iso-9899.info/n3047.html#6.7.2.5) 显然将包含在 C23 中。在 C++ 中,是的,有 `decltype`。 (2认同)

Mic*_*tin 8

PVS Studio 可以发出此类警告(以及更多警告),这里是他们文档中几乎相同的示例:

https://pvs-studio.com/en/docs/warnings/v104/

它是一个付费工具,但他们为开源项目提供免费许可。

我在Clang-tidy中没有找到这样的警告,这是一个来自 LLVM 项目的免费 linter 工具,但是添加对不同大小的整数的比较的检查会非常简单(Scott McPeak 后来用出色的 clang-query 进行了回复)大部分工作 - 剩余部分只是将此查询插入 clang-tidy)。但这会是非常吵闹的检查。人们可以通过限制对循环条件的检查来限制噪音,这也可以使用 Clang-tidy 来完成,但使用 AST 匹配器需要做更多的工作。


chq*_*lie 5

gcc 的最新版本似乎支持 -Warith-conversion此目的:

-Warith-conversion

即使将操作数转换为相同类型无法更改其值,也要警告算术运算的隐式转换。这会影响来自-Wconversion-Wfloat-conversion和 的警告-Wsign-conversion

void f (char c, int i)
{
    c = c + i; // warns with -Wconversion
    c = c + 1; // only warns with -Warith-conversion
}
Run Code Online (Sandbox Code Playgroud)

但它不适用于您的示例,可能是因为i < n不是算术表达式。对于通用二进制表达式,此警告似乎没有变体。