2017-04-20 72 views
0

这是一个简单的问题,但它让我头晕目眩。我需要将一串字符(输入为负十进制数)转换为无符号整数。 rdi寄存器保存要转换的字符串。 rax寄存器将保存结果。x86 NASM将字符串转换为整数

xor rsi, rsi 
    xor rax, rax 
    xor dl, dl 
    xor rdx, rdx 
convert: 
    mov dl, [rdi+rsi] ;+rsi causes segmentation fault 

    cmp dl, "-" 
    jz increment 

    cmp dl, "." 
    jz dtoi_end 

    sub dl, "0" 

    mov rdx, 10 
    mul rdx 

    add rax, dl   ;invalid combination 

    inc rsi 
    jmp convert 

increment: 
    inc rsi 
    jmp convert 

convert_end: 
    ret 
  1. 我需要遍历每个角色,我试图通过注册RSI使用此。但是每次我尝试这个时,都会出现分段错误。

  2. 组合错误无效。我知道这是因为寄存器是不同的大小,但我失去了如何继续添加转换后的ascii值回rax。

这里有一个类似的问题,帮助我了解的过程中更好,但我已经碰了壁: Convert string to int. x86 32 bit Assembler using Nasm

+0

'DL'是'RDX'的一部分。你对'DL'所做的事情你也对'RDX'做了,反之亦然。其次,'mul rdx'采用操作数和'RAX',将它们相乘并将结果存储在'RDX:RAX'中。这不是你明显想要的。你必须重新考虑你对寄存器的使用。 – rkhb

+0

当你访问你不应该访问的内存时,'mov dl,[rdi + rsi]'崩溃。 rdi指向哪里?我个人不会添加rdi和rsi,因为您使用2个寄存器用于一个目的。初始化rsi以指向“字符串”,并用“mov dl,[rsi]'+'inc rsi'加载字节 – Tommylee2k

回答

1

我需要遍历每个角色,并且我想通过使用rsi寄存器来使用它。但是每次我尝试这个时,都会出现分段错误。

根据您所显示的代码,并RDI包含字符串的开头的地址的说法,我可以看到几个不同的原因,你会得到在负载分段错误。

也许问题是RDI包含一个8字符的ASCII字符串(通过值传递),而不是包含该字符串(通过引用传递)的内存位置的地址?

另一种更可能的可能性是它在循环的前几次迭代中工作正常,但是随后您开始尝试读取字符串的末尾,因为您没有正确终止循环。您所展示的代码中没有dtoi_end标签,也没有您实际跳到convert_end标签的地方。这些应该是同一个标签吗?如果我传递字符串“-2”,会发生什么?你的循环何时终止?在我看来,它不会!

您需要某种方式来指示整个字符串已被处理。有几个常用的方法。一种是在字符串末尾使用一个哨兵终结符字符,就像C使用ASCII NUL字符一样。在你的循环内部,你会检查正在处理的字符是否为0(NUL),如果是,跳出循环。另一个选择是将字符串的长度作为附加参数传递给函数,就像Pascal对计数长度字符串所做的那样。然后,你需要在循环内部进行测试,以检查是否已经处理了足够多的字符,如果是,则跳出循环。

我会尽量不要太讲究这个,但你应该已经能够通过使用调试器自己检测到这个问题。逐行执行代码,观察变量/寄存器的值,并确保您了解正在发生的事情。这基本上就是我在分析你的代码时所做的,除了我的头脑是调试器,在我自己的脑海里“执行”了代码。尽管如此,让计算机执行它更容易(也更不容易出错),这就是调试器发明的原因。如果你的代码不工作,并且你没有在调试器中逐行执行,你还没有努力去解决这个问题。事实上,单步穿越你写的每个功能是一个很好的习惯,因为(A)它会确保你理解你写的内容的逻辑,(B)它会帮助你找到错误。

组合错误无效。我知道这是因为寄存器是不同的大小,但我失去了如何继续添加转换后的ascii值回rax。

您必须使尺寸匹配。你可以做add al, dl,但是你会限制结果为8位的字节。这可能不是你想要的。因此,您需要将dl转换为64位QWORD,如rax。要做到这一点的显而易见的方法是使用零扩展的MOVZX指令。换句话说,它将值“扩展”为更大的大小,用0填充高位。这就是你想要的无符号值。对于带符号的值,您需要执行符号感知扩展(即将符号位考虑在内),并且要这样做,您可以使用MOVSX指令。

在代码:

movzx rdx, dl 
add rax, rdx 

别注意,因为评论者之一指出的,DL仅仅是RDX寄存器的最低8位:

| 63 - 32 | 31 - 16 | 15 - 8 | 7 - 0 | 
-------------------------------------- 
        | DH | DL | 
-------------------------------------- 
      |   EDX   | 
-------------------------------------- 
|     RDX    | 

因此,它对xor dl, dlxor rdx, rdx是多余的。后者完成前者。另外,每次修改dl时,实际上都会修改rdx的最低8位,这会导致错误的结果。提示,提示:这是你用调试器单步执行的其他东西,但你可能已经发现了它(尽管你可能不明白为什么!)。

此外,根本不需要做xor rdx, rdx!您可以通过执行xor edx, edx完成相同的任务,more efficiently


只是为了好玩,这里是一个可能的实现代码:

; Parameters: RDI == address of start of character string 
;    RCX == number of characters in string 
; Clobbers: RDX, RSI 
; Returns: result is in RAX 

    xor esi, esi 

convert: 
    ; See if we've done enough characters by checking the length of the string 
    ; against our current index. 
    cmp rsi, rcx 
    jge convert_end 

    ; Get the next character from the string. 
    mov dl, BYTE [rdi + rsi] 

    cmp dl, "-" 
    je increment 

    cmp dl, "." 
    je convert_end 

    ; Efficient way to multiply by 10. 
    ; (Faster and less difficult to write than the MUL instruction.) 
    add rax, rax 
    lea rax, [4 * rax + rax] 

    sub dl, "0" 
    movzx rdx, dl 
    add rax, rdx 

    ; (fall through to increment---no reason for redundant instructions!) 

increment: 
    inc rsi   ; increment index/counter 
    jmp convert  ; keep looping 

convert_end: 
    ret 

(警告:这样做的逻辑是未经测试我只是改写了更优化的方式将现有的代码,而不bug)。