如何解决关于gcc翻译的汇编代码用于C实现N阶乘的疑惑
我对阶乘函数的反汇编感到困惑。
C 代码
long factorial(int x)
{
long result = 1;
while (x > 1)
{
result = result * x;
x= x - 1;
}
return result;
}
我使用 gcc 命令反汇编阶乘函数
gcc -S -O1 test.c
factorial:
.LFB0:
cmpl $1,%edi
jle .L2
movslq %edi,%rdx
leaq -1(%rdx),%rcx
leal -2(%rdi),%eax
subq %rax,%rcx
movl $1,%eax
.L3:
imulq %rdx,%rax
subq $1,%rdx
cmpq %rcx,%rdx
jne .L3
.L2:
movl $1,%eax
rep ret
我不明白下面的代码是干什么的,谁能帮帮我?
movq %rax,%rdx
leaq -1(%rax),%rcx
leal -2(%rdi),%esi
subq %rsi,%rcx
解决方法
(对问题的更新更改了 C 和 asm,删除了问题仍然询问的 movq %rax,%rdx
,但否则会使答案的第一部分无效。请参阅编辑历史记录或遵循此中的 Godbolt 链接回答以查看本节所指的内容。)
movq %rax,%rdx
正在制作符号扩展 x
(32 位 int
到 64 位 long
)的副本,用于表达式中的循环result * x
表达式隐式执行 (long)x
。请注意,它避免了每次循环都像 C 抽象机那样重做符号扩展。 (与 GCC5 及更早版本不同,后者或多或少按编写的方式编译,仅进行正常的转换 like do{}while loop structure。)
它以符号扩展 x
的 2 个副本开始的事实是因为您的 C 以 result=x
开头。这是您的阶乘实现中的一个错误,因为您没有执行 x--
,但编译器只是在实现您编写的内容。实际上使用 x--
会产生其他奇怪的代码 (https://godbolt.org/z/345K6hbas),例如 leal -3(%rdi),%edi
/ addq $1,%rdi
,它仅与 lea -2(%rdi),%edi
不同,以防 LEA 产生 0xFFFFFFFF (-1) 和qword +1 进入高 32 位。但这不可能发生,因为较早的 cmp/jcc 会提前返回 x-1 <= 1
,因此 rdi-3+1 是另一个错过的优化。
其他 3 条指令(lea/lea/sub)是 GCC 愚蠢的,我认为以复杂的方式计算常量 1
作为 RCX 中的循环终止条件,以进行比较反对RDX。这是一个遗漏的优化错误,您可以在 GCC's bugzilla 上报告,因为它仍然发生在 -O2 (https://godbolt.org/z/achGeePYb) 的当前主干夜间构建中。
我猜,提升符号扩展导致创建此逻辑为时已晚,优化传递无法将其归类为合理的东西,或者以他们不能/不可以的方式。
顺便说一句,这看起来像 GCC7,因为它与您的 asm https://godbolt.org/z/jMhjsvfdM 匹配。后来的 GCC 省略了 rep 前缀(但否则会造成同样的混乱),早期的 GCC 要么使 asm 略有不同,要么(gcc5 及更早版本)直接进入循环而没有先做那么多。但是他们在每次循环迭代(从 32 位 x
到 64 位 int
)时都会重做 long
的符号扩展。
即使在 -O2
时也会发生这种情况,因此这不是仅启用部分优化 (-O1) 的结果。 GCC8 和更早版本在 -O3
处自动矢量化,但这可能无利可图,这可能是 GCC9 和后来停止这样做的原因。 (x86 在 AVX-512 之前没有 SIMD qword 乘法,-march=skylake-avx512
,并且从多个 pmuludq
操作中合成它很慢。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。