在C

时间:2016-12-17 21:06:12

标签: c undefined-behavior unsigned twos-complement

例如,无符号字符如何从-128+127获取值?根据我的理解,最重要的位用于表示数字的符号,而字符的剩余位用于表示数字的大小。现在,7位的最大可能幅度为127,因此范围不应该从-127+127?如何-128成为结果?

其次,以下行为背后的位级逻辑是什么

#include <stdio.h>

int main()
{
    signed char x = 127;
    x += 1;
    printf("%i", x);
}

输出:

-128

有人可以看到,x变为-128,但为什么呢?这种行为背后的算法是什么?

2 个答案:

答案 0 :(得分:5)

这基于名为Two's Complement的东西。这里的想法是,给定一些二进制数,它的两个补码将是它的一个补码(翻转所有位)加一个。我们可以看到一个简单的例子,让我们找到13的两个补码,我们可以将其写成0b0110101101 (flip) -> 10010 (+1) --> 10011

现在,虽然如果我们像往常一样将其解释为二进制数,我们会以十进制读取19,但我们必须知道该数字是用二进制补码写的,以便反转过程并得到前一个数字13。因此,我们可以看到我们已经表示+13 = 01101-13 = 10011这样的事情,请注意正数以0开头,并且与1对称。使用此表示时,这将是常量,正数始终以0开头,而负数以1开头。值得注意的是,我将0作为13原始表示的前缀,这是为了正确表示它的两个补码所需要的。您可以尝试通过相同的示例而不执行此操作并验证它是否必要。

现在,我们来看看这样的几个值,

╔══════╦════════════════╦════════════════════════╗
║ Bits ║ Unsigned Value ║ Two's Complement Value ║
╠══════╬════════════════╬════════════════════════╣
║ 011  ║ 3              ║ 3                      ║
╠══════╬════════════════╬════════════════════════╣
║ 010  ║ 2              ║ 2                      ║
╠══════╬════════════════╬════════════════════════╣
║ 001  ║ 1              ║ 1                      ║
╠══════╬════════════════╬════════════════════════╣
║ 000  ║ 0              ║ 0                      ║
╠══════╬════════════════╬════════════════════════╣
║ 111  ║ 7              ║ -1                     ║
╠══════╬════════════════╬════════════════════════╣
║ 110  ║ 6              ║ -2                     ║
╠══════╬════════════════╬════════════════════════╣
║ 101  ║ 5              ║ -3                     ║
╠══════╬════════════════╬════════════════════════╣
║ 100  ║ 4              ║ -4                     ║
╚══════╩════════════════╩════════════════════════╝

正如您所看到的,它的工作方式与我们之前的预期相同,但您现在可以开始了解您发现的“错误”。 Two's Complement中4位表示的上限是十进制值3。让我们看看如何通过向-4添加1来达到3 = 0b011。因此3+1 = 0b100 -4,您可以从表格中看到,在二人补语中映射到4(而不是127 = 0b01111111 127 + 1 = 0b10000000 )。你的情况是这个确切的问题,但有更多的位。这样的有符号表示是循环的,因此在顶部溢出会产生最低值。让我们来看看你的案例

1

正如你所看到的那样,它以$vehicles = array(); foreach ($data as $val) { $temp = array( 'license_plate' => $val->license_plate, 'created_at' => $now, 'updated_at' => $now, 'state_id' => $activeState, 'type_id' => $typeId, ); array_push($vehicles, $temp); } 开头,因此它是负数(!),如果你解决了Two's Complement,你会看到它代表-128(因为下限总是大于上限) )。

不是每个硬件都会以同样的方式实现,Intel,AMD,ARM,据我所知,通用CPU的所有主要架构都在其ALU中使用Two补码,但有硬件使用用于实现整数签名的其他技术,因此从根本上说,您描述的行为是未定义的。需要注意的另一个有趣的事情是IEEE's standard for floating point arithmetic,实现基于exponent bias的签名浮点数。

最后,既然我们在这里谈论C,请注意编译器可以优化未定义的行为,this blog post中描述了这种优化的一个很好的例子。

答案 1 :(得分:1)

在C中,运算符+=的行为是通过=+运算符的等效组合定义的。例如。根据定义,您的x += 1代表x = x + 1。由于x具有窄类型signed char,因此在任何算术开始之前将其提升为int。这意味着在x + 1类型的域中评估子表达式int。之后,结果(类型int)将转换回signed char并存储回x

因此,在您的情况下,您的x += 1实际上等同于

x = (signed char) ((int) x + 1);

(int) x + 1子表达式不会溢出。它成功生成128类型的值int。但是,此值不适合signed char类型的范围,当此值转换回signed char类型时,这会导致实现定义的行为。在您的平台上,此实现定义的行为会生成-128类型的值signed char