在使用Linux的x86机器上使用gcc 4.5.2编译时,以下内容没有给我任何警告:
char foo = 255;
但是当我使用-pedantic
时,gcc说:
警告:隐式常量转换溢出
gcc行事的方式有点奇怪,这让我怀疑我是否真的明白这项任务中发生了什么。我认为如果char
在POSIX上有8位长并且默认情况下已签名,则无法保留255
。
在C标准中,它表示无符号整数溢出导致溢出,但未定义有符号整数溢出。这个赋值是不确定的行为?为什么gcc这样做?
答案 0 :(得分:30)
摘要:结果是实现定义的,很可能是-1
,但它很复杂,至少在原则上是这样。
关于溢出的规则对于运算符与转换以及签名类型与无符号类型的不同而不同 - 转换规则在C90和C99之间更改。
从C90开始,带有符号整数操作数的运算符溢出(“溢出”意味着数学结果无法在表达式的类型中表示)具有未定义的行为。对于无符号整数操作数,行为被定义为通常的回绕(严格来说,标准不称之为“溢出”)。但是你的宣言:
char foo = 255;
不使用任何运算符(=
是初始值设定项,而不是赋值),因此在这种情况下都不适用。
如果类型char
可以表示值255
(无论是char
是无符号还是CHAR_BIT >= 9
都是如此),那么当然行为定义明确。 int
表达式255
隐式转换为char
。 (从CHAR_BIT >= 8
开始,这个特殊情况不可能调用unsigned wraparound。)
否则,转换产生的结果无法存储在char
。
从C90开始,转换的结果是实现定义的 - 这意味着它可以保证在foo
类型的范围内将char
设置为某些值,您可以通过阅读实现文档来确定该值是什么,这需要告诉您转换的工作原理。 (我从未见过存储值不是-1
的实现,但原则上任何结果都是可能的。)
C99更改了定义,因此溢出转换为签名类型 会产生实现定义的结果或会引发实现定义的信号。
如果编译器选择执行后者,则必须记录引发的信号。
那么如果引发实现定义的信号会发生什么?该标准的第7.14节说:
完整的信号集,它们的语义和默认值 处理是实现定义的
(对我来说)信号“默认处理”的可能行为范围并不完全清楚。在最坏的情况下,我想这样的信号可以终止该程序。您可能会或可能不会能够定义捕获信号的信号处理程序。
7.14也说:如果函数返回,如果sig的值为 SIGFPE , SIGILL , SIGSEGV 或任何其他实现定义的值 对应于计算异常,行为未定义; 否则程序将恢复执行 中断。
但我不认为适用,因为溢出转换不是“计算异常”,因为这里使用的是术语。 (除非实现定义的信号恰好是SIGFPE
,SIGILL
或SIGSEGV
- 但这很愚蠢。)
因此,最终,如果实现选择响应溢出转换而引发信号,则行为(不仅仅是结果)至少是实现定义的,并且可能存在未定义的情况。无论如何,似乎没有任何便携式方式来处理这样的信号。
在实践中,我从未听说过利用C99中新措辞的实现。对于我听说过的所有编译器,转换的结果是实现定义的 - 并且很可能产生你对2的补码截断所期望的结果。 (而且我完全不相信C99中的这种变化是一个好主意。如果没有别的话,它的答案大约是原本需要的3倍。)
答案 1 :(得分:13)
有符号整数溢出仅在评估算术表达式的中间结果时发生未定义的行为,例如:在二进制多重,一元递减等过程中。如果值超出范围,将浮点值转换为整数类型会导致未定义的行为。
溢出的整数转换为签名类型不会导致未定义的行为。相反,它产生实现定义的结果(可能会引发实现定义的信号)。
答案 2 :(得分:9)
根据C11,6.3.1.3:
当整数类型的值转换为
_Bool
以外的另一个整数类型时,如果[...]新类型已签名且值无法在其中表示;无论是 结果是实现定义的或引发实现定义的信号。