我有一项任务,阐述C代码(在x86上运行)的一些看似奇怪的行为。我可以很容易地完成其他一切,但这件事真的让我困惑。
代码段1输出-2147483648
int a = 0x80000000;
int b = a / -1;
printf("%d\n", b);
int a = 0x80000000;
int b = -1;
int c = a / b;
printf("%d\n", c);
这里发生了四件事:
>
GCC-O0
行为解释了您的两个版本之间的差异:IDIV
和NEG
。(而clang-o0
恰好使用idiv
编译它们)。以及为什么即使使用编译时常量操作数也能得到这个结果。
ARM上x86IDIV
错误行为与除法指令行为的比较
-O0
减少编译时间并使调试产生预期结果。这是默认值。
这解释了您的两个版本之间的差异:
gcc-o0
不仅不尝试优化,而且主动地去优化,以生成在函数中独立实现每个C语句的asm。这允许gdb
的jump
命令安全地工作,使您可以跳转到函数中的另一行,并像在C源代码中跳转一样。为什么clang用-O0生成低效的asm(对于这个简单的浮点和)?详细说明-o0
如何编译以及为什么编译。
int a = 0x80000000;
int b = -1;
// debugger can stop here on a breakpoint and modify b.
int c = a / b; // a and b have to be treated as runtime variables, not constants.
printf("%d\n", c);
但是,Clang5.0-O0
不进行这种优化。它仍然使用idiv
来表示a/-1
,因此这两个版本在x86上都会出现clang错误。海合会为什么要“优化”?请参见禁用GCC中的所有优化选项。gcc总是通过内部表示进行转换,而-o0只是生成二进制文件所需的最小工作量。它没有试图使asm尽可能像源代码的“哑巴和文字”模式。
x86-64:
# int c = a / b from x86_fault()
mov eax, DWORD PTR [rbp-4]
cdq # dividend sign-extended into edx:eax
idiv DWORD PTR [rbp-8] # divisor from memory
mov DWORD PTR [rbp-12], eax # store quotient
与IMUL r32、r32
不同,没有2个操作数IDIV
没有红利的上半部分输入。不管怎样,这并不重要;gcc只在edx
=eax
中符号位的副本中使用它,所以它实际上是在做32b/32b=>32b商+余数。正如Intel手册中所述,IDIV
引发#de on:
AARCH64:
# int c = a / b from x86_fault() (which doesn't fault on AArch64)
ldr w1, [sp, 12]
ldr w0, [sp, 8] # 32-bit loads into 32-bit registers
sdiv w0, w1, w0 # 32 / 32 => 32 bit signed division
str w0, [sp, 4]
ARM硬件除法指令不会引发被零除或int_min/-1
溢出的异常。内特·埃尔德里奇评论道:
完整的ARM体系结构参考手册指出,UDIV或SDIV在被零除时,只返回零作为结果,“没有任何被零除发生的迹象”(Armv8-A版本中的C3.4.8)。没有异常,也没有标志--如果您想要捕获除零,您必须编写一个显式测试。同样,int_min
被-1
带符号的除法返回int_min
,但没有溢出指示。
启用优化后,编译器可以假设a
和b
在运行a/b
时仍然具有它们的设置值。然后它可以看到程序有未定义的行为,因此可以做任何它想做的事情。gcc选择生成int_min
,就像从-int_min
生成一样。
在2的补系统中,最负的数是它自己的负数。对于2的补码来说,这是一个令人讨厌的拐角处,因为它意味着abs(x)
仍然可以是负数。https://en.wikipedia.org/wiki/two%27s_complement#most_negative_number
int x86_fault() {
int a = 0x80000000;
int b = -1;
int c = a / b;
return c;
}
使用gcc6.3-o3
对x86-64进行编译
x86_fault:
mov eax, -2147483648
ret
x86_fault:
ret
int *local_address(int a) {
return &a;
}
local_address:
xor eax, eax # return 0
ret
void foo() {
int *p = local_address(4);
*p = 2;
}
foo:
mov DWORD PTR ds:0, 0 # store immediate 0 into absolute address 0
ud2 # illegal instruction
还请参见每个C程序员应该了解的关于未定义行为的内容(与Basile链接的LLVM博客文章相同)。
问题内容: 我对此很好奇: 使用Java进行评估时,会发生以下异常: 线程“主”中的异常java.lang.ArithmeticException:/在Foo.main(Foo.java:3)处为零 但是被评估为。 为什么会这样? 问题答案: 这是因为整数没有+/- Inf,NaN的值,并且不允许除以0,而浮点数确实具有这些特殊值。
我观察到库函数,当它在循环中被调用一次时,它几乎总是产生正数。
在测试CRC实现时,我注意到0x01的CRC通常(?)似乎是多项式本身。然而,当试图手动进行二进制长除法时,我总是以丢失多项式的前导“1”告终,例如,当消息为“0x01”和多项式“0x1021”时,我将得到 通过查看https://en.wikipedia.org/wiki/computation_of_cyclic_redundancy_checks,我可以看到使用生成多项式离开移位寄存器的高位
为什么这个程序的输出是而不是 当你除以3/2,它等于1.5,我认为Java只取整数的第一个值。发生什么事?
问题内容: -1作为转换为二进制的int表示为321。当我右移31次时,得到1(31个0和一个1)。但是当我右移32次时,我又得到-1。它不应该等于0吗? 问题答案: Java规范对移位运算符的解释如下: 如果左侧操作数的提升类型为,则仅将右侧操作数的最低5位用作移位距离。就像右手操作数受到掩码值的按位逻辑AND运算符(第15.22.1节)一样。因此,实际使用的移动距离始终在0到31(含)范围内。
问题内容: 我想知道这是否是JVM错误? Java版本“ 1.6.0_0” OpenJDK运行时环境(IcedTea6 1.4.1)(6b14-1.4.1-0ubuntu13)OpenJDK 64位服务器VM(内部版本14.0-b08,混合模式) 当我运行它时会产生这个: 对于32的任意倍数,我也得到相同的结果。 我需要编写自己的右移来检查吗? 问题答案: http://docs.oracle.c