【问题标题】:How to enable compiler warning when comparing char and unsigned char?比较 char 和 unsigned char 时如何启用编译器警告?
【发布时间】:2017-03-13 11:53:06
【问题描述】:

以下面的 C 代码为例:

int main(int argc, char *argv[])
{
    signed char i;
    unsigned char count = 0xFF;

    for (i=0; i<count;i++)
    {
        printf("%x\n", i);
    }
    return 0;
}

这段代码在无限循环中运行,即使我编译如下:

# gcc -Wall -Wpedantic -Wconversion -Wsign-compare -Wtype-limits -Wsign-conversion test.c -o test

有人知道应该警告此类问题的编译器标志吗?

为了清楚起见,我不是在问“为什么会出现无限循环”,而是想知道是否有办法使用编译器或静态分析来防止它?

【问题讨论】:

  • -W 选项不只是警告吗?您的代码将永远运行,因为有符号字符在回绕(或提交一些未定义的行为)之前不能超过 127,因此它不能超过您的循环终止条件。
  • 对我来说似乎很像一个家庭作业问题。这么小的程序中不必要的类型不匹配?完全使用signed char?
  • 也许 lint 会检测到它。
  • 签名字符总是小于 255。因此它不会退出循环。
  • 除了现有答案外,还有一个注意事项:确实,您无法检测有符号整数溢出,但使用 gcc,您可以使用 -fwrapv 至少 避免UB (但是除非编译器将其用于某些优化,否则您的代码仍然被破坏,这可以通过-wstrict-overflow 检测到。简而言之:如果您知道它,您可以检测到它(许多assign-with-overflow -检查那里的实现)但是AFAIK如果你做错了那么你就被咬了

标签: c gcc compiler-warnings unsigned signed


【解决方案1】:

标志 -Wconversion 不会捕获错误,因为比较中的两个操作数:i&lt;count,使用整数提升提升为 int。

gcc 中没有标记可以捕捉到这一点。

除此之外,您的代码的行为是未定义的,因为变量 i 溢出,当它具有值 0x7F 并递增:i++。

如果您想迭代到某个值,请确保您使用的类型可以表示该值。

【讨论】:

  • 我知道变量 i 溢出了,这正是我想要实现的。我不是在问“为什么”它在无限循环中运行,以及如何解决它。我想知道编译器是否有办法捕捉到这个问题??
  • 感谢@2501,我浏览了 gcc 手册页中的所有警告标志,但找不到。
  • @sagi 你可以试试第三方静态分析器。
  • clang-analyzer 和上面的 gcc 命令没有找到任何东西
  • @usr:虽然它本身是正确的,但编译器警告确实会在这里和那里指出未定义的行为,在这种情况下,警告将是最有帮助的。
【解决方案2】:

由于i 是signed char,其值范围通常为-128 to 127。而 count 是 unsigned char 的值是 255 (0xFF)。

在循环内部,当i 值到达127 并再次递增时,它永远不会到达128 并到达-128,然后再次到达127,然后再次滚动到@987654332 @, 等等。 i 的值将永远小于循环内count 的值,因此循环永远不会终止!

发生这种情况是因为数据类型溢出,因此必须仔细检查可能发生自动类型强制的表达式,因为它们不会发出任何警告。

编辑: 来自 GCC 文档,

-W转换: 对可能改变值的隐式转换发出警告。

在这里,由于比较而不是分配,我们得到了不一致。

【讨论】:

  • 我知道变量 i 溢出了,这正是我想要实现的。我不是在问“为什么”它在无限循环中运行,以及如何解决它。我想知道编译器是否有办法捕捉到这个问题??
  • 值得一提:由于有符号字符的所有值都不能用无符号字符表示(反之亦然),所以比较运算符的两个操作数都转换为下一个可以容纳的 int 类别他们都是int。保证该转换的符号保留,因此最终将 -128..+127 与 255 进行比较,这将始终为真。
  • @sagi:由于for循环条件部分的类型转换将较窄的类型(有符号字符)提升为较宽的类型(无符号字符),因此编译器不会报告此转换。
  • 其实比较起来,两者都会转换成int,但是在这种情况下,这是微不足道的。
  • 如果这个答案对任何人来说似乎不正确或不合适,请在投反对票的同时发表评论!
【解决方案3】:

i 是一个signed char,将其递增到SCHAR_MAX 之外具有实现定义的效果。计算i + 1 是在将i 提升为int 之后执行的,它不会溢出(除非sizeof(int) == 1 和SCHAR_MAX == INT_MAX)。然而,这个值超出了i 的范围,并且由于i 具有带符号的类型,结果要么是实现定义的,要么会引发实现定义的信号。(C11 6.3.1.3p3有符号和无符号整数)。

根据定义,编译器是实现,因此行为是针对每个特定系统和 x86 体系结构定义的,其中存储值会导致屏蔽低位,gcc 应该知道循环测试肯定是常数,使其成为无限循环。

请注意,clang 也不会检测到常量测试,但如果 count 被声明为 const,clang 3.9.0 会检测到,并且如果将 i &lt; count 替换为 i &lt; 0xff,它会发出警告,不像gcc。

两个编译器都没有抱怨有符号/无符号比较问题,因为在比较之前两个操作数实际上都被提升为int。

您在这里发现了一个有意义的问题,尤其重要,因为某些编码约定坚持对所有变量使用尽可能小的类型,从而导致诸如int8_t 或uint8_t 循环索引变量之类的奇怪现象。这样的选择确实容易出错,我还没有找到一种方法让编译器警告程序员你发布的愚蠢错误。

【讨论】:

  • 您似乎在谈论加法 i+1,但 OP 使用的是不进行整数提升的后缀 ++ 运算符。会发生溢出和 ub。
  • @2501:我在 C11 标准中寻找相关语言来支持这一点,但找不到任何结论性的东西。您能否详细说明为什么您认为i++ 的行为应该不同于i += 1 和/或i = i + 1?
  • 您假设后缀 ++ 的行为与 += 相同。它没有。前缀 ++ 之所以这样做,是因为它被明确提及。
  • @2501:我为此提出了一个单独的问题。答案值得从标准中得到准确的参考。
【解决方案4】:

没有警告可以捕捉到这一点因为从编译器的角度来看,这段代码没有任何问题。

  1. 正如在其他答案中指出的那样,比较线被有效地视为

    for (i=0; (int)i<(int)count; i++)
    
    • 如Rationale for International Standard—Programming Languages—C 第 6.3.1.1 节所述,他们在比较值时为隐式类型转换选择了“值保留”方法,因为它不太可能出现意外比较结果。

      • 请注意,您的程序运行“不正确”具体是因为算术比较的结果是预期的结果。
    • 1234563 ),并且很少有(如果有的话)架构允许一个操作数有符号而另一个无符号。 如果您在此处将chars 替换为ints,这就是编译器发出“有符号和无符号之间的比较”警告的核心原因。
  2. 对于增量操作,i++,

    • 上面链接的基本原理在一些地方提到,“无声溢出”是迄今为止最常见和预期的语义。它甚至一开始就表示“C 社区对有符号和无符号算术之间的差异缺乏敏感性”! 所以,这里也没有什么可疑之处。

归根结底,造成这种混乱的原因是您忘记了int 促销语义。这导致代码的行为出乎您的意料(不要难过,在阅读其他答案之前我也不知道这一点!)。然而,事实证明,这是对标准的期待——而且,由它决定。正如他们所说,“对法律的无知不是借口”。

【讨论】:

    猜你喜欢
    • 2016-05-29
    • 2012-01-25
    • 2011-07-02
    • 2013-06-15
    • 2011-04-29
    • 1970-01-01
    • 2022-12-15
    • 2011-04-13
    • 1970-01-01
    相关资源
    最近更新 更多