【问题标题】:Trying to translate C program to reverse a number into x86 assembly试图翻译 C 程序以将数字反转为 x86 程序集
【发布时间】:2018-10-01 03:26:45
【问题描述】:

我正在尝试将以下程序转换为 x86 程序集 (AT&T)。

#include <stdio.h>

int main()
{
   int n = 123;
   int reverse = 0;


   while (n != 0)
   {
      reverse = reverse * 10;
      reverse = reverse + n%10;
      n       = n/10;
   }

   printf("%d\n", reverse);

   return 0;
}

应该打印321。

但是,使用下面的代码,我得到的是 0。 任何人都可以告诉我我在这里做错了什么吗? (我只粘贴了下面的相关部分。我确定初始化和打印工作正常。你可以看到the whole thing here

  movl  $123, %esi    # int n
  movl  $0, %edi    # int reverse
  movl $10, %ebx    # divisor


L1:     # while n != 0

cmpl $0, %esi
je L2

# reverse = reverse * 10
imul $10, %edi

# reverse = reverse + n % 10
movl $0, %edx
movl %edi, %eax
idivl %ebx
addl %edx, %edi

# n = n / 10
movl %esi, %eax
movl $0, %edx
idivl %ebx
movl %eax, %esi

jmp L1

L2:  # end while

movl %edi, %eax

也许我还没有完全理解 idivl 命令应该做什么。我知道它将 %edx:%eax 除以 %ebx 并将商存储在 %eax 中,余数存储在 %edx 中。

【问题讨论】:

  • 是的,idivl 就是这样工作的。使用调试器单步执行代码,看看哪里出错了。
  • 一个令人耳目一新的好问题。
  • 一个idiv 给你商和余数;您无需使用相同的输入运行两次。 (哦,我认为这实际上是你的错误;即使你想要 n/10n%10使用相同的输入。)
  • 注意可以使用xor %reg, %reg设置寄存器为0,这样会比movl $0, %reg

标签: c assembly x86 att


【解决方案1】:
# reverse = reverse + n % 10
movl $0, %edx
movl %edi, %eax   ; <--- here

%edi不是n,根据上面的cmets:

movl  $123, %esi    # int n

所以,它应该使用%esi,即movl %esi, %eax

【讨论】:

  • 这是唯一缺少的东西。现在它完美地工作了。
【解决方案2】:

有时看看编译器生成了什么是件好事

int reverse(int x)
{
   int r = 0;

   while (x != 0)
   {
      r = r * 10;
      r = r + x%10;
      x = x/10;
   }
   return r;
}

和最短的版本:

reverse:
  xor eax, eax
  mov esi, 10
.L2:
  test edi, edi
  je .L5
  imul ecx, eax, 10
  mov eax, edi
  cdq
  idiv esi
  mov edi, eax
  lea eax, [rdx+rcx]
  jmp .L2
.L5:
  ret

或最快的:

reverse:
  xor eax, eax
  test edi, edi
  je .L4
  mov esi, 1717986919
.L3:
  lea ecx, [rax+rax*4]
  mov eax, edi
  imul esi
  mov eax, edi
  sar eax, 31
  sar edx, 2
  sub edx, eax
  lea eax, [rdx+rdx*4]
  add eax, eax
  sub edi, eax
  test edx, edx
  lea eax, [rdi+rcx*2]
  mov edi, edx
  jne .L3
  rep ret
.L4:
  rep ret

正如您所见,编译器与 99.99% 的编码器一样好/更好

【讨论】:

  • 对于您的“最快”版本,您似乎忘记告诉 gcc 为现代 CPU 进行优化;它没有利用宏融合。使用-mtune=haswell,或者更好的-march=haswell。如果您使用无符号而不是有符号除法,您还可以获得更快的代码。无论如何,我认为您不希望您的余数为-9..0 用于负输入。但是,是的,using idiv for constant divisors is well known to be sub-optimal
  • @PeterCordes - 在这个例子中我没有看到很多可能的改进。
  • @PeterCordes OP 想要签名以获得签名结果
  • 好吧,这很公平,我认为算法会因负输入而被破坏,但它确实有效。你确实从-123godbolt.org/g/u9rrru 得到-321。尽管如此,对于较大的数字,在循环外处理符号会更有效,而不是使用定点乘法逆进行有符号除法/模数的额外工作。
  • 您从-march=haswell 获得的改进是将test 放在jne 之前,总共节省1 uop。还要从ret 中删除rep 前缀,因为我们可能不关心AMD K10。
猜你喜欢
  • 2014-08-07
  • 2020-03-21
  • 1970-01-01
  • 2014-05-31
  • 2018-03-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多