如何基于x86汇编实现指定规则的string_cmp字符串比较函数
x86汇编实现符合要求的string_cmp函数
需求回顾
实现函数int string_cmp(const char* str1, const char* str2),按ASCII字母序比较两个字符串,返回规则:
- str1 大于 str2:返回1
- str1 等于 str2:返回0
- str1 小于 str2:返回-1
原代码的问题
你的代码循环比较的逻辑框架是对的,但结果返回部分存在缺陷:sbb eax, eax只能生成0或-1,无法返回1,不符合需求。下面是修正后的完整可运行实现:
int string_cmp(const char* str1, const char* str2) { int result; asm volatile ( "cld\n" // 清除方向标志,让字符串指令自动递增指针 "1:\n" // 循环起始标签 "lodsb\n" // 从str1取字节到al,rsi自增 "scasb\n" // 比较al和str2的字节,设置标志位,rdi自增 "jne 2f\n" // 字节不等则跳转到分支处理 "test al, al\n" // 检查当前字节是否为字符串结束符 "jne 1b\n" // 未结束则回到循环继续比较 "xor eax, eax\n" // 字符串相等,结果设为0 "jmp 3f\n" // 跳转到结束点 "2:\n" // 字节不等的分支 "mov eax, 1\n" // 默认假设str1 > str2,设为1 "jb 2f\n" // 若CF=1(说明str1字节更小),跳转到子分支 "neg eax\n" // 子分支中把1取反为-1,对应str1 < str2 "2:\n" "3:\n" // 结束标签 : "=a" (result) // 输出:eax的值存入result : "S" (str1), "D" (str2) // 输入:str1放rsi,str2放rdi(直接用寄存器约束更规范) : "cc", "memory" // 告知编译器:标志寄存器被修改,内存可能被访问 ); return result; }
详细解释
1. 内联汇编约束说明
- 输入约束:
"S" (str1)和"D" (str2)直接让编译器把str1放入rsi、str2放入rdi,比手动mov指令更符合GCC内联汇编的规范,避免寄存器冲突问题。 - 输出约束:
"=a" (result)指定eax寄存器的最终值作为函数返回的result,因为x86系统调用和函数约定中,eax通常用来存返回值。 - 破坏列表:
"cc"告诉编译器这段汇编修改了标志寄存器;"memory"防止编译器对字符串内存做过度优化(比如提前缓存到寄存器,导致比较的不是原始内存值)。
2. 核心指令逐行解析
cld:必须执行的步骤,清除方向标志DF。当DF=0时,lodsb、scasb这类字符串操作指令会自动递增rsi/rdi指针;如果DF=1则会递减,这里我们需要从字符串开头向后遍历,所以必须清DF。1:\n:循环起始标签,1b表示向前跳转到最近的1标签,2f表示向后跳转到最近的2标签,这是x86汇编中标签跳转的常用写法。lodsb:将rsi指向的字节加载到al寄存器,然后rsi自动加1(因为cld),每次循环取str1的下一个字符。scasb:把al的内容和rdi指向的字节做比较,根据比较结果设置EFLAGS标志位(比如CF、ZF),然后rdi自动加1,每次循环取str2的下一个字符。jne 2f:如果scasb的结果是字节不相等(ZF=0),跳转到标签2处理不等的情况。test al, al:检查当前al里的字节是否为0(字符串结束符),test指令会设置ZF标志位(al=0时ZF=1)。jne 1b:如果al不是0(ZF=0),说明还没到字符串结尾,跳回循环开头继续比较下一对字符。xor eax, eax:如果循环正常结束(两个字符串同时到结尾),说明字符串完全相等,把eax设为0。jmp 3f:直接跳转到结束标签,避免进入不等分支。- 不等分支处理:
mov eax, 1:先默认设置结果为1,对应str1字符大于str2字符的情况。jb 2f:jb是基于CF标志的跳转,当scasb比较出al(str1的字节)小于原rdi指向的字节时,CF会被设为1,此时跳转到子标签2。neg eax:如果没跳转,说明str1的字节更大,eax保持1;如果跳转过来,把eax的1取反成-1,对应str1小于str2的情况。
3:\n:结束标签,汇编执行到这里完成,eax里的结果通过输出约束传给result变量。
原代码的缺陷分析
原代码中用sbb eax, eax生成结果,这个指令的逻辑是:如果CF=1,sbb eax,eax会得到-1;如果CF=0,得到0。但这只能覆盖相等(0)和小于(-1)的情况,无法返回1,所以必须添加判断逻辑区分“大于”和“小于”的场景,这就是修正代码的核心改动点。
内容的提问来源于stack exchange,提问作者Deezel
相关产品推荐
相关产品推荐

