硬件如何知道一个变量是正数还是负数?

Can*_*0rd 5 c hardware assembly twos-complement

如果这个问题太基本,我很抱歉......我只是没有在任何地方找到答案。

假设我像这样声明了一个 C 变量:

unsigned int var = 241;
Run Code Online (Sandbox Code Playgroud)

在这种情况下,var 是无符号的,所以我的目的是让它具有十进制值 241。

或者我可以这样声明:

signed int var = -15;
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我将其声明为有符号整数,因此根据我的理解,它应该具有十进制值 -15。

但是,这两次,我都假设 var 将在内存(硬件)中声明如下:1111 0001。

那么处理器如何知道在硬件中的最低级别我打算将其声明为 241 还是 -15?我知道用于表示负数等的二进制补码表示法,但是,我假设在硬件中,处理器只能看到一系列 1 和 0,然后通过切换某些 IC 的状态对其进行一些操作。处理器如何知道是解释标准二进制(无符号)还是 2 的补码(有符号)中的位序列?

还有另一个有点不相关的问题:

  1. 在 CI 中可以这样做:

    无符号整数 var = -15; printf("变量是:%d", var); 这将按预期打印 -15。为什么,当我这样做时:

有符号 int var = 0xF1; //或 0b11110001 printf("The var is: %d ", var);

我得到 241 而不是 -15?既然我声明它是有符号的并且在二进制补码中 0xF1 是 -15 为什么我得到的值 241 相当于标准二进制中的 0xF1 ?

  1. 为什么编译器让我做这样的事情: unsigned int var = -15;

它不应该抛出一个错误,告诉我我不能将负值分配给我声明为无符号的变量吗?

谢谢你,我为我的许多可能是基本的问题道歉,我不知道的太多了:D。

Yun*_*sch 7

硬件不知道。
编译器知道。
编译器知道,因为你在这里说过signed int var = -15;,“亲爱的编译器,这是一个可以为负的变量,我将它初始化为负值。”
在这里你说的是不同的unsigned int var = 241;,“亲爱的编译器,这是一个不能为负的变量,我将它初始化为正值。”

编译器将记住这一点,以便您以后对变量及其值进行任何操作。编译器会将所有相应的代码转换为机器语言中的那组指令,这将导致硬件做出相应的行为。所以硬件最终会做一些适合消极或不适合的事情;不是因为知道,而是因为没有选择。

“相应指令”的一个有趣方面(正如 Peter Cordes 在下面的评论中所指出的)是,对于负值的 2 补码表示的特殊(但非常广泛使用)情况,指令实际上对于两者是相同的(这是 2-complement 的一个重要优势)。

  • 2 的补码的优点在于加法/减法与无符号的二进制运算相同;对于非扩展乘法也是如此。只有补码或符号/数值机器需要不同的指令来进行有符号/无符号基本数学运算。(2 的补码机器只需要不同的比较和/或分支指令)。 (2认同)

pro*_*-fh 5

如果两个值是char(有符号或无符号),那么它们的内部表示(8 位模式)在内存或寄存器中将是相同的。唯一的区别在于编译器在处理这些值时发出的指令。例如,如果这些值存储在声明signed或in 的变量unsignedC,则这些值之间的比较将使编译器在汇编级别生成有符号无符号的特定比较指令。

但是在您的示例中,您使用ints。假设在您的平台上这些ints 使用四个字节,那么当涉及到它们的 32 位模式时,您提供的两个常量并不相同。较高位考虑值的符号并传播以填充 0 或 1 到 32 位(参见0f下面的序列)。

请注意,unsigned int如果您使用正确的编译器标志(-Wconversion例如),为 an 分配负值会在编译时产生警告。在下面的评论中,@PeterCordes 提醒我们这样的赋值在 C 中是合法的,并且在某些情况下很有用;使用(或不)编译器标志来检测(或不)这种情况只是个人选择的问题。但是,赋值-15U而不是-15明确将常量视为无符号的意图(尽管有减号),并且不会触发警告。

int i1=-15;
int i2=0xF1;
int i3=241;
printf("%.8x %d\n", i1, i1); // fffffff1 -15
printf("%.8x %d\n", i2, i2); // 000000f1 241
printf("%.8x %d\n", i3, i3); // 000000f1 241
unsigned int u1=-15; // warning: unsigned conversion from ‘int’ to ‘unsigned int’ changes value from ‘-15’ to ‘4294967281’
unsigned int u2=0xF1;
unsigned int u3=241;
printf("%.8x %u\n", u1, u1); // fffffff1 4294967281
printf("%.8x %u\n", u2, u2); // 000000f1 241
printf("%.8x %u\n", u3, u3); // 000000f1 241
Run Code Online (Sandbox Code Playgroud)

  • 我猜转换警告来自 MSVC?GCC 和 clang 不会对此发出警告,因为它是完全合法的 C,并且 `-16U` 或 `-1U` 是某些位模式的有用方法,例如 `x & -16U` 向下舍入为 16 的倍数。所以它在这样的情况下收到有关它的警告会很烦人。https://godbolt.org/z/vM64df。但是 MSVC 的“-Wall”启用了一系列警告,包括那些经常是虚假/误报的警告,因此这很好,并且可能有用,并且非常适合。(我希望 GCC 或 clang 会在“-Wpedantic”或其他地方对此发出警告,但我没有找到。) (2认同)
  • 请注意,在正式的 C 术语中,“-15U”绝不是负数。正如 MSVC 警告的那样(https://godbolt.org/z/nf9x8M):“*C4146:一元减运算符应用于无符号类型,结果仍然无符号*”。所以它与“(0U - 15U)”完全相同。C 数字文字不包含减号;这就是为什么“-0x80000000”在 32 位 int C 实现中具有“unsigned”类型:0x80000000 不适合有符号 32 位 int,因此它会提升为无符号,*然后*应用一元“-”。https://godbolt.org/z/eW7bsx。这意味着您通常需要强制转换为有符号或类似的内容来定义“INT_MIN”。 (2认同)