字符串处理函数

这篇文章中我想讨论下strlen()的问题。strlen()的作用是计算一个字符串的长度,最简单的代码如下:

size_t strlen(const char *str)
{
        const char *s;

        for (s = str; *s; ++s)
                ;
        return (s - str);
}

好像这是理所当然的事情,以前从来没有想过这种实现方式的效率问题,也没有想过strlen()是否还有其他实现方式,直到看到glibc的代码。glibc中strlen()是这么实现的:

size_t
strlen (str)
     const char *str;
{
  const char *char_ptr;
  const unsigned long int *longword_ptr;
  unsigned long int longword, himagic, lomagic;

  /* Handle the first few characters by reading one character at a time.
     Do this until CHAR_PTR is aligned on a longword boundary.  */
  for (char_ptr = str; ((unsigned long int) char_ptr
			& (sizeof (longword) - 1)) != 0;
       ++char_ptr)
    if (*char_ptr == '\0')
      return char_ptr - str;

  /* All these elucidatory comments refer to 4-byte longwords,
     but the theory applies equally well to 8-byte longwords.  */

  longword_ptr = (unsigned long int *) char_ptr;

  himagic = 0x80808080L;
  lomagic = 0x01010101L;
  if (sizeof (longword) > 4)
    {
      /* 64-bit version of the magic.  */
      /* Do the shift in two steps to avoid a warning if long has 32 bits.  */
      himagic = ((himagic << 16) << 16) | himagic;
      lomagic = ((lomagic << 16) << 16) | lomagic;
    }
  if (sizeof (longword) > 8)
    abort ();

  /* Instead of the traditional loop which tests each character,
     we will test a longword at a time.  The tricky part is testing
     if *any of the four* bytes in the longword in question are zero.  */
  for (;;)
    {
      longword = *longword_ptr++;

      if (((longword - lomagic) & ~longword & himagic) != 0)
	{
	  /* Which of the bytes was the zero?  If none of them were, it was
	     a misfire; continue the search.  */

	  const char *cp = (const char *) (longword_ptr - 1);

	  if (cp[0] == 0)
	    return cp - str;
	  if (cp[1] == 0)
	    return cp - str + 1;
	  if (cp[2] == 0)
	    return cp - str + 2;
	  if (cp[3] == 0)
	    return cp - str + 3;
	  if (sizeof (longword) > 4)
	    {
	      if (cp[4] == 0)
		return cp - str + 4;
	      if (cp[5] == 0)
		return cp - str + 5;
	      if (cp[6] == 0)
		return cp - str + 6;
	      if (cp[7] == 0)
		return cp - str + 7;
	    }
	}
    }
}

是不是很复杂?这种实现方法的思想是每次处理多个字符(32位系统中每次处理4个字符,64位系统中每次处理8个字符),通过这种改进strlen()的效率得到了大幅提升。为了支持64位系统,strlen()中增加了一些代码,我们先去掉这些代码,只关注32位系统。代码可以分成两段,第一段代码如下:

  for (char_ptr = str; ((unsigned long int) char_ptr
                        & (sizeof (longword) - 1)) != 0;
       ++char_ptr)
    if (*char_ptr == '\0')
      return char_ptr - str;
这段代码在处理字符串对齐问题。因为字符串str的起始地址不一定按照4字节对齐了,而strlen()每次处理4字节,因此当字符串地址按4字节对齐后可以加快处理效率,因此这段代码先处理str开头的几个字符(最多循环3次)。如果头3个字符中就找到了结束符'\0',那么strlen()就结束了。
第二段代码如下:

  longword_ptr = (unsigned long int *) char_ptr;
  himagic = 0x80808080L;
  lomagic = 0x01010101L;

  for (;;)
    {
      longword = *longword_ptr++;

      if (((longword - lomagic) & ~longword & himagic) != 0)
        {
          const char *cp = (const char *) (longword_ptr - 1);

          if (cp[0] == 0)
            return cp - str;
          if (cp[1] == 0)
            return cp - str + 1;
          if (cp[2] == 0)
            return cp - str + 2;
          if (cp[3] == 0)
            return cp - str + 3;
        }
    }

这段代码以四字节为单位在字符串中查找结束符'\0',如果在某次循环中找到了结束符,那么就判断第几个字符是结束符,cp[0]表示第0个字符,cp[1]表示第1个字符,cp[2]表示第2个字符,cp[3]表示第3个字符。那么,现在的重点就是如何判断四字节构成的unsigned long int类型变量是否包含结束符,这是通过(longword - lomagic) & ~longword & himagic判断的。如果这个结果不为0说明longword包含结束符,循环结束。下面详细分析这个判断过程。

首先看longword - lomagic,longword长度是4字节,lomagic的值是0x01010101,因此longword-lomagic对longword中每个字节减1。如果longword某个字节的值为0或者大于0x80,那么longword-lomagic中对应字节的最高位为1,否则对应字节最高位为0。再看~longword&himagic,himagic的值是0x80808080,~longword表示对longword取反操作。如果longword中某个字节大于等于0x80,那么~longword&himagic中对应字节的最高位为0;否则~longword&himagic中对应字节的最高位为1。因此(longword - lomagic) & (~longword & himagic)将前面两种情况进行与运算取二者的交集,也就是(0 或者 大于0x80)&(小于0x80),交集只有等于0一种情况。也就是说,上述表达式只在longword中至少一个字节为0的条件下成立。通过这种方法就可以快速判断longword中是否包含结束符。

最后我们对strlen()的速率进行测试。定义了一个长度为100000字节的字符串,然后调用两种算法计算字符串长度。原始方法花费了277微秒,优化后的方法花费了95微秒,可见优化效果还是很明显。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值