在过去的几天里,我一直在学习程序集,我制作了一个简单的RPN计算器。
以下是程序的主要逻辑,不包括打印之类的实用程序功能:
%include "utilities.s"
SECTION .data
badOpMsg db "Unknown character: ", 0h
badNumMsg db "Error parsing number: ", 0h
badExprMsg db "Invalid expression supplied", 0h
SECTION .bss
userInput resw 500
SECTION .text
global _start
%macro preOp 0
cmp ecx, 2
jl .invalidExpr
; pop two values off of the "stack" and place them into esi and eax
dec ecx
mov esi, [userInput + ecx * 4]
dec ecx
mov eax, [userInput + ecx * 4]
%endmacro
%macro postOp 0
; put eax back onto the "stack" and return to the end of the parse loop
mov [userInput + ecx * 4], eax
inc ecx
jmp .endloop
%endmacro
_start:
mov ebp, esp
xor ecx, ecx ; parsed stack index
mov ebx, 2 ; argument list index
.parseLoop:
mov eax, [ebp + ebx * 4]
; Hack to allow numbers prefixed with '-'
; If current argument has a length of 1, treat it as an op/number
; otherwise treat it as a number
cmp byte [eax + 1], 0
jnz .parseNum
cmp byte [eax], 43 ; "+"
je .add
cmp byte [eax], 45 ; "-"
je .subtract
cmp byte [eax], 47 ; "/"
je .divide
cmp byte [eax], 120 ; "x"
je .multiply
cmp byte [eax], 48 ; "0"
jl .fail
cmp byte [eax], 57 ; "9"
jg .fail
.parseNum:
; otherwise, add number to the stack
; use edi to indicate atoi error
xor edi, edi
call atoi
cmp edi, 1
je .numError
mov [userInput + ecx * 4], eax
inc ecx
.endloop:
inc ebx
cmp ebx, [ebp]
jle .parseLoop
jmp .success
.add:
preOp
add eax, esi
postOp
.subtract:
preOp
sub eax, esi
postOp
.multiply:
preOp
imul eax, esi
postOp
.divide:
preOp
div si
postOp
.numError:
mov eax, badNumMsg
call sprint
mov eax, [ebp + ebx * 4]
call sprintln
jmp .end
.fail:
; An invalid character was supplied as input
push eax
mov eax, badOpMsg
call sprint
; Infringing character is on the top of the stack
pop esi
mov eax, [esi]
call putchar
call newline
jmp .end
.invalidExpr:
mov eax, badExprMsg
call sprintln
jmp .end
.success:
; If the stack has more than one item in it,
; the supplied expression is invalid
cmp ecx, 1
jne .invalidExpr
; Pop remaining value off the stack and print it
sub ecx, 1
mov eax, [userInput + ecx * 4]
call iprintln
.end:
call quit以下是“实用程序”的内容:
;----------------------------
; int strlen(String message)
strlen:
push ebx
mov ebx, eax
.nextchar:
cmp byte[eax], 0
jz .finished
inc eax
jmp .nextchar
.finished:
sub eax, ebx
pop ebx
ret
;----------------------------
; void sprint(String message)
sprint:
push edx
push ecx
push ebx
push eax
call strlen
mov edx, eax ; move string len from eax to edx
pop eax ; restore eax to string pointer
mov ecx, eax ; move string pointer to ecx
mov ebx, 1 ;
mov eax, 4 ; opcode 4
int 80h ; make syscall
pop ebx
pop ecx
pop edx
ret
;----------------------------
; void sprintln (String message)
sprintln:
call sprint ; print string found at eax
push eax ; preserve eax
mov eax, 0Ah
call putchar
pop eax
ret
;----------------------------
; void putchar (char)
putchar:
push edx
push ebx
push ecx
push eax ; eax has character
mov edx, 1
mov ecx, esp
mov ebx, 1
mov eax, 4
int 80h
pop eax
pop ecx
pop ebx
pop edx
ret
;------------------------------
; newline
newline:
push eax
mov eax, 0ah
call putchar
pop eax
ret
;-----------------------------
; void iprint(int)
iprint:
push eax
push ecx
push edx
push esi
push edi
xor edi, edi
xor ecx, ecx
test eax, eax
js .negate
.divide:
inc ecx
xor edx, edx
mov esi, 10
idiv esi
add edx, 48
push edx
cmp eax, 0
jnz .divide
cmp edi, 1
je .printNegative
.print:
dec ecx
mov eax, esp
call sprint
pop eax
cmp ecx, 0
jnz .print
jmp .end
.negate:
mov edi, 1
neg eax
jmp .divide
.printNegative:
push 45d
inc ecx
jmp .print
.end:
pop edi
pop esi
pop edx
pop ecx
pop eax
ret
;-----------------------
; iprintln
iprintln:
call iprint
call newline
ret
;----------------------------
; atoi
atoi:
push ecx
push esi
push ebx
mov esi, eax ; move string pointer to esi as eax will be used for math
mov eax, 0
mov ecx, 0
xor edi, edi ; negative flag and error flag
cmp byte [esi], 45 ; "-"
je .negative
.parse:
xor ebx, ebx
mov bl, [esi + ecx]
cmp bl, 10
je .terminated
cmp bl, 0
jz .terminated
cmp bl, 48 ; "0"
jl .error
cmp bl, 57 ; "9"
jg .error
sub bl, 48
add eax, ebx
mov ebx, 10
mul ebx
inc ecx
jmp .parse
.negative:
inc ecx
mov edi, 1 ; make sign negative
jmp .parse
.negate:
neg eax
mov edi, 0 ; because edi is both negative and error, make sure to clear it
jmp .end
.error:
mov edi, 1
jmp .end
.terminated:
mov ebx, 10
div ebx
cmp edi, 1
je .negate
.end:
pop ebx
pop esi
pop ecx
ret
;----------------------------
; void quit()
quit:
mov ebx, 0 ; exit code 0
mov eax, 1 ; opcode
int 80h
ret我主要想知道我的代码是否是惯用的。例如,我的寄存器选择是否正确?使用edi (我任意选择的寄存器)来指示解析错误是错误的做法吗?我应该这样使用.bss段吗?人们通常如何记录他们的功能?它们是否指定输入和输出寄存器?
请随时指出,无论如何,我的程序可以改进,包括我如何支持更大的数字和浮点。
编辑:代码现在支持负数。
发布于 2016-10-11 20:18:19
当我第一次看到call atoi时,我想您应该调用同名的C函数。这让我有点害怕,因为这个函数不应该在严肃的程序中使用,因为它没有提供正确的错误检查。因此,您应该选择另一个名称,特别是在Linux上编程时,在Linux上,您的代码的潜在读者熟悉该函数。
我喜欢在iprint中使用自终止字符串的技巧。此外,把字符按反序排列,然后打印它们的想法也很好,也很简单。如果您必须为每个系统调用进行斗争,那么分配一个12字节的缓冲区将更有效,无论是在内存使用上还是在系统调用数量上。
你是否缩进以支持负数?
您可以将字符串表示为(start,length)元组,而不是像C中那样使用以空结尾的字符串,这样就没有必要使用strlen函数。
https://codereview.stackexchange.com/questions/143921
复制相似问题