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

时间:2016-10-31 11:42:11

标签: c gcc compiler-warnings unsigned signed

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

是否有人知道应该警告这些问题的编译器标志?

为了清楚起见,我不是要问为什么会得到一个无限循环,而是要知道是否有使用编译器或静态分析防止它的方法?< / p>

4 个答案:

答案 0 :(得分:13)

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

gcc中没有可以捕获此标记的标记。

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

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

答案 1 :(得分:7)

isigned char,将其递增到SCHAR_MAX以上,具有实现定义的效果。计算i + 1是在将i升级为int后执行的,并且不会溢出(除非sizeof(int) == 1SCHAR_MAX == INT_MAX)。然而,此值超出了i的范围,并且i具有签名类型,结果是实现定义的,或者引发了实现定义的信号。(C11 6.3.1.3p3有符号和无符号整数)。

根据定义,编译器是实现,因此为每个特定系统和x86架构定义了行为,其中存储值导致屏蔽低位,gcc应该知道循环测试绝对是不变的,使它成为一个无限循环。

请注意,clang也未检测到常量测试,但clang 3.9.0count声明为const,如果i < count确实发出警告与i < 0xff不同,{}替换为gcc

两个编译器都没有抱怨签名/未签名的比较问题,因为在比较之前,两个操作数实际上都被提升为int

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

答案 2 :(得分:3)

由于isigned char,因此其值通常为-128 to 127countunsigned char时,255 (0xFF)的值为i

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

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

编辑: 从GCC文档中,

  

-Wconversion:       警告可能会改变值的隐式转换。

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

答案 3 :(得分:1)

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

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

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

      • 请注意,您的程序“错误地”运行,因为算术比较的结果是预期的结果。
    • 如果这些是int s,那么“保留值”语义将无法实现 - 因为int通常是硬件类型(或至少C是设计的)考虑到这一点,并且很少(如果有的话)架构允许一个操作数被签名而另一个操作数未签名。 如果您将char替换为此int,则编译器发出“已签名和未签名的比较”警告的核心原因。

  2. 对于增量操作,i++

    • 相同的上述相关理论在一些地方提到“无声溢出”是迄今为止最常见和预期的语义。它甚至表示首先导致“C社区缺乏对签名和无符号算术之间差异的敏感性”!所以,这里也没有任何可疑之处。
  3. 最终,造成这种混乱的原因是你对int推广语义的遗忘。这导致代码的行为意外的(不要感觉不好,在阅读其他答案之前我也不知道这个!)。然而,事实证明,对于标准而言,它是非常期待的 - 而且,由它决定的 “无知的法律不是借口”,正如他们所说的那样。