【问题标题】:how to compare 32 bit char against 32 bit char in, inline assembely c++ [closed]如何比较 32 位字符和 32 位字符,内联汇编 C++ [关闭]
【发布时间】:2018-07-31 20:18:05
【问题描述】:

我想比较两个 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 个东西。
  • memcmp 呢?
  • 为什么要组装?标准库函数比您可以编写的任何程序集都更有效。我敢打赌,memcmp 通常会比您编写的任何程序集做得更好。
  • 扩展@4386427 的评论,这有一种非常强烈的Premature Optimization 气味
  • @RezaBehboodi 用汇编语言编写并不能保证高速。事实上,这意味着你走得快,“你”知道怎么走。编译器在优化速度方面可能要好得多。因此,如果您正确使用高级构造,您的速度将与“编译器”知道的一样快(在大多数情况下,它可能会击败“你”(当它没有击败你时,你会等于你)。这就是dgbuff 说的是过早优化。

标签: c++ assembly visual-c++ x86 inline-assembly


【解决方案1】:

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

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


从 char 数组加载双字的必要语法是 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 指令由编译器生成,将 4 字节数组以 dword MOV-immediate 的形式存储到堆栈中。

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


如果您想比较作为函数 arg 获得的 char* 的 4 个字节,它将是一个指针,而不是 C 数组,因此您必须自己将指针加载到内联 asm 中的寄存器中。 (即使编译器已经在寄存器中有指针;MSVC 内联 asm 语法对于小块基本上很烂,因为它强制为输入和输出进行存储/重新加载往返(约 5 个延迟周期),除非您可以使用显然- 支持在 EAX 中留下一些东西并从非 void 函数的末尾脱落。另请参阅 What is the difference between 'asm', '__asm' and '__asm__'? 与 GNU C 内联 asm 的比较,这使得在寄存器中请求输入并在寄存器中产生多个输出变得容易,允许编译器尽可能地优化。当然它仍然会破坏常量传播;如果你使用memcmp,编译器可以只使用return 0,因为数组具有编译时常量内容。https://gcc.gnu.org/wiki/DontUseInlineAsm)

无论如何,这是比较函数 args 的前 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 中引用数组内容。

【讨论】:

    【解决方案2】:

    首先,您的阵列存在严重问题。您将数组定义为包含 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;
    }
    

    【讨论】:

    • 感谢您的回答。我想在装配中使用这个解决方案
    • @RezaBehboodi 再次为什么要组装?学校作业?如果是这样,只需下载memcmp 的众多公共可用版本之一 - 它可以从他们那里获得......或者只是用memcmp 编译一个程序并查看反汇编......更好的 C 程序员 - 如果您想学习 C,请坚持使用 C,并将细节留给平台专家。
    • @4386427:你建议找到一个完整的 memcmp 实现,这完全没有抓住重点。 OP 想了解如何在 size = 4 的特殊情况下完成它,它可以通过一个 dword 比较来完成。 (不过,您将从智能编译器中获得更好的 asm,它会以这种方式为您内联它;请参阅 Martin 的答案和我的链接,了解为什么 MSVC 内联 asm 对于包装 1 条或几条指令是垃圾:存储/重新加载延迟是不可避免,并且无法针对常量进行优化。)
    • memcp函数的结构是什么?它使用循环吗?
    • @RezaBehboodi 也许吧。如果您在生产代码中使用memcmp 并启用完全优化,它肯定不会一次比较一个字节。对于您的情况,编译器几乎肯定会识别出要比较的大小是四,并优化为一个简单的寄存器到寄存器比较。对于较大的缓冲区,它将使用循环,但如果内存服务,至少对于 MSVC,它在 x86 模式下一次比较四个字节,在 x64 模式下可能一次比较八个。
    【解决方案3】:

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

    您应该使用高级语言结构。这将允许代码是可移植的,当推来推去时,编译器将在任何像这样的窥孔优化中击败“大多数”人类。

    所以我检查了 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 获得不错的 asm,通常来自最近的 gcc on the Godbolt compiler explorer。 (如果函数可以内联,上面的 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
    

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


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

    【讨论】:

    • 看起来您在编译时禁用了优化(-fomit-frame-pointer 在 -O1 或 -O2 处启用了 clang,但您的代码使用 RBP 作为帧指针。)jmp 是无条件跳转和 @ 987654336@ 不是比较。所以这基本上是无稽之谈。编写一个函数,它接受两个不是编译时常量的输入,并通过优化进行编译。顺便说一句,C 不像 NASM 这样的汇编程序那样支持多字节字符常量,但显然它是 GNU C 扩展,因为它确实有效。 godbolt.org/g/dp3NJM。另外,您是否手动从标签中删除了前导 .
    • @PeterCordes:“C 不支持多字节字符常量”——你是从哪里得到这个想法的?
    • @Martin:请展示一个单独编译的函数的程序集,该函数接受两个参数并进行比较。开启优化。提供的代码可能是正确的,因为相等性检查已被完全优化掉,但是。赞成一般方法和建议。细节不是那么重要,但很高兴让那些也清楚地正确,而不是有点可疑。
    • @Cheersandhth.-Alf:哦,我的错,显然使用'ATCG' 之类的常量是实现定义的,它不仅是多个字节,而且是多个UTF8 字符Multi-character constant warnings。我只是根据我之前评论中godbolt链接中的gcc警告做出假设。但是该标准仍然不能保证支持。 (而且我措辞错误,我应该说多字符字符常量。)
    • @PeterCordes:我认为您的术语比 g++ 的要好。 :) 这里的字符都是ASCII,单字节。当执行字符集为 UTF-8 时,挪威语 'ø' 是多字节 UTF-8 常量的示例。类型是int,而不是明显的char。 :(
    【解决方案4】:

    类似这样的:

    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;
    

    【讨论】:

    • 感谢您的回答。如何使用 mov 这样的: mov eax,b which b 是一个 char 数组 我想将 b[4] 数组的全部数据存储在 eax 寄存器中
    • OP 需要 MSVC inline-asm 语法来从 C char 数组加载 4 个字节,而不是在 asm 语句中使用 mov-immediate。
    【解决方案5】:
    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");
    
    }
    

    【讨论】:

    • 如果你是 DV 的任何评论
    • 那是 GNU C 语法(不是问题所问的 MSVC 语法),您不能假设 eax 在两个 __asm__ 语句之间没有被修改。您也不能在第一条语句中不告诉编译器的 eax 就破坏它。将这两个指令放在一个 asm 语句中,这两个问题都消失了。
    • sub 是一个好主意,但它可以生成 0 / 非零值而不是布尔值。
    猜你喜欢
    • 1970-01-01
    • 2011-01-31
    • 1970-01-01
    • 2016-03-31
    • 2019-12-16
    • 2014-10-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多