比较char和unsigned char时如何启用编译器警告?

How to enable compiler warning when comparing char and unsigned char?

以下面的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

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

澄清一下,我不是在问 'why is get an infinite loop',而是想知道是否有办法使用编译器或静态分析来阻止它?

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

gcc 中没有可以捕捉到这个的标志。

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

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

由于 isigned char,其值通常在 -128 to 127 之间。而 count 作为 unsigned char 被分配值 255 (0xFF).

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

发生这种情况是因为数据类型溢出,必须小心谨慎地检查可能发生自动类型强制转换的表达式,因为它们不会发出任何警告。

编辑: 来自 GCC 文档,

-Wconversion: Warn for implicit conversions that may alter a value.

在这里,由于比较而不是赋值,我们变得不一致。

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

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

请注意,clang 也不会检测常量测试,但如果 count 声明为 constclang 3.9.0 会检测,并且如果 i < count 替换为 i < 0xff,与 gcc 不同。

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

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

没有警告捕捉这个 因为从编译器的角度来看这段代码没有任何问题。

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

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

      • 请注意,您的程序运行 "incorrectly" 特别是因为 算术比较的结果 是预期的结果。
    • 如果这些是 ints,那么 "value preserving" 语义将不可能实现 - 因为 int 通常是硬件类型(或在至少,C 在设计时就考虑到了这一点),并且很少有(如果有的话)架构允许一个操作数被签名而另一个操作数不被签名。 如果您在此处将 chars 替换为 ints,这就是编译器发出 "comparison between signed and unsigned" 警告 的核心原因。

  2. 自增运算,i++,

    • 上面链接的基本原理在几个地方提到 "silent overflow" 是迄今为止最常见和预期的语义。它甚至 说首先导致了 "a lack of sensitivity in the C community to the differences between signed and unsigned arithmetic"! 所以,这里也没有什么可疑的。

最终,造成这种混淆的原因是您对 int 促销语义的遗忘。这导致代码的行为出乎你的意料(不要难过,我在阅读其他答案之前也不知道这一点!)。然而,事实证明,完全符合标准的预期 - 此外,由它决定。 "Ignorance of the law is no excuse",正如他们所说。