为什么这个 asm strcmp() return 错误的值

why does this asm strcmp() return wrong values

我正在尝试学习一些汇编(英特尔语法,x86_64)。 我写这段代码是为了简单地实现 strcmp():

section .text
    global ft_strcmp

ft_strcmp:
    cmp byte [rsi], 0
    je  exit
    mov cl, byte [rsi]
    cmp byte [rdi], cl
    jne exit
    inc rsi
    inc rdi
    jmp ft_strcmp

exit:
    xor rax, rax
    mov al, [rdi]
    sub al, byte [rsi]
    ret

但是通过调用 ft_strcmp("Hello", "Hellooooo") return 145 来尝试,而真正的 strcmp() return -1,我似乎不能弄清楚为什么。 是我的语法有误,还是我尝试这样做的方式?

您还需要检查 [rdi] 是否为空。第二个字符串可能比第一个短。

strcmp 应该 return eax 中的 32 位 int 根据第一个字符串是更大还是更小,它是正数还是负数。通过执行 8 位减法,eax 的高 24 位保持为零,因此当作为有符号整数查看时,结果为正数。

你想做一个 32 位减法,所以你需要将 32 位寄存器中的两个字节的高 24 位置零。这是有效的 movzx:

exit:
    movzx eax, byte [rdi]
    movzx ecx, byte [rsi]
    sub eax, ecx
    ret

如果你不知道movzx,你可以将整个寄存器置零,然后加载低字节:

exit:
    xor eax, eax
    mov al, [rdi] ; 'byte' is unnecessary, operand size inferred from register al
    xor ecx, ecx
    mov cl, [rsi]
    sub eax, ecx
    ret

(作为旁注,像xor rax, rax这样的归零指令可以用更小但效果相同的xor eax, eax代替:Why do x86-64 instructions on 32-bit registers zero the upper part of the full 64-bit register?

你是对的,第二个字符串 nul 检查不在比较操作中,太棒了!

考虑以下几点:

内存引用越少越好: 您的代码在循环中有 3 个数据内存引用,在退出时有 2 个 (3N+2)。 此代码在循环中有 2 个数据内存引用,在退出时有 0 个(2N)。

指令字节越少越好: 您的代码有 37 个字节的循环代码和 14 个字节的退出代码。 此代码有 20 个字节的循环代码和 3 个字节的退出代码。

一个技巧是使用return值寄存器作为工作寄存器之一。另一个技巧是将操作数移动到寄存器中,然后在寄存器中进行操作。

编译完成于 https://defuse.ca/online-x86-assembler.htm#disassembly

代码未经测试。

ft_strcmp:
    movzx cl,[rsi]      #[2] get byte(1) from 1st string
    movzx al,[rdi]      #[2] get byte(2) from 2nd string
    test cl,cl          #[2] end of first string, is it nul?
    je exit             #[2] 
    cmp cl,al           #[2] compare byte(1) w/ byte(2)
    jne exit            #[2] differ?
    inc rsi             #[3] point to next byte
    inc rdi             #[3] ditto
    jmp ft_strcmp       #[2] test them
                        #[20] bytes of instructions in loop
exit:
    sub al, cl          #[2] generate return value
                        # if neither null; return difference of values
                        # if byte(1) is null and byte(2) is null; ret 0
                        # if byte(1) is null and byte(2) is not null; return positive
                        # if byte(1) is not null and byte(2) is null; return negative 
    ret                 #[1]
                        #[3] bytes of instructions in exit
    
    
ft_strcmp:
    cmp byte [rsi], 0   #[7]
    je  exit            #[2]
    mov cl, byte [rsi]  #[6]
    cmp byte [rdi], cl  #[6]
    jne exit            #[2]
    inc rsi             #[6]
    inc rdi             #[6]
    jmp ft_strcmp       #[2]
                        #[37] bytes of loop code
exit:
    xor rax, rax        #[2]
    mov al, [rdi]       #[5]
    sub al, byte [rsi]  #[6]
    ret                 #[1]
                        #[14] bytes of exit code

直接实施 - 使用 Compiler Explorer :

strcmp:
        xor     ecx, ecx
.L3:
        movzx   eax, BYTE PTR [rdi+rcx]
        movzx   edx, BYTE PTR [rsi+rcx]
        cmp     al, dl
        jne     .L2
        inc     rcx
        test    al, al
        jne     .L3
.L2:
        sub     eax, edx
        ret

字节正确地进行了零扩展,因此 return 值是 unsigned 字符的差异,结果提升为 int.

正如其他人所指出的,对于 C 字符串可能具有任意长度的 C 库实现,可以使用机器字或 SSE 指令,前提是设置成本被分摊到某个长度阈值。