在使用Linux的x86机器上使用gcc 4.5.2编译时,以下内容不会给我任何警告:
char foo = 255;
但当我使用 -pedantic
时,gcc说:
警告:隐式常量转换溢出
gcc行为的方式有点奇怪,这让我怀疑我是否真的理解这项任务中发生了什么 . 我认为如果 char
在POSIX上是8位长并且's signed by default, it can' t保持 255
.
在C标准中,它表示无符号整数溢出导致溢出,但未定义有符号整数溢出 . 这个赋值是不确定的行为?为什么gcc这样做?
3 回答
根据C11,6.3.1.3:
简介:结果是实现定义的,很可能是
-1
,但它很复杂,至少在原则上是这样 .关于溢出的规则对于运算符与转换以及签名类型与无符号类型有所不同 - 转换规则在C90和C99之间更改 .
从C90开始,带有符号整数操作数的运算符溢出(“溢出”意味着数学结果无法在表达式的类型中表示)具有未定义的行为 . 对于无符号整数操作数,行为被定义为通常的回绕(严格来说,标准不称之为“溢出”) . 但你的声明:
不使用任何运算符(
=
是初始化程序,而不是赋值),因此在这种情况下都不适用 .如果类型
char
可以表示值255
(无论是普通char
是无符号还是CHAR_BIT >= 9
都是如此),那么当然行为定义得很好 .int
表达式255
隐式转换为char
. (从CHAR_BIT >= 8
开始,这个特殊情况不可能调用unsigned wraparound . )否则,转换会产生无法存储在
char
中的结果 .从C90开始,转换的结果是实现定义的 - 这意味着它保证将
foo
设置为char
类型范围内的某个值,并且您可以通过读取实现's documentation, which is required to tell you how the conversion works. (I'来确定该值是什么 . 实现,其中存储的值不是-1
,但原则上任何结果都是可能的 . )C99更改了定义,因此溢出转换为有符号类型会产生实现定义的结果或引发实现定义的信号 .
如果编译器选择执行后者,那么它必须记录引发的信号 .
那么如果引发实现定义的信号会发生什么?该标准的第7.14节说:
(对我而言)"default handling"信号的可能行为范围并不完全清楚 . 在最坏的情况下,我想这样的信号可以终止该程序 . 您可能会也可能无法定义捕获信号的信号处理程序 .
7.14还说:
但我不认为这适用,因为溢出的转换不是"computational exception",因为这里使用的术语 . (除非实现定义的信号恰好是
SIGFPE
,SIGILL
或SIGSEGV
- 但这很愚蠢) .因此,最终,如果实现选择响应溢出转换而引发信号,则行为(不仅仅是结果)至少是实现定义的,并且可能存在未定义的情况 . 在任何情况下,似乎没有任何可移植的方式来处理这样的信号 .
在实践中,我从未听说过利用C99中新措辞的实现 . 对于我听说过的所有编译器,转换的结果都是实现定义的 - 很可能产生你对2的补码截断所期望的结果 . (而且我完全不相信C99中的这种变化是一个好主意 . 如果不出意外,它的答案大约是原本需要的3倍 . )
有符号整数溢出只有在评估算术表达式的中间结果时才会出现未定义的行为,例如:在二元多重复制期间,一元此外,如果值超出范围,则将浮点值转换为整数类型会导致未定义的行为 .
对signed类型的溢出整数转换不会导致未定义的行为 . 相反,它产生实现定义的结果(可能会产生实现定义的信号) .