如何比较 32 位字符和 32 位字符,内联汇编 C++

how to compare 32 bit char against 32 bit char in, inline assembely c++

我想比较两个 4 个字符的字符串。例如 "A"、"T"、"T"、"C" 反对 "A"、"T"、"T"、"c"。我已将这些字符存储在 C++ 的数组中,我想在一条指令中比较这两个词。此外,我不想使用循环进行比较。我如何将这些词存储在 "eax" 和 "ebx" 寄存器中并相互比较?

int _tmain()
{
char b[3],a[3];
b[0]='A',b[1]='T',b[2]='C',b[3]='G';
a[0]='A',a[1]='T',a[2]='C',a[3]='G';
__asm
{
    movzx eax,b[1]  //here i want to load b to eax
}
getchar();
return 0;
}

如果有其他在一条指令中比较两个单词的想法,请分享谢谢。

首先,您的阵列存在严重问题。您将数组定义为包含 3 个元素,但您尝试将 4 个元素填充到数组中。这真的很糟糕,会导致未定义的行为。

除此之外...放弃程序集! lib 函数将(在几乎所有情况下)执行您在汇编中可以执行的操作。换句话说 - 只需使用 memcmp

喜欢:

int main()
{
    char b[4],a[4];
    b[0]='A',b[1]='T',b[2]='C',b[3]='G';
    a[0]='A',a[1]='T',a[2]='C',a[3]='G';

    if (memcmp(a, b, sizeof(a)) == 0)
         printf("Equal\n");
    else
         printf("Different");

    return 0;
}

像这样:

asm{

mov eax,'A'
mov ebx,'C'

 cmp eax,ebx
 JAE input_a
** here you print that 'A' <= 'C'  **
jump endofMain
input_a:
** here you print that 'A' >= 'C'  **
endofMain: 
}
return 0;

我要说的是,在汇编中这样做是个坏主意。

您应该使用高级语言结构。这将使代码具有可移植性,当紧要关头时,编译器将在任何像这样的窥孔优化方面击败 "most" 人类。

所以我检查了 g++ 的输出,看看它生成了什么程序集。

main.cpp

#include <array>
#include <iostream>

bool testX(int a, int b);
bool testY(std::array<char, 4> const& a, std::array<char, 4> const& b);
bool testZ(char const(&a)[4], char const(&b)[4]);

int main()
{
    {
        int a = 'ATCG';
        int b = 'ATCG';
        if (testX(a, b)) {
            std::cout << "Equal\n";
        }
    }
    {
        std::array<char, 4> a {'A', 'T', 'C', 'G'};
        std::array<char, 4> b {'A', 'T', 'C', 'G'};
        if (testY(a, b)) {
            std::cout << "Equal\n";
        }
    }
    {
        char    a[] = {'A', 'T', 'C', 'G'};
        char    b[] = {'A', 'T', 'C', 'G'};

        if (testZ(a, b)) {
            std::cout << "Equal\n";
        }
    }
}

启用优化后,我们可以从 clang 和最近的 gcc on the Godbolt compiler explorer 中获得不错的 asm。 (如果函数可以内联,上面的 main 将优化掉比较,因为输入是编译时常量。)

X.cpp

bool testX(int a, int b)
{
    return a == b;
}

# gcc and clang -O3 asm output
testX(int, int):
    cmpl    %esi, %edi
    sete    %al
    ret

Z.cpp

#include <cstring>

bool testZ(char const(&a)[4], char const(&b)[4])
{
    return std::memcmp(a, b, sizeof(a)) == 0;
}

Z.s

# clang, and gcc7 and newer, -O3
testZ(char const (&) [4], char const (&) [4]):
    movl    (%rdi), %eax
    cmpl    (%rsi), %eax
    sete    %al
    retq

Y.cpp

#include <array>

bool testY(std::array<char, 4> const& a, std::array<char, 4> const& b)
{
    return a == b;
}

Y.s

# only clang does this.  gcc8.2 actually calls memcmp with a constant 4-byte size
testY(std::array<char, 4ul> const&, std::array<char, 4ul> const&):           
    movl    (%rdi), %eax
    cmpl    (%rsi), %eax
    sete    %al
    retq

因此 std::array 和用于比较 4 字节对象的 memcmp 都使用 clang 生成相同的代码,但仅使用 gcc memcmp 优化得很好。


当然,该函数的独立版本必须实际生成一个 0 / 1 整数,而不是仅仅为 jcc 设置标志以直接分支。这些函数的调用者必须在分支之前 test %eax,%eax。但是,如果编译器可以内联这些函数,那么这些开销就会消失。

此答案的其余部分假设您需要使用内联汇编来完成一些家庭作业(因为它不会更有效率比智能编译器内联 4 字节 memcmp) 的内容要多。请参阅@MartinYork 的回答,了解 gcc/clang 对 4 字节 memcmp 的作用。但令人惊讶的是,只有 gcc7 和更高版本内联常量大小 memcmp。 Clang 至少回到 3.5 管理。

MSVC 2017 还内联 memcmp 以获得恒定的 4 字节大小,以及 std::array 运算符 ==,生成与 gcc/clang 相同的汇编。 (我没有测试早期版本)。请参阅纯 C++ 版本 on the Godbolt compiler explorer


从字符数组加载双字的必要语法是 dword ptr 大小覆盖。

// true for equal, false for not-equal
bool foo()
{
    //char a[] = "ACTG";
    char a[] = {'A', 'C', 'T', 'G'};
    char b[] = {'A', 'T', 'T', 'G'};
    _asm {
        mov eax, dword ptr a       // mov eax, a   would complain 
        cmp eax, dword ptr b
        sete al                    // al= 0 or 1 depending on ZF, the "e" condition like je
    }
    // falling off the end of a non-void function implicitly returns EAX
    // apparently this is supported in MSVC even when inlining
}

作为一个完整的函数,这个编译如下,with MSVC 19, 2017, with -Ox on the Godbolt compiler explorer:

 ;; define a couple assembler constants for use
_a$ = -8                                                ; size = 4
_b$ = -4                                                ; size = 4
foo PROC
        sub      esp, 8
        mov      DWORD PTR _a$[esp+8], 1196704577 ; 47544341H
        mov      DWORD PTR _b$[esp+8], 1196708929 ; 47545441H
  ;; inline asm block starts here
        mov      eax, DWORD PTR _a$[esp+8]
        cmp      eax, DWORD PTR _b$[esp+8]
        sete     al
  ;; and ends here
        add      esp, 8
        ret      0
foo ENDP

前 2 mov 条指令由编译器生成,用 dword MOV-immediate 将 4 字节数组存储到堆栈。

如果你想return一个0/非0int而不是0/1bool,你可以使用@P__J__的建议mov / sub 而不是检查 cmp 之后的标志。两个相等的双字将离开寄存器 0,其他任何东西都不会。 (xor 有相同的 属性。)


如果你想比较作为函数 arg 获得的 char* 的 4 个字节,它将是一个指针,而不是 C 数组,所以你必须自己将指针加载到寄存器中内联汇编(即使编译器已经在寄存器中有了指针;MSVC 内联 asm 语法对于小块来说基本上很糟糕,因为它强制输入和输出进行 store/reload 往返(~5 个延迟周期),除非你可以使用在 EAX 中留下一些东西并从非 void 函数的末尾掉落的明显支持的 hack。另见 What is the difference between 'asm', '__asm' and '__asm__'? for a comparison with GNU C inline asm, which makes it easy to ask for inputs in registers and produce multiple outputs in registers, allowing the compiler to optimize as much as possible. Of course it still defeats constant-propagation; if you used memcmp the compiler could just return 0 because the arrays have compile-time constant contents. https://gcc.gnu.org/wiki/DontUseInlineAsm)

无论如何,这就是比较函数参数的前 4 个字节的结果:

char bar(char *a, char *b)
{
    // a and b are pointers, not arrays
    _asm {
        mov eax, a              // loads the address
        mov eax, [eax]          // loads 4 bytes of data
        mov ecx, b
        cmp eax, [ecx]
        sete al
    }
}

bar PROC
        mov      eax, DWORD PTR _a$[esp-4]
        mov      eax, DWORD PTR [eax]
        mov      ecx, DWORD PTR _b$[esp-4]
        cmp      eax, DWORD PTR [ecx]
        sete     al
        ret      0

如果您使用 -Gv 或其他任何方式编译以启用在寄存器中传递 args 的更好的调用约定,实际上 更糟 :编译器必须溢出指针 args到堆栈以供 asm 重新加载它们,而不是变成 reg-reg 移动。 AFAIK,无法通过强制转换或其他方式让编译器为您将指针加载到寄存器中,因此您可以直接在内联 asm 中引用数组内容。

int main()
{
volatile char b[4],a[4];
b[0]='A';b[1]='T';b[2]='C';b[3]='G';
a[0]='A';a[1]='T';a[2]='C';a[3]='G';

uint32_t val;


__asm__("movl %0, %%eax;" : "=m" (a) : "m" (a));
__asm__ ( "subl %1, %%eax;" : "=a" (val) : "m" (b) );

printf("%s\n", !val ? "Equal" : "Not equal");

}