【问题标题】:How to print a triangle of stars in assembly?如何在组装中打印三角形的星星?
【发布时间】:2018-08-26 19:42:00
【问题描述】:

我需要得到以下输出:

*
**
***
****
*****
******
*******
********
*********
**********

所以它有 10 行,我的星星将从 1 开始到 10。

目前我得到:

**********
***********
************
*************
**************
***************
****************
*****************
******************
*******************
********************

我的代码:

section .data

char db ' '
trianglesize db 0;      ;stars per line
trianglerows db 10;

section .text
global _start
_start

mov rax, [trianglerows] ;rows
outer_loop:
    mov rbx, [trianglerows]
    inner_loop:
    call star
    dec bx
    cmp bx,0
    jg inner_loop
call newline
call down_triangle
dec ax
cmp ax, 0
jne outer_loop
call newline
call exit

exit:
  mov eax,1 ;sys_exit
  mov ebx,0     ;return 0
  int 80h;
  ret

newline:
  mov [char],byte 10
  push rax;
  push rbx;
  mov eax,4;    ;sys_write
  mov ebx,1;    ;stdout
  mov ecx, char;
  mov edx,1;    ;size of new line
  int 80h

  pop rbx;
  pop rax;
  ret

star:
  mov [char], byte '*';
  push rax;
  push rbx;
  mov eax,4;    ;sys_write
  mov ebx,1;    ;stdout
  mov ecx, char;
  mov edx,1;
  int 80h;
  pop rbx;
  pop rax;
  ret

down_triangle:
  push rax;
  push rbx;

  mov rax, [trianglerows]
  inc ax
  mov [trianglerows],rax

  pop rbx
  pop rax
  ret

我尝试了又尝试了,但我无法得到我需要得到的东西。

由于所有这些pushpop,我似乎无法找到将行与星行分开的方法。

老实说,我不太了解这些。我被告知执行循环需要它们,但我不确定为什么,例如,在函数star 中我需要调用外部循环。

我找不到任何有效的 pushpop 组合。我经常得到很多星或每行一颗星或只有一颗星。

我真的很困惑我正在更改哪些位并保持不变。我能够获得所需的输出,但输出永远不会增加。

我能够获得从 10 星开始下降到 1 星的输出,但不是我想要的。

我做错了什么?这道题怎么做?

【问题讨论】:

  • 使用调试器来查看自己更改了哪些位以及更改的位置。使用instruction reference guide 阅读您使用的每条指令,以交叉检查您在调试器中观察到的内容。如果您在第一次尝试时没有完全理解所有内容,请不要担心,请继续重新阅读和重新检查。另外,尝试找到更好的教程/书籍作为开始。您可以从 MASM tutorial 开始,然后 NASM/YASM 在语法上有一些差异,但这在 NASM 文档中有所介绍:nasm.us/doc
  • 顺便说一句,如果你是个厚脸皮的混蛋,你可以用定义为starline: db '**********',10(即10个星和换行符)的单个字符串输出10行三角形,输出(sys_write)从第一行的地址 starline+9 到最后一行的 starline+0,第一行只有 2 个字符,然后是 3 个字符,等等……或者为了更有趣,您可以先将整个多行字符串生成到内存中然后以单写方式输出,等等……有数百万种方法。继续努力。
  • @Ped7g 这真的不是我第一次尝试,我解释过。这周的大部分时间我都在尝试这样做。这就是给我的所有工作和理解。我正在使用 Linux 终端来执行此操作,那里有什么调试器可用..?我也必须使用循环来实现这一点。我在这里寻求实际的帮助,究竟是什么导致我的代码出现故障?
  • 另外现在我注意到你在 64b 模式下使用 int 0x80,这是非法滥用 linux 内核仁 stackoverflow.com/q/46087730/4271923 所以你的 "all that were given to me"包含错误信息,或者您没有正确遵循它。另外,在尝试学习汇编代码时,仅遵循您的官方资源是荒谬的。使用你能找到的任何东西(你也会发现很多 BS,所以继续大圈运行并同时检查几件事情,过滤掉没有希望/低质量的东西)。调试器:gdb 是基于文本的(需要时间来学习控制)
  • 将其用于更多的指针和资源:stackoverflow.com/tags/x86/info .... 编辑:顺便说一句,至少有一些帮助,我会告诉你至少第一条指令你有错误执行后你的代码。第一个错误在mov rax, [trianglerows](将加载 8 字节值而不是定义的单个字节,使用而不是 movzx eax,byte [trianglerows] 将 8 位值零扩展为 32 位 eax,这将自动清除 rax 的高 32 位,因为这就是 x86_64 的工作方式)。 ……没多久……嗯。

标签: assembly nasm x86-64 yasm


【解决方案1】:

您的第一行有 10 颗星,因为您在内循环中使用了 [trianglerows]。我确定您打算使用[trianglesize](目前您没有在任何地方使用)。然后在down_triangle 中,您需要再次增加[trianglesize] 而不是[trianglerows]。最后,您可能希望[trianglesize] 以 1 而不是 0 开头,因为第一行中有 1 颗星。

此外,请务必按照 Michael Petch 在下面的 cmets 中的描述更正您的内存使用情况,否则您的变量将被损坏,因为它们共享相同的内存。

【讨论】:

  • 他还必须解决使用 1 字节数据定义的标签并读取 8 字节的问题。如果他开始使用trianglesize,这将非常重要。
  • @MichaelPetch 有趣。我最后一次使用汇编是在 1989 年,当时是 6502! ;-) 但我发现这是一个有趣的谜题,并且得到了看似合乎逻辑的答案,因此认为值得贡献。不过,魔鬼在细节中!听起来你在说如果他从trianglesize 读取数据,他将读取还包含来自trianglerows 的“10”的数据,因此他仍然不会获得正确数量的星。 (也许第一行有 2561 颗星?换句话说,如果 trianglesize 包含 1,则为十六进制 A01。)正确吗?
  • 正确,因此他应该将这些内存位置设为四字(8 字节值)或修改他的代码以读取和处理单字节值。
  • 它仍然没有完全修复,因为他还使用了错误的系统调用(例如在 win10 linux 机器中它会崩溃,而正确的 64b linux 二进制文件会正确打印三角形)等等......它不是甚至值得正确修复他的代码,因为它有很多弱点,但是您的问题确实缩小了星星数量错误的主要问题。但是 OP 需要先学习一些组装并获得一些像样的讲师或书籍,你不能通过修补其中的两个来修复有 50 个孔的溺水船...... :/ 不幸的是,没那么简单。 (一如既往,我听起来很消极。OP:在你所在的地方没关系,推!)
  • @Ped7g :系统调用问题是次要的。由于 OP 没有使用可以用 32 位表示的范围之外的地址,因此如果 IA32 仿真在带有int 0x80 的内核中,则代码将起作用。这不是首选,但它将在给定的一组约束内工作。由于 OP 正在获得输出,因此可以假设他在支持 IA32 仿真的 64 位系统上。因此,Sharon 专注于语义和逻辑问题将对 OP 具​​有更大的价值。 OP 可以专注于此,然后进行清理。
【解决方案2】:

我通过这种方式解决了问题,它是 32 位的:

bits 32
global _start

section .data
    rows dw 10

section .text
_start:
movzx ebx, word [rows] ; ebx holds number of rows, used in loop

; here we count how many symbols we have
lea eax, [ebx+3]
imul eax,ebx
shr eax,1 ; shr is used to divide by two
; now eax holds number of all symbols
mov edx, eax ; now edx holds number of all symbols, used in print

;we prepare stack to fill data
mov ecx,esp
sub esp,edx

;we fill stack backwards
next_line:
    dec ecx 
    mov [ecx],byte 10
    mov eax,ebx
    next_star:
        dec ecx
        mov [ecx],byte '*'
        dec eax
        jg next_star
    dec ebx
    jg next_line

;print ; edx has number of chars; ecx is pointer on the string
mov eax,4;  ;sys_write
inc ebx;    ;1 - stdout, at the end of the loop we have ebx=0
int 80h;

;exit
mov eax,1       ;1 -  sys_exit
xor ebx,ebx     ;0 - return 0
int 80h;
ret

我是怎么做到的?
首先,我计算我们必须打印的符号数量。我会一次性打印出来。它是有限的arithmetic progression(算术级数)的总和。

在我们的例子中

我们看到 3 个操作 +*/。我们只能优化除以 2,做右移:

lea eax, [ebx+3] ; n + 3
imul eax,ebx ; n * (n + 3)
shr eax,1 ; n * (n+3) / 2

我们的字符串将在堆栈上,让我们准备它有足够的内存:

mov ecx,esp
sub esp,edx

然后,我们用星号和\ns 填充我们的堆栈

next_line:
    dec ecx 
    mov [ecx],byte 10
    mov eax,ebx
    next_star:
        dec ecx
        mov [ecx],byte '*'
        dec eax
        jg next_star
    dec ebx
    jg next_line

我向后填充。这是什么意思?我用符号从头到尾填充字符串。我为什么要这样做?只是因为我想尽可能少地使用寄存器。在循环结束时ecx 包含一个指向我们要打印的字符串的指针。如果我向前填充,ecx 在“堆栈准备”之前包含esp 上的指针,并且我不能将寄存器用作print 函数中的字符串指针。另外我必须使用另一个寄存器来递减或使用比dec慢的cmp

就是这样,打印并结束。


另一个案例

global _start

section .data
    rows dw 10

section .text
_start:

;it defines how many symbols we have to print
movzx ebx, byte[rows] ; ebx holds number of rows
lea eax,[ebx+3]
imul eax,ebx 
shr eax,1 ; now eax holds number of all symbols
mov edx,eax ; now edx holds number of all symbols, used in print

;prepare pointer
mov ecx,esp
sub ecx,eax ; ecx points on the beginning of the string, used in print

;fill the string by stars
mov eax,edx
shr eax,2
mov ebp, dword '****'
next_star:
    mov [ecx+4*eax],ebp
    dec eax
    jge next_star

;fill the string by '\n'
mov edi,esp
dec edi
mov eax,ebx; in the eax is number of rows
inc eax
next_n:
    mov [edi],byte 0xa
    sub edi,eax
    dec eax
    jg next_n

;print
;mov ecx,esp
mov eax,4;  ;sys_write
mov ebx,1;  ;1 - stdout 
int 80h;

;exit
mov eax,1       ;1 -  sys_exit
xor ebx,ebx     ;0 - return 0
int 80h;
ret

在这里,一开始我们用星号填充堆栈,然后才用\ns 填充它

https://github.com/tigertv/stackoverflow-answers

【讨论】:

  • 您的程序依赖于eaxecx 的高2 字节,在进入_start 时为零。这在 Linux 上仅适用于静态可执行文件,但 ABI 不保证,因此您绝对应该在注释中注意这一点。或者像普通人一样使用movzx ecx, word [trianglerowchars]。或者更好的是,xor ecx,ecx 在外循环之外将 ecx 归零,并且不要对计数器使用任何静态存储。 (对trianglerowsmov eax, trianglerows 使用equ 常量。)
  • 并使用 int 0x80 ABI 不使用的寄存器,因此您不必保存/恢复。也请不要建议the slow loop instruction,尤其是当问题中的代码不使用它时。你不会推荐像 xlatb 这样的旧的奇怪指令,而 loop (不幸的是)就是这样:一个复杂的慢指令,仅在优化代码大小而不是速度时才有用。
  • @PeterCordes:谢谢,我不知道loop 是慢功能。替换了eaxebx。我用过xormovzx
  • 使用lea / imul / shr,只需3条指令即可完成n * (n+3)/2See my answer on a triangle-matrix question,我可以优化掉shr,因为我希望n*(n+1)/2 * 4 索引双字。请注意,2 操作数 imulmul 快;除非您想将 EDX 归零(已知操作数很小)或优化代码大小而不是性能。
  • 是的,这就是为什么你仍然需要shr,就像我在第一句话中所说的,元素大小为 4 是我能够优化它的原因,但你不能。不过,使用leaimul 仍然是一种优化,而不是使用包括mul 在内的4 条指令。 lea 将您的 mov eax,ebx / add eax,3 替换为 lea eax, [ebx+3]
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-12-08
  • 1970-01-01
  • 1970-01-01
  • 2019-02-27
  • 2016-02-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多