- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我的任务是解释一些看似奇怪的C代码行为(在x86上运行)。我可以轻松完成所有其他工作,但是这确实让我感到困惑。
代码段1输出-2147483648
int a = 0x80000000;
int b = a / -1;
printf("%d\n", b);
Floating point exception
int a = 0x80000000;
int b = -1;
int c = a / b;
printf("%d\n", c);
1 + ~INT_MIN == INT_MIN
)生成结果的原因,但是我不太了解如何将-1除以整数会生成FPE,也无法在Android手机(AArch64,GCC 7.2)上重现它。 0)。代码2的输出与代码1相同,没有任何例外。它是x86处理器的隐藏错误功能吗?
最佳答案
这里有四件事:gcc -O0
行为说明了两个版本之间的区别。 (而clang -O0
恰巧同时使用idiv
编译它们)。以及为什么即使使用编译时常数操作数也能得到它。
x86 idiv
故障行为与ARM上除法指令的行为
如果整数运算导致传递信号,则POSIX要求它为SIGFPE:On which platforms does integer divide by zero trigger a floating point exception?但是POSIX不需要为任何特定的整数运算而设陷阱。 (这就是为什么x86和ARM可以不同的原因)。
单一Unix规范defines SIGFPE作为“错误的算术运算”。它以浮点数来混淆地命名,但是在FPU处于默认状态的正常系统中,只有整数运算会提高它。在x86上,只有整数除法。在MIPS上,编译器可以使用add
instead of addu
进行带符号的数学运算,因此您可能会在带符号的添加溢出中获取陷阱。 (gcc uses addu
even for signed,但是未定义行为的检测器可能使用add
。)
C未定义的行为规则(有符号的溢出和除法)使gcc发出可以在这种情况下捕获的代码。
没有选项的gcc与gcc -O0
相同。
-O0
减少编译时间并使调试产生预期的结果。这是默认值。
这说明了两个版本之间的区别:gcc -O0
不仅不尝试优化,还主动进行反优化,以使asm独立实现函数中的每个C语句。这使gdb
's jump
command安全地工作,使您可以跳到函数内的另一行,并像在C语言源中真正跳来跳去一样。
它也不能假设有关语句之间的变量值,因为您可以使用set b = 4
更改变量。这显然对性能造成灾难性的不利影响,这就是为什么-O0
代码运行速度比普通代码慢几倍的原因,也是为什么optimizing for -O0
specifically is total nonsense的原因。由于所有存储/重新加载,甚至缺少最明显的优化,它也使-O0
asm输出really noisy and hard for a human to read。
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);
a/b
,
gcc -O0
必须发出代码以从内存中重新加载
a
和
b
,并且不对其值进行任何假设。
int c = a / -1;
时,您无法使用调试器更改
-1
,因此gcc可以并且确实以与xcc
int c = -a;
或AArch64
neg eax
指令实现
neg w0, w0
相同的方式来实现该语句,被负载(a)/商店(c)包围。在ARM32上,它是一个
rsb r3, r3, #0
(反减:
r3 = 0 - r3
)。
-O0
不会执行该优化。它仍然对
idiv
使用
a / -1
,因此两个版本在使用clang的x86上都会出错。为什么gcc完全“优化”了?请参见
Disable all optimization options in GCC。 gcc总是通过内部表示形式进行转换,而-O0只是生成二进制文件所需的最少工作量。它没有一种“笨拙而文字化”的模式,该模式试图使asm尽可能地类似于源。
idiv
与AArch64
sdiv
:
# 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商+余数。
As documented in Intel's manual,
idiv
在以下位置引发#DE:
int result = long long / int
。但是gcc不能进行这种优化,因为它不允许创建有故障的代码,而不是遵循C整数提升规则并进行64位除法然后截断为
int
。它也
doesn't optimize even in cases where the divisor is known to be large enough that it couldn't #DE
cdq
)时,唯一可以溢出的输入是
INT_MIN / -1
。 “正确”商是一个33位带符号整数,即带前导零符号位的正
0x80000000
,使其成为正2的补码带符号整数。由于这不适用于
eax
,因此
idiv
会引发
#DE
异常。然后内核提供
SIGFPE
。
# 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]
sdiv
documentation没有提及任何例外。
INT_MIN
/
-1
在C中是未定义的行为,就像所有有符号整数溢出一样。这使编译器可以在x86之类的机器上使用硬件划分指令,而无需检查特殊情况。如果不必出错,未知的输入将需要运行时比较和分支检查,并且没人希望C要求这样做。
a
和
b
在
a/b
运行时仍具有其设置值。然后,它可以看到程序具有未定义的行为,因此可以执行所需的任何操作。 gcc选择像从
INT_MIN
产生
-INT_MIN
一样产生。
abs(x)
仍然可以是负数。
int x86_fault() {
int a = 0x80000000;
int b = -1;
int c = a / b;
return c;
}
gcc6.3 -O3
对此进行编译
x86_fault:
mov eax, -2147483648
ret
clang5.0 -O3
编译为(即使使用-Wall -Wextra`也没有警告):
x86_fault:
ret
eax
中的所有垃圾,或加载NULL指针和非法指令。例如对于x86-64,使用gcc6.3 -O3:
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
-O0
的情况没有让编译器在编译时看到UB,因此您获得了“预期”的asm输出。
关于c - 为什么整数除以-1(负数)会导致FPE?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58373496/
赏金:对于提供代码以使此子程序与负数一起工作的任何人,+50 信誉点。 我编写了一个 MIPS 程序来将华氏温度转换为摄氏温度。它打开自己的输出窗口(即 UART)并正确显示摄氏值。它在从 C 调用到
我得到了以下代码 # On va convertir en somme monétaire # We convert our integer into a way to read money
我得到了以下代码 # On va convertir en somme monétaire # We convert our integer into a way to read money
我使用以下 RegEx 基本上过滤掉任何文本,并接受数字 + 运算符。 ([-+]?[0-9]*\.?[0-9]+[\/\+\-\*])+([-+]?[0-9]*\.?[0-9]+) 所以它抓取 1+
我有一个查询,它计算我在查询中使用 union all 的平均值,以便获取我最终使用 max 函数的数据。 当联合返回结果时,如下所示:- col 1 col2 1 0
我有这样一个类: public class SpiralGenerator implements Iterator> { private void generate(int pos, E...
A = numpy.matrix([[36, 34, 26], [18, 44, 1], [11, 31, 41]]) X1 = numpy.matrix([[462
我有一个应用程序,其中有一个显示硬币 00 的 TextView ,一个按钮显示奖励视频广告,为用户提供 10 个硬币,还有一个购买按钮,将硬币减少 30 个。现在,当用户有 30 个硬币时,单击购买
话不多少,直接附上代码实例,仅供参考 ? 1
我有一系列正数和负数,我想将每个数字的绝对值增加一个,同时仍保持正数/负数。0.2 -> 1.2-0.3 -> -1.3我怎样才能做到这一点? 最佳答案 让我们尝试使用numpysign s=pd.S
我有这段代码,只允许在 keypress() 的输入字段中输入数字 if (e.which != 8 && e.which != 0 && (e.which 57)) { return fa
我试图用“-1”作为所有值填充二维数组。我正在使用的代码是: int c [] []=new int[4][4]; Arrays.fill(c,-1) 这会引发以下错误: Exception in t
在学校作业中,我们应该编写一个程序,该程序接受一个数字并将其分为三个部分:1. 检查数字是正数还是负数2. 整数(大小)3.小数部分 要求是应该有一个自己的函数,名为separate,具有输入和输出参
有没有什么方法可以在 C# 中执行整数除法(没有 float 或小数,我需要保持这个非常快)来向下舍入数字? 默认除法只是丢弃分数参数。考虑: 1 / 2 = 0 // That is correc
我正在使用 matplotlib 为报告生成图表,并指定我自己的样式表来指定文本格式以符合报告的指定文档样式。在我的 .mplstyle 样式表中,我按如下方式指定字体系列: font.family
在 C++11 中,如果我们尝试使用全局运算符 new 分配负大小的数组,它会抛出 std::bad_array_new_length,但是 C++98/C++03 呢?是 UB 还是会抛出 std:
我试过 scanf("%u",&number) 并且我输入了负数问题是当我 printf("%d",number) 我得到负数。我认为这会阻止我读取负数。scanf("%d",&number) 和 s
我的任务是解释一些看似奇怪的C代码行为(在x86上运行)。我可以轻松完成所有其他工作,但是这确实让我感到困惑。 代码段1输出-2147483648 int a = 0x80000000; int
js有问题吗? if("hello".indexOf("world")) { // I forgot to add > -1 here console.log("hello world");
我正在尝试使用 Antlr 4 设置一个简单的计算器。 语法: grammar calcGrammar; input : expression EOF; expression : MINUS
我是一名优秀的程序员,十分优秀!