C 带位运算的快速strlen

C 带位运算的快速strlen,c,bitwise-operators,strlen,C,Bitwise Operators,Strlen,我找到了这个密码 int strlen_my(const char *s) { int len = 0; for(;;) { unsigned x = *(unsigned*)s; if((x & 0xFF) == 0) return len; if((x & 0xFF00) == 0) return len + 1; if((x & 0xFF0000) == 0) return le

我找到了这个密码

int strlen_my(const char *s)
{
    int len = 0;
    for(;;)
    {
        unsigned x = *(unsigned*)s;
        if((x & 0xFF) == 0) return len;
        if((x & 0xFF00) == 0) return len + 1;
        if((x & 0xFF0000) == 0) return len + 2;
        if((x & 0xFF000000) == 0) return len + 3;
        s += 4, len += 4;
    }
}

我很想知道它是如何工作的。有人能解释一下它是如何工作的吗?

它检测在一台小端机器上是否有任何位被设置在一个特定的字节上。因为我们只检查一个字节(因为所有的半字节,0或0xF,都是双倍的),而它恰好是最后一个字节的位置(因为机器是小端的,因此数字的字节模式是相反的)我们可以立即知道哪个字节包含NUL。

循环为每次迭代获取4个字节的字符数组。四个if语句用于确定字符串是否结束,使用带AND运算符的位掩码读取所选子字符串的第i个元素的状态

它用未定义的行为(未对齐的访问,超过数组末尾的访问概率为75%)换取一个非常可疑的加速(很可能甚至更慢)。并且不符合标准,因为它返回
int
而不是
size\u t
。即使平台上允许未对齐的访问,它们也可能比对齐的访问慢得多

它也不适用于big-endian系统,或者如果
unsigned
不是32位。更不用说多重掩码和条件操作了

也就是说:

它一次测试4个8位字节,方法是加载一个
无符号的
(它甚至不能保证超过16位)。一旦任何字节包含
'\0'
-终止符,它将返回当前长度加上该字节位置的总和。否则,它将按并行测试的字节数(4)增加当前长度,并获取下一个
未签名的

我的建议:优化的坏例子加上太多的不确定性/陷阱。它可能不会更快-只需对照标准版本进行配置即可:

size_t strlen(restrict const char *s)
{
    size_t l = 0;
    while ( *s++ )
        l++;
    return l;
}

可能有一种方法可以使用特殊的向量指令,但除非您能证明这是一个关键函数,否则您应该将其留给编译器—有些编译器可能会更好地展开/加速此类循环。

按位AND with one将从另一个操作数检索位模式。意思是,
10101&11111=10101
。如果按位AND的结果是0,那么我们知道另一个操作数是0。将单个字节与
0xFF
(个)进行ANDing时,结果为0将指示空字节

代码本身在四字节分区中检查char数组的每个字节注意:此代码不可移植;在另一台计算机或编译器上,无符号int可能超过4个字节。最好使用
uint32\u t
数据类型来确保32位无符号整数

首先要注意的是,在一个小的endian机器上,组成字符数组的字节将以相反的顺序读入一个无符号数据类型;也就是说,如果当前地址的四个字节是对应于
abcd
的位模式,则无符号变量将包含对应于
dcba
的位模式

第二个是,C中的十六进制数常量会产生一个整数大小的数字,指定的字节位于位模式的小端。这意味着,当使用4字节整数编译时,
0xFF
实际上是
0x000000FF
<代码>0xFF00
0x0000FF00
。等等

所以程序基本上是在四个可能的位置寻找空字符。如果当前分区中没有空字符,它将前进到下一个四字节插槽

以char数组
abcdef
为例。在C语言中,字符串常量的末尾总是有空终止符,因此在该字符串的末尾有一个
0x00
字节

它的工作原理如下:

将“abcd”读入无符号整数x:

x: 0x64636261 [ASCII representations for "dcba"]
x: 0xBF006665 [ASCII representations for "fe"]
检查每个字节是否有空终止符:

  0x64636261
& 0x000000FF
  0x00000061 != 0,

  0x64636261
& 0x0000FF00
  0x00006200 != 0,
  0xBF006665
& 0x000000FF
  0x00000065 != 0,

  0xBF006665
& 0x0000FF00
  0x00006600 != 0,

  0xBF006665
& 0x00FF0000
  0x00000000 == 0 !!!
并检查其他两个位置;这个4字节分区中没有空终止符,因此请前进到下一个分区

将“ef”读入无符号整数x:

x: 0x64636261 [ASCII representations for "dcba"]
x: 0xBF006665 [ASCII representations for "fe"]
注意0xBF字节;这超过了字符串的长度,因此我们正在从运行时堆栈中读取垃圾。它可能是任何东西。在不允许未对齐访问的计算机上,如果字符串后的内存未对齐1字节,则会崩溃。如果字符串中只剩下一个字符,我们将额外读取两个字节,因此与char数组相邻的内存的对齐方式必须是2字节对齐

检查每个字节是否有空终止符:

  0x64636261
& 0x000000FF
  0x00000061 != 0,

  0x64636261
& 0x0000FF00
  0x00006200 != 0,
  0xBF006665
& 0x000000FF
  0x00000065 != 0,

  0xBF006665
& 0x0000FF00
  0x00006600 != 0,

  0xBF006665
& 0x00FF0000
  0x00000000 == 0 !!!
所以我们返回
len+2
len
是4,因为我们将它一次递增4,所以我们返回6,这确实是字符串的长度。

假定字符串的布局和可访问性类似于
int
数组,尝试一次读取4个字节,代码“起作用”。代码依次读取第一个int,然后读取每个字节,测试它是否为空字符。理论上,使用
int
的代码运行速度将快于4个单独的
char
操作

但也存在一些问题:

校准是一个问题:例如,
*(无符号*)s
可能存在seg故障

Endian是
的一个问题,如果((x&0xFF)==0)
可能无法获取地址
s

s+=4
是一个问题,因为
sizeof(int)
可能与4不同

数组类型可能超过
int
范围,最好使用
size\u t


试图纠正这些困难

#include <stddef.h>
#include <stdio.h>

static inline aligned_as_int(const char *s) {
  max_align_t mat; // C11
  uintptr_t i = (uintptr_t) s;
  return i % sizeof mat == 0;
}

size_t strlen_my(const char *s) {
  size_t len = 0;
  // align
  while (!aligned_as_int(s)) {
    if (*s == 0) return len;
    s++;
    len++;
  }
  for (;;) {
    unsigned x = *(unsigned*) s;
    #if UINT_MAX >> CHAR_BIT == UCHAR_MAX
      if(!(x & 0xFF) || !(x & 0xFF00)) break;
      s += 2, len += 2;
    #elif UINT_MAX >> CHAR_BIT*3 == UCHAR_MAX
      if (!(x & 0xFF) || !(x & 0xFF00) || !(x & 0xFF0000) || !(x & 0xFF000000)) break;
      s += 4, len += 4;
    #elif UINT_MAX >> CHAR_BIT*7 == UCHAR_MAX
      if (   !(x & 0xFF) || !(x & 0xFF00)
          || !(x & 0xFF0000) || !(x & 0xFF000000)
          || !(x & 0xFF00000000) || !(x & 0xFF0000000000)
          || !(x & 0xFF000000000000) || !(x & 0xFF00000000000000)) break;
      s += 8, len += 8;
    #else
      #error TBD code
    #endif
  }
  while (*s++) {
    len++;
  }
  return len;
}
#包括
#包括
静态内联对齐作为int(常量字符*s){
最大对齐矩阵;//C11
uintptr_t i=(uintptr_t)s;
返回i%sizeof mat==0;
}
尺寸标准尺寸(常量字符*s){
尺寸长度=0;
//对齐
而(!对齐为int){
如果(*s==0),则返回len;
s++;
len++;
}
对于(;;){
无符号x=*(无符号*)s;
#如果UINT\u MAX>>字符位==UCHAR\u MAX
如果(!(x&0xFF)| |!(x&0xFF00))中断;
s+=2,len+=2;
#elif UINT\u MAX>>字符位*3==UCHAR\u MAX