c - 为什么 GCC 在实现整数除法时使用乘以一个奇怪的数字?

标签 c gcc assembly x86-64 integer-division

我一直在阅读有关 divmul 汇编操作的内容,因此我决定通过用 C 语言编写一个简单的程序来了解它们的实际应用:

文件分割.c

#include <stdlib.h>
#include <stdio.h>

int main()
{
    size_t i = 9;
    size_t j = i / 5;
    printf("%zu\n",j);
    return 0;
}

然后生成汇编语言代码:

gcc -S division.c -O0 -masm=intel

但是查看生成的 division.s 文件,它不包含任何 div 操作!相反,它使用位移位和魔数(Magic Number)来执行某种黑魔法。下面是计算 i/5 的代码片段:

mov     rax, QWORD PTR [rbp-16]   ; Move i (=9) to RAX
movabs  rdx, -3689348814741910323 ; Move some magic number to RDX (?)
mul     rdx                       ; Multiply 9 by magic number
mov     rax, rdx                  ; Take only the upper 64 bits of the result
shr     rax, 2                    ; Shift these bits 2 places to the right (?)
mov     QWORD PTR [rbp-8], rax    ; Magically, RAX contains 9/5=1 now, 
                                  ; so we can assign it to j

这是怎么回事?为什么 GCC 根本不使用 div?它是如何生成这个魔数(Magic Number)的,为什么一切正常?

最佳答案

整数除法是您可以在现代处理器上执行的最慢的算术运算之一,延迟高达几十个周期并且吞吐量很差。 (对于 x86,请参阅 Agner Fog's instruction tables and microarch guide )。

如果您提前知道除数,则可以通过将其替换为一组具有等效效果的其他运算(乘法、加法和移位)来避免除法。即使需要多个运算,它通常仍然比整数除法本身快很多。

以这种方式实现 C / 运算符而不是使用涉及 div 的多指令序列只是 GCC 按常量进行除法的默认方式。它不需要跨操作优化,甚至在调试时也不会改变任何东西。 (不过,对小代码量使用 -Os 确实会让 GCC 使用 div。)使用乘法逆而不是除法就像使用 lea代替 muladd

因此,如果除数在编译时未知,您只会在输出中看到 dividiv

有关编译器如何生成这些序列的信息,以及让您自己生成它们的代码(几乎肯定是不必要的,除非您使用的是脑死亡编译器),请参阅 libdivide .

关于c - 为什么 GCC 在实现整数除法时使用乘以一个奇怪的数字?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41183935/

相关文章:

c - 预编译 Go 程序中依赖 C 来提高速度的部分

CS50 pset5 哈希表节点

c++ - C++14中扣除 'auto func(int)'前使用 'auto'

c++ - qtcreator 没有使用指定的编译器

assembly - printf 在 x86-64 上是否需要额外的堆栈空间?

c++ - 结构体(数组)中出现次数最多的数字

c - 显示最少/最常出现的字符的程序

C - Fclose -> 中止(核心转储)

c# 加载C文件,然后将其转换为Assembly

c - GCC 为数组元素的重复 XOR 生成冗余代码