如何计算 i386 中字符串中某个字符的出现次数?

lea*_*est 1 x86 assembly i386

我是 80386 汇编语言的新手。目前正在努力完成一项学校作业,要求用汇编语言编写一个将在 ac 程序中调用的函数。

extern int count(char *string, char c);

我想我知道应该如何做到这一点,但仍在努力选择正确的指令(指令以“b”、“w”或“l”结尾),也许还有“正确”的寄存器,我知道有一些为某些目的而保留的。

.text
.global count

count:
    pushl   %ebp        # set up stack frame
    movl    %esp,%ebp   # save %esp in %ebp
    subl    $12, %esp   # automatic variables
    movl    $0, %eax    # initialize %eax to 0
    movl    8(%ebp), %esi   # pointer to s
    movb    12(%ebp), %bh   # pointer to c

check:
    movb    (%esi), %bl # move the first char in s to %bl
    cmp     0, %bl      # if the char is \0 
    je      done        # job is done

    cmp     %bh, %bl    # else compare the char to %bh
    je      found1      # if match increase the counter
    incb    %bl         # else move to next char
    jmp     check

found1:
    addl    $1, %eax    # found a match
    incb    %bl
    jmp     check       # go back to the beginning of check
    
done:
    movl    %ebp, %esp  # restore %esp from %ebp
    popl    %ebp        # restore %ebp
    ret

.end
Run Code Online (Sandbox Code Playgroud)

我对这个程序的理解是它应该将两个值(字符串和字符)的地址存储到两个寄存器中。然后逐个字符访问字符串,并将其与另一个寄存器中存储的字符进行比较。如果找到匹配,则增加 %eax 中的返回值,否则转到字符串中的下一个字符,直到\0到达末尾。

我的程序似乎陷入了循环,因为它不会崩溃,也不会输出结果。 在此输入图像描述

任何帮助将不胜感激。

在此输入图像描述

LSe*_*rni 5

我认为没有真正的理由保存%esp%ebp或从中减去%esp。你确实需要保存%esi。我认为a、b、c 和 d 寄存器可以安全地丢失,但如果没有(自从我使用汇编以来已经有一段时间了),您%ebx也需要保存。
(更新:正如 @NateEldredge 指出的那样,%ebx必须保留 - 我忘记更新堆栈指针。是的,它已经太长了)。

count:
    pushl   %esi             # save %esi as we use it
    pushl   %ebx
    # "In assembly language, all the labels and numeric constants used 
    #  as immediate operands (i.e. not in an address calculation like 
    #  3(%eax,%ebx,8)) are always prefixed by a dollar sign."
    #  https://flint.cs.yale.edu/cs421/papers/x86-asm/asm.html
    movl    12(%esp), %esi   # pointer to s
    movb    16(%esp), %bh    # char
    # I think it's more common "xor %eax, %eax"
    movl    $0, %eax         # initialize %eax to 0

check:
    movb    (%esi), %bl      # move the current char in s to %bl
    cmp     $0, %bl          # if the char is \0 
    je      done             # job is done

    cmp     %bh, %bl         # else compare the char to %bh
    je      found1           # if match increase the counter
    # We must increase the pointer to the character, not %bl
    incl    %esi             # else move to next char
    jmp     check
found1:
    addl    $1, %eax         # found a match
    # incb    %bl
    incl    %esi             # move to next char
    jmp     check            # go back to the beginning of check
done:
    popl    %ebx
    popl    %esi             # restore %esi
    ret

.end
Run Code Online (Sandbox Code Playgroud)

您还可以反转测试以保存一些说明:

    cmp     %bh, %bl         # else compare the char to %bh
    jne     notfound         # if not match, skip incrementing
    addl    $1, %eax         # found a match
notfound:
    incl    %esi             # move to next char
    jmp     check
Run Code Online (Sandbox Code Playgroud)

  • `ebx` 在 SysV ABI 中被调用保留,所以是的,应该保存它。 (3认同)
  • @LSerni:如果您好奇的话,我之前评论中的链接在一定程度上解释了性能影响。[Haswell/Skylake 上的部分寄存器究竟如何执行?写入 AL 似乎对 RAX 有错误的依赖性,并且 AH 不一致](/sf/ask/3196209761/)说明了将“mov”写入 8 位寄存器如何*不*独立于旧值Haswell/Skylake,造成了一个延迟瓶颈,其中无序执行可能会与以前的 CPU 上的迭代重叠,这些 CPU 与完整寄存器分开重命名了低 8 寄存器。 (2认同)