代码之家  ›  专栏  ›  技术社区  ›  patrik

Asembly x86计算器

  •  0
  • patrik  · 技术社区  · 2 年前

    好的,所以我对组装相当陌生,事实上,我对组装非常陌生。我写了一段代码,简单地从用户那里获取数字输入,乘以10,并通过程序退出状态(在终端中键入echo$?)将结果表达给用户 问题是,它没有给出正确的数字,4x10显示为144。然后我想输入可能是一个字符,而不是一个整数。我的问题是,如何将字符输入转换为整数,以便用于算术计算?

    如果有人能回答这个问题,并记住我是初学者,那就太好了:) 还有,我怎样才能把这个整数转换回一个字符呢?

    section .data
    
    section .bss
    input resb 4
    
    section .text
    
    global _start
    _start:
    
    mov eax, 3
    mov ebx, 0
    mov ecx, input
    mov edx, 4
    int 0x80
    
    mov ebx, 10
    imul ebx, ecx
    
    mov eax, 1
    int 0x80
    
    0 回复  |  直到 4 年前
        1
  •  13
  •   Michael    11 年前

    下面是几个函数,用于将字符串转换为整数,反之亦然:

    ; Input:
    ; ESI = pointer to the string to convert
    ; ECX = number of digits in the string (must be > 0)
    ; Output:
    ; EAX = integer value
    string_to_int:
      xor ebx,ebx    ; clear ebx
    .next_digit:
      movzx eax,byte[esi]
      inc esi
      sub al,'0'    ; convert from ASCII to number
      imul ebx,10
      add ebx,eax   ; ebx = ebx*10 + eax
      loop .next_digit  ; while (--ecx)
      mov eax,ebx
      ret
    
    
    ; Input:
    ; EAX = integer value to convert
    ; ESI = pointer to buffer to store the string in (must have room for at least 10 bytes)
    ; Output:
    ; EAX = pointer to the first character of the generated string
    int_to_string:
      add esi,9
      mov byte [esi],STRING_TERMINATOR
    
      mov ebx,10         
    .next_digit:
      xor edx,edx         ; Clear edx prior to dividing edx:eax by ebx
      div ebx             ; eax /= 10
      add dl,'0'          ; Convert the remainder to ASCII 
      dec esi             ; store characters in reverse order
      mov [esi],dl
      test eax,eax            
      jnz .next_digit     ; Repeat until eax==0
      mov eax,esi
      ret
    

    这就是你使用它们的方式:

    STRING_TERMINATOR equ 0
    
    lea esi,[thestring]
    mov ecx,4
    call string_to_int
    ; EAX now contains 1234
    
    ; Convert it back to a string
    lea esi,[buffer]
    call int_to_string
    ; You now have a string pointer in EAX, which
    ; you can use with the sys_write system call
    
    thestring: db "1234",0
    buffer: resb 10
    

    请注意,我在这些例程中没有做太多错误检查(比如检查是否有超出范围的字符) '0' - '9' ).这些例程也不处理有符号的数字。因此,如果你需要这些东西,你必须自己添加它们。

        2
  •  6
  •   Peter Cordes    2 年前

    字符串的基本算法->数字是: total = total*10 + digit ,从MSD开始。(例如: digit = *p++ - '0' 用于ASCII数字字符串)。因此,最左边/最高有效位/第一位数字(在内存中,按读取顺序)乘以10N倍,其中N是它后面的总位数。

    这样做通常比在加法之前将每个数字乘以10的右幂更有效。这将需要2倍;一个是增加10的幂,另一个是将其应用于数字。(或以10的升幂查找表格)。

    当然 为了提高效率,您可以使用SSSE3 pmaddubsw 和SSE2 pmaddwd 将数字与它们的并置值相乘 :见 Is there a fast way to convert a string of 8 ASCII decimal digits into a binary number? 和任意长度 How to implement atoi using SIMD? .但在数据通常较短的情况下,后者可能不是一场胜利。当大多数数字只有两位数长时,标量循环是有效的。


    再加上@Michael的答案,int->字符串函数 在第一个非数字处停止 ,而不是固定长度。这会捕捉到一些问题,比如当用户按下return键时,字符串中出现了换行符,以及没有旋转 12xy34 变成一个非常大的数字。(将其视为 12 , like C's atoi function ).停止字符也可以是终止字符 0 在C隐式长度字符串中。

    我也做了一些改进:

    • 不要使用 the slow loop instruction 除非你在优化代码大小。只是忘记它的存在和使用 dec / jnz 如果倒计时到零仍然是你想要做的,而不是比较指针或其他东西。

    • 2 LEA指令明显优于 imul + add :更低的延迟。

    • 在EAX中累积结果,我们无论如何都要返回它。(如果您将其内联而不是调用它,请使用您想要结果的任何寄存器。)

    我更改了寄存器,使其遵循x86-64系统V ABI(RDI中的第一个arg,EAX中的返回)。

    移植到32位: 这完全不依赖于64位;只需使用32位寄存器即可将其移植到32位。(即更换 rdi 具有 edi , rax 具有 ecx 拉克斯 具有 eax ).请注意32位和64位之间的C调用约定差异,例如EDI保留调用,arg通常在堆栈上传递。但如果你的来电者是asm,你可以在EDI中传递arg。

        ; args: pointer in RDI to ASCII decimal digits, terminated by a non-digit
        ; clobbers: ECX
        ; returns: EAX = atoi(RDI)  (base 10 unsigned)
        ;          RDI = pointer to first non-digit
    global base10string_to_int
    base10string_to_int:
    
         movzx   eax, byte [rdi]    ; start with the first digit
         sub     eax, '0'           ; convert from ASCII to number
         cmp     al, 9              ; check that it's a decimal digit [0..9]
         jbe     .loop_entry        ; too low -> wraps to high value, fails unsigned compare check
    
         ; else: bad first digit: return 0
         xor     eax,eax
         ret
    
         ; rotate the loop so we can put the JCC at the bottom where it belongs
         ; but still check the digit before messing up our total
      .next_digit:                  ; do {
         lea     eax, [rax*4 + rax]    ; total *= 5
         lea     eax, [rax*2 + rcx]    ; total = (total*5)*2 + digit
           ; imul eax, 10  / add eax, ecx
      .loop_entry:
         inc     rdi
         movzx   ecx, byte [rdi]
         sub     ecx, '0'
         cmp     ecx, 9
         jbe     .next_digit        ; } while( digit <= 9 )
    
         ret                ; return with total in eax
    

    这将停止转换第一个非数字字符。 这通常是终止隐式长度字符串的0字节。通过检查,可以在循环结束后检查它是否是字符串结尾,而不是其他非数字字符 ecx == -'0' (仍然持有 str[i] - '0' 整数“digit”值超出范围),如果要检测尾随垃圾。

    如果输入的是显式长度字符串,则需要使用循环计数器,而不是检查终止符(如@Michael的答案),因为内存中的下一个字节可能是另一个数字。或者它可能位于未映射的页面中。


    让第一次迭代变得特别 在跳入循环的主要部分之前处理它被称为 loop peeling .剥离第一次迭代允许我们特别优化它,因为我们知道total=0,所以不需要将任何值乘以10。就像从 sum = array[0]; i=1 而不是 sum=0, i=0; .

    得到 nice loop structure (with the conditional branch at the bottom) ,我在第一次迭代中使用了跳入循环中间的技巧。这甚至不需要额外的钱 jmp 因为我已经在剥离的第一次迭代中进行了分支。重新排列一个循环以便 if()break 在中间变为底部的循环分支被称为环旋转,并且可以涉及剥离第一迭代的第一部分和最后一次迭代的第二部分。

    解决在非数字上退出循环问题的简单方法是使用 jcc 在循环体中,就像一个 if() break; 总计=总计*10+位数 .但是我需要一个 jmp 循环中总共有2条分支指令,这意味着更多的开销。


    如果我不需要 sub ecx, '0' 对于循环条件,我可以使用 lea eax, [rax*2 + rcx - '0'] 把它作为LEA的一部分 .但那会 made the LEA latency 3 cycles instead of 1 ,在Sandybridge系列CPU上。(3组分LEA与2组分或更少。)这两个LEA形成了一个由依赖链组成的循环 eax ( total ),所以(尤其是对于大数字而言)在英特尔上不值得这么做。在CPU上哪里 base + scaled-index 不比 base + scaled-index + disp8 ( Bulldozer-family / Ryzen ),然后确定,如果循环条件是显式长度,并且根本不想检查数字。

    我首先使用movzx加载零扩展名,而不是在将数字从ASCII转换为整数后进行加载。(必须在某个时候完成,才能添加到32位EAX中)。处理ASCII数字的代码通常使用字节操作数大小,如 mov cl, [rdi] .但这会在大多数CPU上产生对旧RCX值的错误依赖。

    sub al,'0' 节省1字节 sub eax,'0' ,但在Nehalem/Core2上会导致部分寄存器暂停,在PIII上更糟。好的 on all other CPU families ,甚至Sandybridge:它是AL的RMW,所以它不会将部分reg与EAX分开重命名。但是 cmp al, 9 不会引起问题,因为读取字节寄存器总是可以的。它保存了一个字节(没有ModRM字节的特殊编码),所以我在函数顶部使用了它。


    有关更多优化内容,请参阅 http://agner.org/optimize , 以及网站上的其他链接 tag wiki .

    tag wiki也有初学者链接,包括一个FAQ部分,其中包含指向integer->字符串函数和其他常见的初学者问题。

    相关的: