39

在使用 Linux 的 x86 机器上使用 gcc 4.5.2 编译时,以下内容不会给我任何警告:

char foo = 255;

但是当我使用时-pedantic,gcc 说:

警告:隐式常量转换溢出

gcc 的行为方式有点奇怪,这让我怀疑我是否真的理解这个作业中发生的事情。我认为如果char在 POSIX 上是 8 位长并且默认签名,它不能保持255.

在 C 标准中,它说无符号整数溢出会导致溢出,但有符号整数溢出是未定义的。那么这个赋值是未定义的行为吗?为什么 gcc 会这样呢?

4

3 回答 3

30

总结:结果是实现定义的,很可能是-1,但它很复杂,至少在原则上是这样。

关于溢出的规则对于操作符和转换以及有符号类型和无符号类型是不同的——并且转换规则在 C90 和 C99 之间发生了变化。

从 C90 开始,带有符号整数操作数的运算符溢出(“溢出”意味着数学结果不能以表达式的类型表示)具有未定义的行为。对于无符号整数操作数,行为被很好地定义为通常的环绕(严格来说,标准并不称其为“溢出”)。但是你的声明:

char foo = 255;

不使用任何运算符( the=是初始化程序,而不是赋值),因此在这种情况下都不适用。

如果 typechar可以表示值255(plain charis unsigned 或 if都为真CHAR_BIT >= 9),那么行为当然是明确定义的。int表达式被255隐式转换为char。(因为CHAR_BIT >= 8,这种特殊情况不可能调用无符号环绕。)

否则,转换会产生无法存储在char.

从 C90 开始,转换的结果是实现定义的——这意味着它保证设置foo为type 范围内的某个char值,您可以通过阅读实现的文档来确定该值是什么,这需要告诉你是如何转换的。(我从未见过存储值不是 的实现-1,但原则上任何结果都是可能的。)

C99 更改了定义,因此到有符号类型的溢出转换要么产生实现定义的结果,要么引发实现定义的信号。

如果编译器选择执行后者,那么它必须记录引发了哪个信号。

那么如果引发了实现定义的信号会发生什么?该标准的第 7.14 节说:

完整的信号集、它们的语义和它们的默认处理是实现定义的

(对我来说)信号的“默认处理”的可能行为范围并不完全清楚。在最坏的情况下,我想这样的信号可能会终止程序。您可能会也可能不会定义捕获信号的信号处理程序。

7.14 还说:

如果并且当函数返回时,如果 sig 的值是SIGFPESIGILLSIGSEGV或与计算异常对应的任何其他实现定义的值,则行为未定义;否则程序将在中断点恢复执行。

但我认为这并不适用,因为溢出转换不是此处使用的术语“计算异常”。(除非实现定义的信号恰好是SIGFPE,SIGILLSIGSEGV-- 但那会很愚蠢)。

因此,最终,如果实现选择引发信号以响应溢出转换,则行为(不仅仅是结果)至少是实现定义的,并且可能存在未定义的情况。无论如何,似乎没有任何可移植的方式来处理这样的信号。

在实践中,我从未听说过利用 C99 中新措辞的实现。对于我听说过的所有编译器,转换的结果是实现定义的——并且很可能会产生您对 2 补码截断的期望。(而且我完全不相信 C99 中的这种变化是一个好主意。如果不出意外,它使这个答案的时间大约是原本需要的时间的 3 倍。)

于 2013-09-20T18:48:53.223 回答
13

有符号整数溢出仅在计算算术表达式的中间结果时才会导致未定义的行为,例如在二进制乘法、一元递减等期间。此外,如果值超出范围,将浮点值转换为整数类型会导致未定义的行为。

溢出的整数转换为有符号类型不会导致未定义的行为。相反,它会产生实现定义的结果(可能会引发实现定义的信号)。

于 2013-09-20T17:38:46.600 回答
9

根据 C11,6.3.1.3:

当一个整数类型的值转换为除 之外的另一种整数类型_Bool时,如果 [...] 新类型是有符号的并且该值不能在其中表示;结果是实现定义的,或者引发了实现定义的信号。

于 2013-09-20T17:37:36.400 回答