【问题标题】:C strlen using pointersC strlen 使用指针
【发布时间】:2017-04-24 02:48:17
【问题描述】:

我已经看到了 strlen 使用指针的标准实现:

int strlen(char * s) {
  char *p = s;
  while (*p!='\0')
    p++;
  return p-s;
}

我得到了这个作品,但是当我尝试使用另外 3 种方法(现在正在学习指针算术)来做到这一点时,我想知道它们有什么问题?

  1. 这有点类似于书中的内容。这是错的吗?

    int strlen(char * s) {
      char *p = s;
      while (*p)
        p++;
      return p-s;
    }
    
  2. 虽然如果我传递一个空字符串但仍然给我 0 会是错误的,这有点令人困惑,因为 p 是预增量:(现在它返回我 5)

    int strlen(char * s) {
      char *p = s;
      while (*++p)
        ;
      return p-s;
    }
    
  3. 想通了,帖子增加并返回+1。

    int strlen(char * s) {
      char *p = s;
      while (*p++)
        ;
      return p-s;
    }
    

【问题讨论】:

    标签: c


    【解决方案1】:

    1) 我觉得不错。我个人更喜欢与 '\0' 进行显式比较,这样很明显,在上下文不清楚的情况下,您并不是要(例如)将 p 与 NULL 指针进行比较。

    2) 当您的程序运行时,称为堆栈的内存区域未初始化。局部变量在那里。您编写程序的方式将p 放入堆栈(如果您将其设为const 或使用malloc,它几乎肯定会存在于其他地方)。当您查看*p 时会发生什么,然后您会查看堆栈。如果字符串长度为 0,则与 char p[1] = {0} 相同。预递增会立即查看\0 之后的字节,因此您正在查看未定义的内存。这里是龙!

    3) 我认为那里没有问题 :) 如您所见,它返回的总是比正确答案多一个。

    附录:如果你更喜欢这种风格,你也可以使用 for 循环来编写:

    size_t strlen(char * s) {
        char *p = s;
        for (; *p != '\0'; p++) {}
        return p - s;
    }
    

    或者(更容易出错)

    size_t strlen(char * s) {
        char *p = s;
        for (; *p != '\0'; p++);
        return p - s;
    }
    

    此外,strlen 不能返回负数,因此您应该使用无符号值。 size_t 更好。

    【讨论】:

    • 这是我的 main 的样子,现在,它给了我 5. main() { char A[ ]= "" ; printf("%d",strlen(A)); }
    • 啊...我明白了。发生的事情是你在检查那里有什么之前增加了指针。您最终检查的是堆栈中的数据。由于没有定义,你可能会得到空字符串的任何值,直到你点击 \0。
    【解决方案2】:

    版本 1 很好 - while (*p != '\0') 相当于 while (*p != 0),相当于 while (*p)

    在原始代码和版本 1 中,指针 p且仅当 *p 不是 0(IOW,你不在字符串末尾)。

    版本 2 和 3 提前 p 无论 *p 是否为 0*p++ 计算为p 指向的字符,并且作为副作用 推进p*++p 评估为字符跟随字符p 指向的字符,并且作为副作用推进p。因此,版本 2 和 3 将始终将 p 推进到字符串的末尾,这就是您的值关闭的原因。

    【讨论】:

      【解决方案3】:

      当您比较 strlen 替换函数的性能时,您会遇到的一个问题是,与长字符串的实际 strlen 函数相比,它们的性能会受到影响吗?为什么? strlen 在搜索字符串结尾时每次迭代处理超过一个字节。如何实现更高效的替换?

      这并不难。基本方法是每次迭代查看 4 字节,并根据在这 4 字节中找到 nul-byte 的位置调整返回。您可以执行以下操作(使用数组索引):

      size_t strsz_idx (const char *s) {
          size_t len = 0;
          for(;;) {
              if (s[0] == 0) return len;
              if (s[1] == 0) return len + 1;
              if (s[2] == 0) return len + 2;
              if (s[3] == 0) return len + 3;
              s += 4, len += 4;
          }
      }
      

      您可以使用指针和掩码做同样的事情:

      size_t strsz (const char *s) {
          size_t 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;
          }
      }
      

      无论哪种方式,您都会发现每次迭代都会进行 4 字节比较,从而获得与 strlen 本身相当的性能。

      【讨论】:

      • 第一个循环当时不检查 4 个字节。它使用更复杂的循环,比原始循环具有更多的分支和添加。第二个循环更糟糕:它违反别名规则并导致未定义的行为。此外,这个答案没有回答这个问题。投反对票。
      • 没有什么比对严格别名规则有一点知识更糟糕的了。 casting to-and-from char* 的规则是什么?那里没有违规行为,如果这是您 ding 的基础,那么您是 100% 错误的。为什么不用-Wall -Wextra -pedantic 编译代码并找出答案?当我错了我不介意投反对票,但我也不指望我是对的。
      • 允许将任何对象类型的地址转换为char*,这是标准中的特定例外。但是,除非原始类型和最终类型匹配(unsigned * -> char* -> unsigned* 是合法的,char[] -> unsigned* 不是),否则您不能在反向和取消引用中做同样的事情。 N1570 6.5 p7 列出了可能的别名类型,这 6 种类型中没有一种适用于这种情况(甚至最后一种也不适用)。测试不足以检查某些东西在 C 中是否合法,因为未定义的行为有时可能似乎起作用。 *(unsigned*)s 就是这方面的经典例子。
      • 你错了,你可以将任何东西投射到char *char * 到任何东西。我很高兴与您和 cmets 一起通过标准来了解主要规则。我已经做过很多次了。以荣誉和正直的方式解决这种情况的方法就是说,我误读了标准,涉及char * 的演员表很好。没什么大不了的。涉及的标准是第 6.5.6 和 6.5.7 节。 (以及每个的注释和 cmets)
      • "你可以将任何东西投射到char *" 是的。 "and char * to any" 但是你不能取消引用它,除非char* 从指针转换为'anything'。 实际示例: *(uint64_t*)s 可以在 ARM Cortex-M3 上调用故障处理程序,因为访问未对齐。 CPU 硬件的对齐限制是在 C 中引入这些别名规则的原因之一。*(unsigned*)s 似乎适用于对对齐非常宽松的 x86,但代码仍然是 UB。这意味着优化编译器可以(例如)用unsigned x = 0; 替换初始化。
      猜你喜欢
      • 2015-01-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-18
      • 1970-01-01
      • 1970-01-01
      • 2014-06-26
      • 1970-01-01
      相关资源
      最近更新 更多