好的,所以我对装配很新,事实上,我对装配很新。我写了一段代码,它只是意味着从用户那里获取数字输入,乘以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 :(得分:8)
这里有一些将字符串转换为整数的函数,反之亦然:
; 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'
之外的字符)。例程也不处理带符号的数字。因此,如果您需要这些东西,您必须自己添加它们。
答案 1 :(得分:2)
字符串>数字的基本算法是:total = total*10 + digit
,从MSD开始。所以最左边/最高有效/第一个数字(在内存中,在阅读顺序中)乘以10 N次,其中N是后面的总位数。
这样做通常比在添加之前将每个数字乘以10的正确幂更有效。这需要2次乘法;一个增加10的幂,另一个增加到数字。 (或以10的递增幂查表表。)
当然,为了提高效率,您可以使用SSSE3 pmaddubsw
和SSE2 pmaddwd
将数字乘以其地方值并行:请参阅How to implement atoi using SIMD?。但是,当数字通常很短时,这可能不是一场胜利。当大多数数字只有几位数时,标量循环是有效的。
添加@ Michael的答案,让int->字符串函数停在第一个非数字,而不是固定长度可能很有用。这将捕获诸如字符串之类的问题,包括用户按下返回时的换行符,以及不将12xy34
转换为非常大的数字。 (将其视为12
,like C's atoi
function)。 stop字符也可以是C隐式长度字符串中的终止0
。
我也做了一些改进:
除非您针对代码大小进行优化,否则不要使用the slow loop
instruction。只是忘记它存在并使用dec
/ jnz
,如果仍然是你想做的倒计时,而不是比较指针或其他东西。
2 LEA说明明显优于imul
+ add
:延迟较低。
将结果累积到我们想要返回它的EAX中。 (如果您将其内联而不是调用它,请使用您想要结果的任何寄存器。)
我更改了寄存器,因此它遵循x86-64 System V ABI(RDI中的第一个arg,在EAX中返回)。
移植到32位:这根本不依赖于64位;只需使用32位寄存器即可将其移植到32位。 (即将rdi
替换为edi
,rax
替换为ecx
,将rax
替换为eax
。注意32和64位之间的C调用约定差异,例如EDI是调用保留的,并且args通常在堆栈上传递。但是如果你的调用者是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
; skew 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'
你想检测一些其他非数字字符的结尾。如果您的输入是显式长度字符串,则需要使用循环计数器而不是检查终结符(如@ Michael的答案),因为内存中的下一个字节可能是另一个数字。或者它可能位于未映射的页面中。
使第一次迭代特殊并在跳转到循环的主要部分之前处理它被称为loop peeling。剥离第一次迭代允许我们特别优化它,因为我们知道总数= 0因此不需要将任何数字乘以10.这就像从sum = array[0]; i=1
而不是{{1}开始一样}。
为了获得nice loop structure (with the conditional branch at the bottom),我使用了跳转到循环中间进行第一次迭代的技巧。这甚至不需要额外的sum=0, i=0;
因为我已经在剥离的第一次迭代中分支了。
解决在非数字上退出循环问题的简单方法是在循环体中有一个jmp
,就像在{{1}之前的C中的jcc
语句一样}。但是,我需要一个if() break;
并在循环中有2个总分支指令,这意味着更多的开销。
如果我不需要total = total*10 + digit
结果作为循环条件,我可以使用jmp
将其作为LEA的一部分来执行。但是在Sandybridge家族的CPU上会有made the LEA latency 3 cycles instead of 1。 (3分量LEA与2或更少。)两个LEA在sub ecx, '0'
(lea eax, [rax*2 + rcx - '0']
)上形成一个循环承载的依赖链,所以(特别是对于大数),它不值得英特尔。在eax
不快于total
(Bulldozer-family / Ryzen)的CPU上,如果您有明确的长度作为循环条件并且不想检查数字,请确保所有
我首先使用movzx加载零扩展,而不是在将数字从ASCII转换为整数后执行此操作。 (必须在某些时候完成添加到32位EAX中)。通常,操作ASCII数字的代码使用字节操作数大小,如base + scaled-index
。但是这会在大多数CPU上产生对旧版RCX的错误依赖。
base + scaled-index + disp8
在mov cl, [rdi]
上保存1个字节,但在Nehalem / Core2上导致部分寄存器停顿,在PIII上更糟糕。精细on all other CPU families,即使是Sandybridge:它是AL的RMW,因此它不会将部分注册表与EAX分开重命名。但是sub al,'0'
不会导致问题,因为读取字节寄存器总是很好。它保存了一个字节(没有ModRM字节的特殊编码),所以我在函数的顶部使用了它。
有关更多优化内容,请参阅http://agner.org/optmize,{strong>以及x86 tag wiki中的其他链接。
标签维基也有初学者链接,包括一个FAQ部分,其中包含指向整数 - >字符串函数的链接,以及其他常见的初学者问题。
相关:How do I print an integer in Assembly Level Programming without printf from the c library? 与此问题相反,整数 - &gt; base10string。