计算字符串中特定char的出现次数

时间:2017-05-27 23:10:51

标签: assembly x86-16 tasm

我正在尝试制作一个执行此任务的混合程序(C ++和ASM),但我的ASM模块并没有像预期的那样工作。文本和字符加载在程序的C ++部分中。我甚至不确定我在哪里犯错误。

编辑:我使用的是Borland C ++和TASM编译器(DosBOX)。该程序显示错误的出现次数(尽管实际出现次数,但结果是相同的,但是随着我对程序的看似无关的改变而改变),或者喷出奇怪的字符(如向下箭头符号或笑脸)在数字应该在哪里 - 这是由在C ++中设置不正确的变量类型引起的。

实际问题肯定是由一些寄存器的值改变造成的,这些寄存器显然必须在程序之前和之后保持不变(正如David Wohlferd和许多其他人指出的那样 - 谢谢大家)。我查看了我们的导师给我们的文件,根据它们,这些寄存器是(对于C / C ++):DS,SS,SP,BP,SI,DI和标志寄存器,如果在过程中修改了方向标志。

以下是有效的更正代码:https://pastebin.com/JPxMxzmK

.MODEL          SMALL, C
.STACK          400h
.DATA
.CODE
public          CountChar

CountChar   PROC
                push            bp
                mov         bp, sp
                xor         bx, bx
                mov         si, [bp+4]
                mov         ah, [bp+6]

                Check:
                mov         dl, [si]
                cmp         dl, 0
                je          EndOfP
                cmp         dl, ah
                je          Increasing
                inc         si
                jmp         Check

                Increasing:
                inc         bx
                inc         si
                jmp         Check

                EndOfP:
                mov         ax, bx
                pop         bp
                ret

CountChar   ENDP

END

1 个答案:

答案 0 :(得分:2)

你实际上并没有说过出了什么问题。这使得很难确定“答案”可能是什么。但是我会对它采取一些措施(嘿,我需要业力)。

阅读你的代码,代码似乎没有任何“错误”(尽管我会做一些不同的事情)。但是,如果要与C交互,汇编程序必须遵循一些规则。最重要的一个规则是,如果更改某些寄存器,则负责按照找到它们的方式将它们放回去。你的代码违反了这条规则。

作为一个新手,这对你来说似乎有点混乱。毕竟,它不像你的C代码使用寄存器,对吧?除了您的C代码 使用寄存器。实际上,这基本上是C编译器的全部目的:将C代码(不使用寄存器)转换为汇编代码(确实如此)。

如果我们可以看到为调用CountChar的代码生成汇编程序代码,我们会看到2个push语句(将参数放在堆栈上),然后是call CountChar。但调用代码(可能)使用一些其他寄存器(如si)来保存其他值。你的CountChar例程不能踩到那些值,否则当CountChar退出时会发生奇怪的事情。

您可能会问:为什么调用例程在调用代码之前不会保存所有寄存器的值?它可能。但是每次调用函数时保存所有寄存器(并恢复所有寄存器)都会减慢速度。而且你所调用的例程完全有可能甚至没有使用所有寄存器,这会浪费时间而没有任何好处。

相反,决定这些事情的人会做出妥协:当调用函数时,调用者将假定某些寄存器在函数返回时保持不变。根据函数的定义方式,哪些寄存器可以改变一点点。您可能还没有碰到它,但有几组规则代码经常使用(cdecl,stdcall,pascal,fastcall等)。

正如Raymond所说,对于16位代码,cdecl说bp,si和di(以及DS,但我们不会去那里)必须由被调用者保留。当你编写C代码时,这一切都是为你完成的。但是当你编写汇编程序时,你必须知道(并遵循)这些规则。

这并不意味着你不能使用这些寄存器。只是如果你这样做,你必须保存旧值(例如使用push si)并在函数退出之前将其恢复(例如使用pop si)。当然,推送/弹出不是免费的,因此您可能希望在使用其中一个必须保存/恢复之前先使用所有其他寄存器。

由于这听起来像是家庭作业,我不会发布重新编写的代码(我还没有运行它的环境),但我会给你一些建议考虑:

  1. 不使用si(必须保留),而是使用cx(不支持)。
  2. 不要使用bx来保存计数(然后将值移至ax),而只需使用ax来保存计数。您可以使用bx来保留要搜索的字符。
  3. 在测试寄存器是否为零时,使用test dl, dl比使用cmp dl, 0(稍微)快一些。
  4. 查看这段代码:

    cmp         dl, ah
    je          Increasing
    inc         si
    jmp         Check
    
    Increasing:
    inc         bx
    inc         si
    jmp         Check
    

    如果您在inc si指令之前移动了cmp,会发生什么?然后你不必在两个地方拥有它:

    inc         si
    cmp         dl, ah
    je          Increasing
    jmp         Check
    
    Increasing:
    inc         bx
    jmp         Check
    

    但看看发生了什么。现在我们有2个跳转指令紧挨着。这看起来有点不必要吗?如果不是跳到Increasing上的je,而是Check,那么你会跳到jne怎么办?现在您的代码如下所示:

    inc         si
    cmp         dl, ah
    jne         Check
    
    inc         bx
    jmp         Check
    
  5. 如果不对评论进行说明,就不会检查汇编程序代码。这是一小段代码,它只是一个练习。但是你仍然应该养成这个习惯:

    inc         si      ; Position to next byte
    cmp         dl, ah  ; Is this the byte we are counting?
    jne         Check
    
    inc         bx      ; Found one
    jmp         Check
    

    即使是这样的微不足道的评论也会使代码方式更容易理解。

    当你从现在起几个月(或几年)回到这段代码,或者其他人必须拿起你的代码并尝试理解它时(至少有3个人今天使用你的代码),它会让生活变得更好更轻松。甚至(尤其是?)如果代码错误,则将注释显示为您的意图/期望。

  6. 这是我用你提供的信息做出的最佳答案。