0

我是汇编语言 (TASM 86x) 的初学者,正在处理我的第一个程序任务。它本质上并不复杂,但是作为这种语言的新手,我很难弄清楚一个简单的冒泡排序。

到目前为止,我只编写了 Witch C++,总体上最难的部分是掌握语法。

任务是获取任何字符串(由用户输入)并按 ASCII 值升序重新排列它(如,如果您输入 beda 它应该给出 abde)

我不确定我的输出,但这应该在排序完成后出现,我很困惑,因为它只允许我输入我的字符串,然后退出到命令提示符。无法追踪我在哪里犯了错误,它过早地指向了代码的末尾。

如果有经验丰富的人看看我的代码并指出正确的方向,甚至向新手解释一两件事,我将不胜感激

.model small
.stack 100h

.data
request     db 'Enter symbols:', 0Dh, 0Ah, '$'  

    buffer      db 100, ?, 100 dup (0)

.code

start:
    MOV ax, @data                   
    MOV ds, ax                      

; request
    MOV ah, 09h
    MOV dx, offset request
    int 21h

; read string                    ;reading string to buffer
    MOV dx, offset buffer           
    MOV ah, 0Ah                     
    INT 21h                         
    MOV si, offset buffer           


    INC si                        ;going from buffer size to actual length 
                                  ;of the string
    MOV cl, [si]              ;string length - loop counter
    mov ch, [si]                  ;string length - loop counter
    mov bl, [si]                  ;bl will be used to reset inner loop counter 
    DEC cl                        ;correcting the values, since count goes
    dec ch                        ; from 0 to n-1 instead of 1 to n

    inc si                        ;moving to strings first byte


outer:                            ;outer loop

    dec ch                        ;decrease counter each pass
    jz ending                     ;when counter reaches 0 end program
    mov cl, bl                    ; reset inner loop counter value


inner:                            ;inner loop
    mov al,byte ptr[si]           ;assigning byte(sybol) to al
    mov ah, byte ptr[si+1]        ;assigning following byte(symbol) to ah
    cmp al,ah                     ;compare the two
    jle after_switch              ;if the latter's value is higher, no need to switch

开关有问题,不确定它是否可以在组装中正常工作

    mov bh, al           ;main problem-switching values, tried a few different   
    mov al, ah           ;ways of doing it (will show them below), but to no avail
    mov ah, bh           ;using familiar C syntax

    jmp output           ;outputing the value

after_switch:         ;no switch needed

在外部开关的某个地方应该跳转到输出,但是我无法弄清楚在不弄乱序列的其余部分的情况下包含它的方法

    inc [si]              ;going to the next byte
    dec cl                ;decreasing inner loop counter
    jnz inner             ;back to the beginning of inner loop until counter reaches 0 (resetting in the outer loop)
    jmp outer             ;if counter reaches zero, get back to outer


output:             ;outputting value from the very first bit 
    mov ah, 2
    mov dl, al          ;which after switch is supposed to be stored in al
    int 21h
    jmp inner           ;returning to inner loop to run next course of comparison

ending:
    MOV ax, 4c00h               
    INT 21h                              
end start

以前尝试过的内循环切换方法

    mov al,[si+1]
    mov byte ptr[si+1],[si]
    mov byte ptr[si], al

返回非法内存引用错误,但是这个问题过去在这个板上已经回答过,找到了。

尝试了相同的方法,但使用了 dx:di 寄存器

    mov al, byte ptr[si+1]
    mov dx:[di], [si]
    mov byte ptr[si+1], dx:[di]
    mov byte ptr[si], al

返回非法覆盖寄存器错误,在上面找不到任何东西

4

3 回答 3

1

逻辑错误

mov al, byte ptr[si+1]
mov dx:[di], [si]             <<-- there is no dx:[di] register.
mov byte ptr[si+1], dx:[di]   <<-- memory to memory move not allowed.
mov byte ptr[si], al          <<-- `byte ptr` is superflous, because `al` is already byte sized.

您可以在此处使用段寄存器,但因为您只使用ds段,所以不需要这样做。

所以这将是有效的:

mov DS:[di],si   <-- ds segment (but DS is already the default)

请注意,不允许内存到内存移动,数据必须来自常量:

mov [di],1000    <-- direct assignment using a constant

或者必须通过寄存器

mov ax,[di]
mov [si],ax      <--- memory to memory must be a 2 step process.

记住[reg]就是记忆;reg是一个寄存器
另一个错误在这里:

inc [si] <<-- error, increases some memory location, not `si` the pointer.
dec cl                ;decreasing inner loop counter
jnz inner             ;back to the beginning of inner loop until counter reaches 0 (resetting in the outer loop)
jmp outer             ;if counter reaches zero, get back to outer

这应该是:

inc si                ;next char in the string
dec cl                ;decreasing inner loop counter
jnz inner             ;back to the beginning of inner loop until counter reaches 0 (resetting in the outer loop)
jmp outer             ;if counter reaches zero, get back to outer

简化
翻转两个值不需要movs。替换此代码:

mov bh, al           ;main problem-switching values, tried a few different   
mov al, ah           ;ways of doing it (will show them below), but to no avail
mov ah, bh           ;using familiar C syntax

使用这个更简单的变体,在过程中保存一个寄存器:

xchg ah, al           ;flip chars around

完成翻转后alah您需要将它们写回内存,否则所有这些工作都是徒劳的。

xchg ah, al           ;flip chars around
mov [si],al           ;save flipped values
mov [si+1],ah         

读到字符串的末尾
在这个片段中,您有正确的想法,但是因为您一直[si]运行到字符串的末尾,所以您读取的 1 个字节太多了。

inner:                            ;inner loop
  mov al,byte ptr[si]           ;<<-- both are correct, but [si+1] will read
  mov ah, byte ptr[si+1]        ;<<-- past the end of the string at the last byte  

因此,您需要更改此部分:

DEC cl                     ;correcting the values, since count goes
dec ch                     ; from 0 to n-1 instead of 1 to n

对此:

sub bl,2                   ;if bl is used to reset inner loop counter it must be
                           ;adjusted as well.
;sub cl,2                  ;inner loop from 0 to n-2
mov cl,bl                  ;bl=cl, so a mov makes more sense
dec ch                     ;outer loop from 0 to n-1 instead of 1 to n

最后的效率提示
如果你没有必要,永远不要从内存中读取。更改此代码:

MOV si, offset buffer           
INC si                        ;going from buffer size to actual length 
                              ;of the string
MOV cl, [si]              ;string length - loop counter
mov ch, [si]                  ;string length - loop counter
mov bl, [si]                  ;bl will be used to reset inner loop counter

对此:

MOV si, offset buffer+1       ;start at length byte of the string    
MOV cl, [si]                  ;string length - loop counter
mov ch, cl                    ;string length - loop counter
mov bl, cl                    ;bl will be used to reset inner loop counter

可以进行更多速度优化,但我不想让事情变得过于复杂。

于 2013-10-16T00:14:57.143 回答
0

如果我们想将一个立即数写入内存位置,那么我们需要指定我们要访问多少字节,只有一个字节、一个字或一个双字。

mov word [di],1000    <-- direct assignment using a constant (immediate value)

短剑

于 2014-02-27T11:37:19.393 回答
0

对于速度优化,xchg 指令在所有 x86 上都不是非常快的指令。所以我更喜欢使用第三个寄存器来切换值并用它替换 xchg 指令,因为简单的 mov 指令可以更好地与指令管道的预取输入队列中的其他简单指令配对,并且 CPU 可以更轻松地翻译简单指令微操作(μops)。

考虑到每个流水线的预取输入队列中的阶段数,其中每条指令都会像获取、解码和存储一样通过,我们可以在我们的 mov 指令之间放置一些其他指令(与其他指令没有依赖关系)用于切换值,用于防止执行时停顿。

放置说明以防止失速的示例:

(注意:并非所有 x86-CPU 在其管道中使用完全相同数量的执行阶段,但原理图相似。)

指令放置不当会导致停顿:

mov eax,value1
add eax,value2 ; reading after writing the same register = results a stall
mov ebx,value3
add ebx,value4 ; stall
mov ecx,value5
add ecx,value6 ; stall
mov edx,value7
add edx,value8 ; stall
mov esi,value9
add esi,value10 ; stall
mov edi,value11
add edi,value12 ; stall
mov ebp,value13
add ebp,value14 ; stall

更好的说明放置:

mov eax,value1 ; first instruction
mov ebx,value3 ; second instruction
mov ecx,value5 ; third ...
mov edx,value7 ; ...
mov esi,value9
mov edi,value11
mov ebp,value13
; no stall, because the execution progress of the first instruction is fully complete
add eax,value2
add ebx,value4 ; ... of the second instruction is fully complete
add ecx,value6 ; ... of the third instruction is fully complete
add edx,value8 ; ...
add esi,value10
add edi,value12
add ebp,value14

短剑

于 2014-02-27T11:15:23.470 回答