比较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++
.
如果您想迭代到某个值,请确保您使用的类型可以表示该值。
由于 i
是 signed 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) == 1
和 SCHAR_MAX == INT_MAX
)。然而这个值超出了 i
的范围,并且由于 i
有一个带符号的类型,结果要么是实现定义的,要么是一个实现定义的信号被引发。(C11 6.3.1.3p3 有符号和无符号整数)。
根据定义,编译器是实现,因此行为是为每个特定系统和 x86 体系结构定义的,在 x86 体系结构中存储值会导致屏蔽低位,gcc
应该知道loop test肯定是常量,无限循环。
请注意,clang
也不会检测常量测试,但如果 count
声明为 const
,clang 3.9.0
会检测,并且如果 i < count
替换为 i < 0xff
,与 gcc
不同。
两个编译器都没有抱怨有符号/无符号比较问题,因为两个操作数实际上在比较之前都提升为 int
。
您在这里发现了一个有意义的问题,特别重要,因为一些编码约定坚持对所有变量使用尽可能小的类型,从而导致诸如 int8_t
或 uint8_t
循环索引变量之类的奇怪现象。这样的选择确实容易出错,我还没有找到一种方法让编译器警告程序员一些愚蠢的错误,比如你发布的错误。
没有警告捕捉这个 因为从编译器的角度来看这段代码没有任何问题。
正如其他答案所指出的,比较线被有效地视为
for (i=0; (int)i<(int)count; i++)
如 Rationale for International Standard—Programming Languages—C 第 6.3.1.1 节所述,他们在比较值时选择 "value preserving" 方法进行隐式类型转换,因为它不太可能出现意外的比较结果。
- 请注意,您的程序运行 "incorrectly" 特别是因为 算术比较的结果 是预期的结果。
如果这些是 int
s,那么 "value preserving" 语义将不可能实现 - 因为 int
通常是硬件类型(或在至少,C 在设计时就考虑到了这一点),并且很少有(如果有的话)架构允许一个操作数被签名而另一个操作数不被签名。 如果您在此处将 char
s 替换为 int
s,这就是编译器发出 "comparison between signed and unsigned" 警告 的核心原因。
自增运算,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",正如他们所说。
以下面的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++
.
如果您想迭代到某个值,请确保您使用的类型可以表示该值。
由于 i
是 signed 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) == 1
和 SCHAR_MAX == INT_MAX
)。然而这个值超出了 i
的范围,并且由于 i
有一个带符号的类型,结果要么是实现定义的,要么是一个实现定义的信号被引发。(C11 6.3.1.3p3 有符号和无符号整数)。
根据定义,编译器是实现,因此行为是为每个特定系统和 x86 体系结构定义的,在 x86 体系结构中存储值会导致屏蔽低位,gcc
应该知道loop test肯定是常量,无限循环。
请注意,clang
也不会检测常量测试,但如果 count
声明为 const
,clang 3.9.0
会检测,并且如果 i < count
替换为 i < 0xff
,与 gcc
不同。
两个编译器都没有抱怨有符号/无符号比较问题,因为两个操作数实际上在比较之前都提升为 int
。
您在这里发现了一个有意义的问题,特别重要,因为一些编码约定坚持对所有变量使用尽可能小的类型,从而导致诸如 int8_t
或 uint8_t
循环索引变量之类的奇怪现象。这样的选择确实容易出错,我还没有找到一种方法让编译器警告程序员一些愚蠢的错误,比如你发布的错误。
没有警告捕捉这个 因为从编译器的角度来看这段代码没有任何问题。
正如其他答案所指出的,比较线被有效地视为
for (i=0; (int)i<(int)count; i++)
如 Rationale for International Standard—Programming Languages—C 第 6.3.1.1 节所述,他们在比较值时选择 "value preserving" 方法进行隐式类型转换,因为它不太可能出现意外的比较结果。
- 请注意,您的程序运行 "incorrectly" 特别是因为 算术比较的结果 是预期的结果。
如果这些是
int
s,那么 "value preserving" 语义将不可能实现 - 因为int
通常是硬件类型(或在至少,C 在设计时就考虑到了这一点),并且很少有(如果有的话)架构允许一个操作数被签名而另一个操作数不被签名。 如果您在此处将char
s 替换为int
s,这就是编译器发出 "comparison between signed and unsigned" 警告 的核心原因。
自增运算,
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",正如他们所说。