如果char被签名,"char foo = 255"是否定义了未定义的行为?

use*_*840 39 c gcc

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

char foo = 255;
Run Code Online (Sandbox Code Playgroud)

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

警告:隐式常量转换溢出

gcc行为的方式有点奇怪,这让我怀疑我是否真的理解这项任务中发生了什么.我认为如果charPOSIX上有8位长并且默认情况下是签名,则无法保持255.

在C标准中,它表示无符号整数溢出导致溢出,但未定义有符号整数溢出.这个赋值是不确定的行为?为什么gcc这样做?

Kei*_*son 30

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

关于溢出的规则对于运算符与转换以及签名类型与无符号类型有所不同 - 转换规则在C90和C99之间更改.

从C90开始,带有符号整数操作数的运算符溢出("溢出"意味着数学结果无法在表达式的类型中表示)具有未定义的行为.对于无符号整数操作数,行为被定义为通常的回绕(严格来说,标准不称之为"溢出").但你的声明:

char foo = 255;
Run Code Online (Sandbox Code Playgroud)

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

如果type char可以表示值255(无论是plain char是无符号还是无符号CHAR_BIT >= 9),那么当然行为定义得很好.的int表达255被隐式转换为char.(因为CHAR_BIT >= 8,这种特殊情况不可能调用未签名的环绕.)

否则,转换产生的结果无法存储在a中char.

从C90开始,转换的结果是实现定义的 - 这意味着它保证在类型范围内设置foo某个char,并且您可以通过阅读实现的文档来确定该值是什么,这需要告诉它你如何转换工作.(我从来没有见过一个实现,其中存储的值是除了之外的任何东西-1,但原则上任何结果都是可能的.)

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

如果编译器选择执行后者,那么它必须记录引发的信号.

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

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

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

7.14还说:

如果函数返回,如果sig的值是SIGFPE, SIGILL,SIGSEGV或对应于计算异常的任何其他实现定义的值,则行为是未定义的; 否则程序将在被中断时恢复执行.

但我不认为这适用,因为溢出转换不是这里使用的术语的"计算异常".(除非实现定义的信号恰好是SIGFPE,SIGILL或者SIGSEGV-但是这将是愚蠢的).

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

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


AnT*_*AnT 13

有符号整数溢出仅在评估算术表达式的中间结果时发生,例如在二进制多重复制,一元递减等过程中发生时,会导致未定义的行为.此外,如果值超出范围,则将浮点值转换为整数类型会导致未定义的行为.

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

  • 你能告诉我你是如何依赖它的吗? (2认同)

Ker*_* SB 9

根据C11,6.3.1.3:

当具有整数类型的值转换为另一个整数类型时_Bool,如果[...]新类型已签名且值无法在其中表示; 结果是实现定义的,或者引发实现定义的信号.

  • 并且可能是程序在收到"实现定义的信号"时的行为是未定义的.(信号条款是由C99添加的;我不知道任何实际的实现,我怀疑这是一个好主意.) (4认同)
  • @haccks:你的意思是什么?:-) (2认同)
  • @KerrekSB Awww,好的忍者编辑抢救! (2认同)