在使用Linux上的x86机器和gcc 4.5.2编译时,以下内容没有任何警告:
char foo = 255;
但是当我使用-pedantic
时,gcc会提示:
警告:隐式常量转换中发生溢出
gcc的行为有些奇怪,这让我怀疑自己是否真正理解了此赋值操作的含义。我认为,在POSIX上,如果char
长8位并默认为带符号类型,则不能保存255
。
C标准规定,无符号整数溢出将导致溢出,而有符号整数溢出未定义。那么,这个赋值操作是未定义行为吗?gcc为什么会这样做呢?
摘要:结果是实现定义的,很可能是-1
,但至少在原则上很复杂。
关于溢出的规则对于操作符和转换有所不同,并且对于有符号类型和无符号类型也有所不同 - 并且转换规则在C90和C99之间发生了变化。
自C90开始,使用带有有符号整数操作数的运算符(“溢出”意味着数学结果不能在表达式类型中表示)会产生未定义行为。对于无符号整数操作数,行为被定义为通常的环绕(严格来说,标准不称之为“溢出”)。但您的声明是:
char foo = 255;
如果不使用任何运算符(=
是初始化器而不是赋值),那么在这种情况下,上述都不适用。
如果类型char
可以表示值255
(当且仅当普通的char
是无符号的或者CHAR_BIT >= 9
),那么当然行为是明确定义的。 int
表达式255
被隐式转换为char
。(由于CHAR_BIT >= 8
,因此不能在这种特定情况下触发无符号包装。)
否则,转换会产生一个无法存储在char
中的结果。
从C90开始,转换的结果是实现定义的,这意味着它保证将foo
设置为char
类型范围内的某个值,并且您可以通过阅读实现文档来确定该值,该文档要求告诉您如何进行转换,我从未见过存储的值不是-1
的实现,但原则上可能出现任何结果。
C99改变了定义,因此对带符号类型的溢出转换可以产生实现定义的结果或者引发实现定义的信号。
如果编译器选择做后者,则必须记录引发的信号是哪个。
那么如果引发实现定义的信号会发生什么?标准的第7.14节说:
信号的完整集合、它们的语义和它们的默认处理是实现定义的
“默认处理”信号的可能行为范围并不完全清楚(至少对我来说)。在最坏的情况下,我认为这样的信号可能会终止程序。您也许可以定义一个信号处理程序来捕获信号,也可能不能。
7.14还表示:
如果函数返回时,sig的值为SIGFPE、SIGILL、SIGSEGV 或任何其他对应于计算异常的实现定义值,则行为未定义;否则,程序将在被中断的点继续执行。
但我认为这不适用,因为溢出转换不是此处使用术语的“计算异常”。(除非实现定义的信号恰好是SIGFPE
、SIGILL
或SIGSEGV
——但这很愚蠢。)
因此,如果实现选择在响应溢出转换时引发信号,则行为(而不仅仅是结果)至少是实现定义的,并且在某些情况下可能是未定义的。无论如何,似乎没有任何可移植的方法来处理这样的信号。
实际上,我从未听说过采用C99中新措辞的实现。对于我听说过的所有编译器,转换的结果是实现定义的,并且很可能产生您从2的补码截断所期望的结果。(我完全不相信C99中的这种改变是一个好主意。如果没有其他的话,它使得这个答案需要的长度增加了3倍 。)
SIGFPE
实际上一点也不傻 - 虽然大多数现实世界的系统不会因整数溢出而引发信号,但它们确实经常会因整数除法而引发一个信号,而它们引发的信号就是SIFGPE。无论名称暗示了_浮点_错误与否。(当然,整数除法的结果是未定义行为) - Random832只有在计算算术表达式的中间结果时发生溢出,例如在二进制乘法、一元递减等过程中,带符号整数溢出才会导致未定义行为。如果将浮点值转换为整数类型并且该值超出范围,则会导致未定义行为。
将数字转换成带符号类型时发生溢出不会导致未定义行为。相反,它会产生实现定义的结果(可能会引发实现定义的信号)。
foo
了吗?你得到了什么值? - smac89foo
是有符号的,所以在运行时它将是未定义的),但我不知道。 - Dave