如何有效地比较字符串与短字符串字面量

4

如果我不想使用调用strcmp的开销,我会按照下面代码示例中描述的方法使用短字符串字面值进行字符串比较:

#ifdef LITTLE_ENDIAN        //little-endian-addressing
#define BytesAsDWord_M(a, b, c, d)\
  ((ulong) ((a) | ((b) << 8) | ((ulong) (c) << 16) | ((ulong) (d) << 24)))

#define BytesAsWord_M(a, b)((ushort) ((a) | ((b) << 8)))

#else //LITTLE_ENDIAN       //little-endian-addressing
#define BytesAsDWord_M(a, b, c, d)\
 ((ulong) ((d) | ((c) << 8) | ((b) << 16) | ((a) << 24)))

#define BytesAsWord_M(a, b) ((ushort) ((b) | ((a) << 8)))
#endif //LITTLE_ENDIAN      //little-endian-addressing

bool AbsCompare(char* chr_p)
//compare string with  "abs"
{
  if (*((ulong*) &chr_p[1]) ==
    BytesAsDWord_M('a', 'b', 's', '\0'))
    return true;

  return false;
}

如果编译时不启用优化选项,gcc会编译此示例。但是如果启用了优化选项,则会出现以下警告:

"dereferencing type-punned pointer will break strict-aliasing rules"(解引用类型转换后的指针将破坏严格别名规则)

-O3优化也无法产生有效代码,正如示例所示:

//abstest.c

#include <string.h>

typedef unsigned long ulong;
typedef unsigned short ushort;

#if BYTE_ORDER == LITTLE_ENDIAN     //little-endian-addressing
#define BytesAsDWord_M(a, b, c, d)\
  ((ulong) ((a) | ((b) << 8) | ((ulong) (c) << 16) | ((ulong) (d) << 24)))

#define BytesAsWord_M(a, b)((ushort) ((a) | ((b) << 8)))

#else //BYTE_ORDER == LITTLE_ENDIAN //little-endian-addressing
#define BytesAsDWord_M(a, b, c, d)\
 ((ulong) ((d) | ((c) << 8) | ((b) << 16) | ((a) << 24)))

#define BytesAsWord_M(a, b) ((ushort) ((b) | ((a) << 8)))
#endif //BYTE_ORDER == LITTLE_ENDIAN    //little-endian-addressing

int AbsCompare1(char* chr_p)
{
  return *(ulong*) chr_p ==  BytesAsDWord_M('a', 'b', 's', '\0');
}

int AbsCompare2(char* chr_p)
{
  return strcmp(chr_p, "abs");
}

int main(int argc __attribute__((unused)), char ** argv)
{
  int i;
  int j;

  i = AbsCompare1(argv[0]);
  j = AbsCompare2(argv[0]);

  return i + j;
}

objdump -d -Mintel abstest:

080483d0 <AbsCompare1>:
 80483d0:   55                      push   ebp
 80483d1:   89 e5                   mov    ebp,esp
 80483d3:   8b 45 08                mov    eax,DWORD PTR [ebp+0x8]
 80483d6:   5d                      pop    ebp
 80483d7:   81 38 61 62 73 00       cmp    DWORD PTR [eax],0x736261
 80483dd:   0f 94 c0                sete   al
 80483e0:   0f b6 c0                movzx  eax,al
 80483e3:   c3                      ret    

080483f0 <AbsCompare2>:
 80483f0:   55                      push   ebp
 80483f1:   0f b6 0d 5c 85 04 08    movzx  ecx,BYTE PTR ds:0x804855c
 80483f8:   89 e5                   mov    ebp,esp
 80483fa:   8b 55 08                mov    edx,DWORD PTR [ebp+0x8]
 80483fd:   0f b6 02                movzx  eax,BYTE PTR [edx]
 8048400:   29 c8                   sub    eax,ecx
 8048402:   75 2b                   jne    804842f <AbsCompare2+0x3f>
 8048404:   0f b6 42 01             movzx  eax,BYTE PTR [edx+0x1]
 8048408:   0f b6 0d 5d 85 04 08    movzx  ecx,BYTE PTR ds:0x804855d
 804840f:   29 c8                   sub    eax,ecx
 8048411:   75 1c                   jne    804842f <AbsCompare2+0x3f>
 8048413:   0f b6 42 02             movzx  eax,BYTE PTR [edx+0x2]
 8048417:   0f b6 0d 5e 85 04 08    movzx  ecx,BYTE PTR ds:0x804855e
 804841e:   29 c8                   sub    eax,ecx
 8048420:   75 0d                   jne    804842f <AbsCompare2+0x3f>
 8048422:   0f b6 42 03             movzx  eax,BYTE PTR [edx+0x3]
 8048426:   0f b6 15 5f 85 04 08    movzx  edx,BYTE PTR ds:0x804855f
 804842d:   29 d0                   sub    eax,edx
 804842f:   5d                      pop    ebp
 8048430:   c3                      ret    

有没有可能直接比较这个短文字而不用将chr_p嵌入联合体,特别是因为我想在任意索引处比较chr_p?


我看到你的目标是面向大端和小端架构,但没有考虑对齐问题。类似&chr_p[1]这样的表达式几乎肯定会在某个时刻计算出一个非字对齐的地址。 - Michael Burr
据我所知,这只是性能限制。 - Wosh
@Wosh 在某些架构上,未对齐的访问将导致硬件异常。 - Bryan Olivier
我可以友善地建议您使用一个简单的分析器来证明您的代码确实比strcmp(或strncmp)表现更好吗?否则,除了混淆代码之外,您所做的任何事情都没有任何可衡量的价值。;) - Andreas Grapentin
@Andreas 很明显,示例1中用于比较的16字节代码比示例2中最多62字节的代码更有效。 - Wosh
1个回答

9
没有这样的方法。你知道编译器会使用它对strcmp函数的了解吗?它将实现你想要的功能(去除调用开销),而无需在源代码中使用类型转换。这种代码转换通常是在编译器利用别名分析后由代码生成器完成的。
如果我使用gcc -O3编译以下程序,将找不到对strcmp函数的调用。
#include <string.h>

int main(int argc, char ** argv)
{
        return strcmp(argv[0], "abs");
}

我的x86汇编代码,例如像这样(gcc版本4.3.2(Debian 4.3.2-1.1))(我知道它很旧):

main:
        leal    4(%esp), %ecx
        andl    $-16, %esp
        pushl   -4(%ecx)
        pushl   %ebp
        movl    %esp, %ebp
        pushl   %ecx
        movl    4(%ecx), %eax
        movl    (%eax), %edx
        movzbl  (%edx), %eax
        subl    $97, %eax
        jne     .L2
        movzbl  1(%edx), %eax
        subl    $98, %eax
        jne     .L2
        movzbl  2(%edx), %eax
        subl    $115, %eax
        jne     .L2
        movzbl  3(%edx), %eax
.L2:
        popl    %ecx
        popl    %ebp
        leal    -4(%ecx), %esp
        ret

基本上,strcmp已经被内联和展开了。当然,这高度取决于您目标代码生成器的发展程度。因此,如果它还不够先进,那么它可能仍会生成一个 strcmp。这仍然让你想知道,如果也许代码生成器以后会支持这个功能,而你现在却被困在你的代码中,是否应该负担自己丑陋的代码。


你得到了什么代码?我的 gcc -O3 -S 生成了一个对 strcmp() 的尾调用。 - Pascal Cuoq
@Pascal:我个人得到了一个repe cmpsb指令(带有相应的设置和完成)。这是使用GCC 4.7.2针对x86的目标。 - Michael Burr
刚测试了一下,在 x86 平台上使用 GCC 4.7.2 对 "abs" 的每个字符都得到了展开的 jne 测试序列。有趣的是,将相同的代码编译为 C++ 后得到了一个 repz cmpsb - SirDarius

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接