回文函数总是报告错误的偏移量#1

Palindrome function always reporting offset #1 for error

我正在编写一个 x86 汇编函数来确定一个字符串是否为回文(空终止符除外)。

这个函数的意思是 return 0 如果字符串是回文,如果字符串不是回文,它会 return 比较失败的(即字符在回文上的索引)留下不匹配的字符串的一半)。

虽然它成功地检测出哪些字符串是回文,哪些不是回文,但它总是报告 1 作为回文测试失败的索引,而不管它实际上在哪里失败。

汇编代码:

.386
.MODEL FLAT, C
.CODE
    ; Determines whether or not a given string is a palindrome
    ; Uses:
    ;   ECX - pointer to start of string (incremented till halfway)
    ;   EDX - pointer to end of string (decremented till halfway)
    ;   AL - dereference character from ECX for comparison
    ;   BL - dereference character from EDX for comparison
    ;   ESI - index where comparison failed in case strings are not palindromes
    ; Arguments:
    ;   [ESP+4] - pointer to string to test
    ;   [ESP+8] - length of string
    ; Returns:
    ; 0 = string is a palindrome
    ; > 0 = string is not a palindrome; return value is the # comparison that failed (e.g. AABAAA would return 3)
    ; C prototype: int __cdecl palin(char *str, int len);
    palin PROC
        push ebx
        push esi
        ; Load ECX with a pointer to the first character in the string
        mov ecx, dword ptr [esp+12]
        ; Copy the pointer into EDX then add the length so EDX points to the end of the string
        mov edx, ecx
        add edx, dword ptr [esp+16]
        xor esi, esi
        loop0:
            ; Begin loop with decrement of EDX to skip the null terminator
            dec edx
            inc esi
            mov al, byte ptr [ecx]
            mov bl, byte ptr [edx]
            cmp al, bl
            ; Comparison fail = strings cannot be palindromes
            jnz not_palindrome
            inc ecx
            ; If start ptr >= end ptr we are done, else keep looping
            cmp ecx, edx 
        jl loop0
        ; Return 0 = success; string is a palindrome
        xor eax, eax
        jmp end_palin
        not_palindrome: 
        ; Return > 0 = fail; string is not a palindrome
            mov eax, esi
        end_palin:  
            pop esi
            pop ebx
            ret
    palin ENDP
END

汇编函数的C驱动程序:

#include <stdio.h>
#include <string.h>

int __cdecl palin(char *str, int len);

int __cdecl main(int argc, char *argv[])
{
    int ret;
    if(argc<2) 
    {
        printf("Usage: pal word");
        return 0;
    }
    if(ret = (palin(argv[1], strlen(argv[1])) > 0))
    {
        printf("%s is not a palindrome; first comparison that failed was #%d\n",  argv[1], ret);    
    }
    else
    {
        printf("%s is a palindrome\n", argv[1]);
    }
    return 0;
}

示例输出:

C:\Temp>pal ABCDEEDCBA
ABCDEEDCBA is a palindrome

C:\Temp>pal ABCDEDCBA
ABCDEDCBA is a palindrome

C:\Temp>pal AABAAA
AABAAA is not a palindrome; first comparison that failed was #1

最后一行应该 return 3 而不是 1 - 有人知道这里发生了什么吗?

您的代码中几乎没有错误...您要找的错误就在这里:

if(ret = (palin(argv[1], strlen(argv[1])) > 0))

这应该会在良好的 C/C++ 编译器中发出警告,我想,你用的是什么?您是否使用 -Wall -Wextra(这些用于 gccclang,对于其他编译器,您应该检查它的文档)。

它在做ret = (res > 0),并且(res > 0)是布尔表达式,所以它是01

您可能想要 if ((ret = palin(argv[1], strlen(argv[1]))) > 0),这说明了为什么有时 KISS 并将这些内容分成两行会更好。


其他错误:

jl loop0:应该是jbecxedx 是内存指针,因此是无符号的。如果您的数据将分配在 0x80000000 边界上,那么 jl 首先会失败 cmp

并且你可以简化退出逻辑:

    ; Return 0 = success; string is a palindrome
        xor esi, esi    ; fake "esi" index = 0, reusing "not palindrome" exit code fully
    not_palindrome: 
    ; Return > 0 = fail; string is not a palindrome
        mov eax, esi
        pop esi
        pop ebx
        ret

最后的风格吹毛求疵:jnz not_palindrome => 我会为这个别名使用 jne 别名,因为你正在比较两个字符是否相等,而不是 "zero" (它是相同的指令,只是别名不同,我倾向于使用两者,尝试使用更合适的来遵循我对功能的"human"描述。

您也可以执行 cmp al,[edx] 而无需将第二个字符加载到 bl(节省 1 条指令而不破坏 ebx,因此您不需要 push/pop ebx 然后,再节省 2 个)。

如果您坚持只为 "easy to read" 代码将第二个字符加载到寄存器中,您仍然可以对第二个字符使用 ah,从代码中完全删除 ebx