【问题标题】:Is "char foo = 255" undefined behavior if char is signed?如果 char 已签名,“char foo = 255”是未定义的行为吗?
【发布时间】:2013-09-20 17:31:28
【问题描述】:

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

char foo = 255;

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

警告:隐式常量转换溢出

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

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

【问题讨论】:

  • 某处我读到只有真正的过度 是UB,因此,赋值运算符没有调用未定义的行为,但我不确定。无论如何都要为这个问题 +1。
  • 但是在此之后您是否尝试打印foo?你得到了什么价值?
  • 警告只是让您知道该数字不是您设置的数字,而不是警告任何未定义的行为,但更一般的问题很有趣。我的直觉说它是未定义的(因为foo 已签名,所以它在运行时是未定义的),但我不知道。
  • @Dave 该答案中的哪一行确实说它未定义?
  • @Smac89:它会打印“垃圾”,因为 ASCII 字符中没有这样的符号,但您可以使用这样的值来组成 Unicode 字符

标签: c gcc


【解决方案1】:

总结:结果是实现定义的,很可能是-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的值为SIGFPESIGILLSIGSEGV 或任何其他实现定义的值 对应于计算异常,行为未定义; 否则程序将恢复执行 被打断了。

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

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

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

【讨论】:

  • 所以这个:“char foo = 255”是在 POSIX 系统上定义的实现,但实际上它归结为环绕。对于诸如“unsigned int foo = 9999999999”之类的无符号类型是否相同?我知道它是环绕的,但它是否也定义了实现?还有为什么 gcc 会在 -pedantic 模式下在字符情况下警告“溢出”,如果它不是溢出,这里会发生什么?
  • IMO 将信号设为SIGFPE 实际上一点也不傻 - 虽然大多数现实世界的系统不会发出整数溢出信号,但它们经常这样做提高一个整数除法,他们提高的一个是 SIFGPE。不管名称暗示 floating-point 错误的事实如何。 (当然,整数除法的结果是未定义的行为)
  • @user1042840:转换为无符号类型是由标准定义的;它有效地丢弃了结果的低 N 位以外的所有位(尽管标准根据算术值而不是位来定义结果)。为什么说它不是溢出? (C 标准没有定义该术语。)
  • @Keith Thompson:亚当·罗森菲尔德所说的话向我提出了建议:“此外,这里没有发生溢出——你所拥有的只是一个整数文字(255,int 类型,不会溢出)正在转换为 char 类型”。但是现在我看到您在谈论如何在转换和表达式中处理溢出。
  • @Keith Thompson:总结一下:1. 在转换中,有符号溢出是实现定义的(或在 C99 中引发实现定义的信号),无符号溢出导致回绕。 2. 在运算符中,有符号溢出是未定义的行为(在第 6.5 节第 5 点中描述),无符号行为会导致回绕。我做对了吗?
【解决方案2】:

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

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

【讨论】:

  • 你能告诉我你是如何依赖它的吗?
【解决方案3】:

根据 C11,6.3.1.3:

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

【讨论】:

  • @hacks:你是什么意思? :-)
  • 并且大概程序在收到“实现定义的信号”时的行为是未定义的。 (信号子句是由 C99 添加的;我不知道有任何实际的实现,我怀疑这是一个好主意。)
  • @KeithThompson 在这种情况下,我更高兴,因为我编写的无符号到有符号转换的代码是用 C89 编写的。
  • @Kerre SB:确实如此,但在 3.4.3 的 n1256.pdf 中它说:“未定义行为的一个例子是整数溢出行为”。标准会在同一件事上说两种不同的东西吗?也许我对溢出的理解是不正确的。
  • 但是如果gcc警告“隐式常量转换中的溢出”,我认为这溢出。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-27
  • 1970-01-01
  • 2017-01-18
  • 1970-01-01
  • 2011-04-29
  • 1970-01-01
相关资源
最近更新 更多