如何将32位字符与内联assembelyc++中的32位字符进行比较

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

本文关键字:字符 32位 中的 比较 assembelyc++      更新时间:2023-10-16

我想比较两个4字符字符串。例如"A"、"T"、"T"、"C"反对"A"、"T"、"T"、"c"。我已经将这些字符存储在c++中的一个数组中,我想在指令中比较这两个字。此外,我不想使用循环进行比较。如何将这些单词存储在"eax"answers"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;
}

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

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

MSVC 2017还为恒定的4字节大小内联memcmp,并且std::array运算符==,生成与gcc/clang相同的asm。(我没有测试早期版本)。请参阅Godbolt编译器资源管理器上的纯C++版本。


从char数组加载dword所需的语法是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
}

作为一个完整的函数,此编译如下,使用MSVC 192017,在Godbolt编译器资源管理器上使用-Ox

;; 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字节数组存储到具有双字MOV立即数的堆栈中。

如果要返回0/non-0int而不是0/1bool,可以使用@p_J__对mov/sub的建议,而不是检查cmp之后的标志。两个相等的双字将离开寄存器0,其他任何字都不会。(xor具有相同的性质。)


如果你想比较作为函数arg获得的char*的4个字节,它将是一个指针,而不是一个C数组,所以你必须在内联asm中自己将指针加载到寄存器中。(即使编译器已经在寄存器中有了指针;MSVC内联asm语法基本上对小块来说很糟糕,因为它强制输入和输出进行存储/重载往返(约5个延迟周期),除非你可以使用明显支持的破解方法,在EAX中留下一些东西,并从非void函数的末尾掉下来。另请参阅';asm''__asm';和'__asm__';?与GNU C内联asm进行比较,后者使在寄存器中请求输入和在寄存器中产生多个输出变得容易,从而允许编译器尽可能优化。当然,它仍然会战胜不断的传播;如果使用memcmp,编译器只能使用return 0,因为数组具有编译时间常量内容。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或其他方法进行编译,以启用在寄存器中传递参数的更好调用约定,情况会更糟:编译器必须将指针参数溢出到堆栈中,以便asm重新加载它们,而不是变成reg reg移动。AFAIK,没有办法通过强制转换或其他方式让编译器为您将指针加载到寄存器中,这样您就可以直接在内联asm中引用数组内容。

首先,您的数组存在严重问题。您将数组定义为包含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("Equaln");
else
printf("Different");
return 0;
}

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

您应该使用高级语言构造。这将允许代码是可移植的,当事态发展到紧要关头时,编译器将在任何像这样的窥视孔优化中击败"大多数"人。

所以我检查了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 << "Equaln";
}
}
{
std::array<char, 4> a {'A', 'T', 'C', 'G'};
std::array<char, 4> b {'A', 'T', 'C', 'G'};
if (testY(a, b)) {
std::cout << "Equaln";
}
}
{
char    a[] = {'A', 'T', 'C', 'G'};
char    b[] = {'A', 'T', 'C', 'G'};
if (testZ(a, b)) {
std::cout << "Equaln";
}
}
}

启用优化后,我们可以从clang中获得不错的asm,通常是从Godbolt编译器资源管理器上最近的gcc中获得的。(如果函数可以内联,上面的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。但是,如果编译器能够内联这些函数,那么开销就会消失。

类似这样的东西:

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;
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("%sn", !val ? "Equal" : "Not equal");
}