memcmp函数的功能非常简单,传入两个指针s1和s2,以及要比较的字节大小n,比较这两块内存的值的差异(逐字节比较,把每个字节都翻译为unsigned char)。当比较第i位时,如果相等,则返回0, 否则返回不相等的字节的差值(s1i-s2i).
这个问题,本来是可以无脑的写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()
由于我们的编译器支持__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