简介

memcmp函数的功能非常简单,传入两个指针s1和s2,以及要比较的字节大小n,比较这两块内存的值的差异(逐字节比较,把每个字节都翻译为unsigned char)。当比较第i位时,如果相等,则返回0, 否则返回不相等的字节的差值(s1[i]-s2[i]).

实现

这个问题,本来是可以无脑的写c代码来逐字节比较的。但是嘛,为了能够更高效的实现,咱们就手写汇编来做吧。

我们使用repe和cmpsb这两条指令来实现。

repz指令是一个循环指令,每次循环会不断的递减rcx寄存器内的值,当rcx为0或处理器的zero flag不为1时,退出循环。

cmpsb指令则是对两个字节作比较的指令,在计算结束后,会设置相应的状态标志位。cmpsb指令涉及到的两个操作数分别存在rdi、rsi寄存器中。在操作结束后,如果这两个操作数的值相同,则会将ZF置位。不管这两个操作数的值是否相同,都会将指针s1、s2自增1。(下面的代码已经将DF复位,因此指针是自增的)

首先,我们将处理器的zero flag给置位,假设这两块内存相等。然后把len传入rcx寄存器,一共循环len次。

然后我们使用repz;cmpsb,逐字节比较。每次比较之后,不管是否相同,s1、s2都会自增。

如果比较完所有的字节,都是相同的话,此时ZF=1. 而输出nz到diff中,因此输出的是0.

如果某一字节不相同,那么diff=1。再在下面计算这两个字节到底相差了多少,然后就出结果了。

static inline int memcmp(const void *s1, const void *s2, size_t len)
{
    int diff;

    asm("cld \n\t"  // 复位DF,确保s1、s2指针是自增的
        "repz; cmpsb\n\t" CC_SET(nz)
        : CC_OUT(nz)(diff), "+D"(s1), "+S"(s2)
        : "c"(len)
        : "memory");

    if (diff)
        diff = *(const unsigned char *)(s1 - 1) - *(const unsigned char *)(s2 - 1);

    return diff;
}

关于CC_SET和CC_OUT

可能有的小伙伴会对CC_SET和CC_OUT比较疑惑,这里将涉及的代码放出来:

CC_SET()和CC_OUT()

由于我们的编译器支持__GCC_ASM_FLAG_OUTPUTS__,因此CC_SET的工作是空的。

然后,CC_OUT就是把rflags的某一位给输出出来。上面的nz就是对ZF求反的意思。

相关链接

https://pubs.opengroup.org/onlinepubs/9699919799/functions/memcmp.html

转载请注明来源:https://longjin666.cn/?p=1572

欢迎关注我的公众号“灯珑”,让我们一起了解更多的事物~