【问题标题】:Negative power of 22的负幂
【发布时间】:2017-12-03 00:13:40
【问题描述】:

我正在学习不同数据类型的特征。例如,这个程序越来越多地以四种不同的格式打印 2 的幂:integer, unsigned integer, hexadecimal, octal

#include<stdio.h>
int main(int argc, char *argv[]){
        int i, val = 1;
        for (i = 1; i < 35; ++i) {
                printf("%15d%15u%15x%15o\n", val, val, val, val);
        val *= 2;
        }
    return 0;
}

它有效。 unsigned 上升到 2147483648integer 上升到 -2147483648。但是为什么会变成负数呢?

我有个理论:是不是因为我们在 32 位机器上可以表示的最大有符号整数是2147483647?如果是,为什么返回负数?

【问题讨论】:

  • 你的理论是对的。发生过低
  • 您的程序导致有符号整数溢出,这是未定义的行为
  • 经常,因为 未定义的行为 是未定义的。有符号整数溢出未定义。一个有点可能的结果是卷入负面区域,但这不能保证 -> 您的程序没有定义的行为。
  • 谢谢你们确认我的嫌疑人。您是否建议任何参考资料来帮助我更好地理解诸如此类的未定义行为?
  • Consult the Standard 以便更好地理解 C 的各个方面。Here is a section 总结了未定义的行为。

标签: c types


【解决方案1】:

你描述的是整数溢出导致的UB。由于行为是未定义的,任何事情都可能发生(“当编译器遇到 [一个给定的未定义构造] 时,它让恶魔飞出你的鼻子是合法的”),但是,实际上发生了什么一些机器(我怀疑包括你的)是这样的:

您从int val = 1; 开始。这以二进制形式表示0b00...1。每次val *= 2; 时,该值都会乘以 2,因此表示会更改为0b00...10,然后更改为0b00...100,依此类推(1 位每次移动一步)。最后一次你val *= 2; 你得到0b100...。现在,使用 2 的补码(这是我猜你的机器使用的,因为它很常见)值实际上是 -1 * 0b1000...,即 -2147483648

请注意,即使这可能是您的机器上真正发生的事情,也不应该被信任或认为是发生的“正确”事情,因为如前所述,这是 UB

【讨论】:

  • NMDV 但int 溢出是未定义的行为-UB。这个答案似乎解释了为什么发生了一些事情,就好像那是指定的和预期的行为一样——事实并非如此。这只是 UB 的常见结果,明天可能会有所不同。
  • 确实 UB 可以预测,就像天气一样,它可能是错误的。关键是,即使结果与我们今天的预测相符,但未来的结果并不可靠——因此是 UB。对于学习者来说,灌输关于 UB 的一致性意识会导致依赖于这一点的弱程序。
  • 关闭。最后一行应为:“该值实际上是 -1 * 0b100000...,即 -2147483648”,因为 2 的补码中的否定计算为 -a == ~a + 1
  • @chux 我同意你的观点,任何 UB 都应该在本网站上明确说明,因为我们的读者都不应该依赖任何实际上是 UB 的行为。然而,UB 是抽象泄漏的地方,解释为什么特定的 UB 构造会产生特定的行为对于那些还没有冒险寻找抽象背后的人来说可能是非常有见地的。因此,我发现像这样的答案很有帮助。
  • @chux 啊,我明白了。
【解决方案2】:

首先,你应该明白这个程序是未定义的。它会导致有符号整数溢出,这在 C 标准中被声明为 undefined

技术原因是无法预测任何行为,因为负数允许使用不同的表示形式,并且表示形式中甚至可能存在 填充位

您在案例中看到负数的最可能原因是您的机器使用 2 的补码(查找)来表示负数,而算术运算在没有溢出检查的位上运行。所以最高位是符号位,如果你的值溢出到这个位,就会变成负数。

【讨论】:

  • 非常感谢 Felix 的简洁解释!
  • “您看到负数的原因”具有误导性。 1) 如果使用了罕见的非 2 补码机,也可能出现负数。 2) 2 的补码想法确实提供了一个潜在的解释,为什么看到特定值 -2147483648 而不是其他值,3) 无论如何,这都是 UB,即使机器是 2 的补码。
  • @chux 在第一段中解释了它 undefined 的事实,我认为最后一段的措辞很明显,2 的补码是 one i> 实际环绕的可能(并且很可能)原因?您会在措辞上进行哪些更改以使其更清楚?
  • 最后说“你看到负数的原因是你的机器可能使用了 2 的补码......”是最薄弱的部分。某些负数的原因不是 2 的补码。
  • 另一个更重要的原因是一些架构对有符号整数使用饱和算法,即结果被限制在最大值/最小值,因此永远不会下溢/溢出。这是典型的,例如工具链或程序代码明确定义此行为的 DSP 可以通过设置机器标志来强制执行部分代码。
【解决方案3】:

在这个程序中,val 的值会溢出,如果它是一个 32 位的机器,因为整数的大小是 4 个字节。现在,我们在数学中有两种类型的值,正数和负数,所以要进行涉及负数结果的计算,我们使用符号表示,即 C 语言中的 intchar

我们以 char 为例,范围 -128 到 127,unsigned char 范围 0-255 。 它告诉我们,范围分为两部分进行有符号表示。所以对于任何有符号的变量,如果它越过它的 +ve 值范围,它就会变成负值。就像这里 char 的情况一样,当值超过 127 时,它变成 -ve。假设如果你将 300 添加到任何 charunsigned char 变量会发生什么,它会翻转并从零重新开始。

char a=2;
a+=300;

什么是价值?现在你知道char 的最大值是 255(总共 256 个值,包括零),所以 300-256 = 44 + 2 =46。
希望这会有所帮助

【讨论】:

  • "在这个程序中,val 值会溢出" - 没有。 “如果它是 32 位机器,因为整数的大小是 4 个字节” - 整数可以有任何大小,具体取决于类型。并且机器的宽度与整数类型的宽度没有直接关系。例如,68K 被认为是 32 位架构,但使用 16 位和 32 位 int,具体取决于 ABI。 “ char,范围 -128 到 127” - 不保证,对于大多数机器来说不正确。等等。你的回答充满了错误的假设。并且char c = 127; c + 1; 不会溢出,即使是CHAR_MAX == 127(这意味着签名char)。
  • 您能否提供任何参考,以便我澄清这一点。
  • C. ISO9899:2011 只有一个权威参考。
猜你喜欢
  • 2011-12-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-21
  • 1970-01-01
  • 1970-01-01
  • 2015-09-06
相关资源
最近更新 更多