String 8086中的字符串比较
我对这个问题有意见。我不知道它想从我这里得到什么 问题:编写一个过程,将DS:SI处的源字符串与ES:DI处的目标字符串进行比较,并相应地设置标志。如果源小于目标,则设置进位标志。如果字符串相等,则设置零标志。如果源大于目标,则清除零和进位标志 我的答覆是:String 8086中的字符串比较,string,assembly,x86,x86-16,strcmp,String,Assembly,X86,X86 16,Strcmp,我对这个问题有意见。我不知道它想从我这里得到什么 问题:编写一个过程,将DS:SI处的源字符串与ES:DI处的目标字符串进行比较,并相应地设置标志。如果源小于目标,则设置进位标志。如果字符串相等,则设置零标志。如果源大于目标,则清除零和进位标志 我的答覆是: MOV ESI , STRING1 MOV EDI, STRING2 MOV ECX, COUNT CLD REPE CMPSB 我还是不确定。这是真的还是我应该试试别的 p、 s:我不明白为什么人们投票否决这个问题。我的问题怎么了?我想
MOV ESI , STRING1
MOV EDI, STRING2
MOV ECX, COUNT
CLD
REPE CMPSB
我还是不确定。这是真的还是我应该试试别的
p、 s:我不明白为什么人们投票否决这个问题。我的问题怎么了?我想我们都是来学习的。还是不?错过了什么吗?如果问题陈述说当你被调用时指针已经在
SI
和DI
中,你不应该碰它们
对于所有函数,16位代码通常不遵循单一调用约定,在寄存器中传递(前几个)参数通常是好的(指令更少,并避免存储/重新加载)。32位x86调用约定通常使用堆栈参数,但这已经过时。Windows x64和Linux/Mac x86-64 System V ABIs/调用约定都使用寄存器参数
然而,问题陈述没有提到计数因此,您正在为以零字节结尾的字符串实现
strcmp
,而不是为已知长度的内存块实现memcmp
。您不能使用单个rep
指令,因为您需要检查字符串的结尾是否不相等如果您只是传递一些较大的字符串,并且字符串相等,repe cmpsb
将继续通过终止符。
repe cmpsb
如果知道任一字符串的长度,则可用。e、 g.在CX中取一个长度参数,以避免在两个字符串中都超过终止符的问题
但就性能而言,repe cmpsb
无论如何都不快(比如Skylake vs.Ryzen上每次比较2到3个周期,或者推土机系列上每次比较4个周期)。只有rep-mov
和rep-sto
在现代CPU上是有效的,优化的微码一次复制或存储16(或32或64)字节
内存中有2种主要的字符串:
字节的C0
,或使用char*
作为终止符的DOS字符串打印函数。)'$'
一个有用的观察结果是,您只需要检查其中一个字符串中的终止符。如果另一个字符串有终止符,而这个字符串没有,则将不匹配 因此,您希望将一个字节从一个字符串加载到寄存器中,并检查它是否为teminator,检查另一个字符串是否为内存 (如果您需要实际使用ES:DI而不是使用默认DS段基的DI,您可以使用
CMPAL[ES:bx+DI]
(NASM语法,根据需要进行调整,如CMPAL,ES:[bx+DI]
).可能是您打算使用lodsb
和scasb
的问题,因为。)
用法:将指针放在SI/DI中,调用strcmp
/je match
,因为匹配/不匹配状态为标志。如果要将条件转换为整数,386和更高版本的CPU允许sete al
根据e
quals条件(ZF==1)在al中创建0或1
使用sub-al[mem]
而不是cmp-al[mem]
,我们将得到al=str1[i]-str2[i]
,仅当字符串匹配时才给我们一个0。如果字符串仅包含0..127之间的ASCII值,则不会导致有符号溢出,因此可以将其用作有符号返回值,实际告诉您哪个字符串在另一个字符串之前/之后排序。(但是,如果字符串中可能有高ASCII 128..255字节,我们需要先将零或符号扩展到16位,以避免(无符号)5-(无符号)254=(有符号)+7之类的大小写由于8位环绕而出现有符号溢出
当然,通过我们的FLAGS返回值,调用者已经可以使用ja
或jb
(无符号比较结果),或者jg
/jl
,如果他们想将字符串视为包含有符号字符的字符串。无论输入字节的范围如何,这都是有效的
或内联此循环,因此jne不匹配
直接跳转到有用的地方
,但BX可以是基,SI和DI都可以是索引。我使用了索引增量,而不是inc SI
和inc DI
。使用lodsb
也是一个选项,甚至可能scasb
将其与其他字符串进行比较。(然后检查终止符。)
表演
在一些现代x86 CPU上,索引寻址模式可能会较慢,但这确实可以将指令保存在循环中(因此对于真正的8086来说,在代码大小很重要的情况下,这是很好的)。虽然要真正调整8086,我认为lodsb
/scasb
将是最好的选择,取代mov
加载和cmpal[mem]
,还有inc bx
。如果您的呼叫约定不能保证这一点,请记住在循环外使用cld
如果您关心现代x86,请使用movzx eax,byte[si+bx]
为不单独重命名部分寄存器的CPU断开对旧值eax的错误依赖。(如果使用sub al[str2],断开错误的dep尤其重要)
因为这将使它变成一个通过EAX的2周期循环的依赖链,在CPU上,而不是通过Sandybridge.IvyBridge和更高版本将AL与EAX分开重命名,所以mov AL,[me]
;; inputs: str1 pointer in DI, str2 pointer in SI
;; outputs: BX = mismatch index, or one-past-the-terminators.
;; FLAGS: ZF=1 for equal strings (je), ZF=0 for mismatch (jne)
;; clobbers: AL (holds str1's terminator or mismatching byte on return)
strcmp:
xor bx, bx
.innerloop: ; do {
mov al, [si + bx] ; load a source byte
cmp al, [di + bx] ; check it against the other string
jne .mismatch ; if (str1[i] != str2[i]) break;
inc bx ; index++
test al, al ; check for 0. Use cmp al, '$' for a $ terminator
jnz .innerloop ; }while(str1[i] != terminator);
; fall through (ZF=1) means we found the terminator
; in str1 *and* str2 at the same position, thus they match
.mismatch: ; we jump here with ZF=0 on mismatch
; sete al ; optionally create an integer in AL from FLAGS
ret
strcmp:
mov ecx,DWORD PTR [esp+0x4]
mov edx,DWORD PTR [esp+0x8] # load pointer args
L(oop): mov al,BYTE PTR [ecx] # movzx eax, byte ptr [ecx] would be avoid a false dep
cmp al,BYTE PTR [edx]
jne L(neq)
inc ecx
inc edx
test al, al
jnz L(oop)
xorl eax, eax
/* when strings are equal, pointers rest one beyond
the end of the NUL terminators. */
ret
L(neq): mov eax, 1
mov ecx, -1
cmovb eax, ecx
ret