【问题标题】:Is using strlen() in the loop condition slower than just checking for the null character?在循环条件中使用 strlen() 是否比仅检查空字符慢?
【发布时间】:2011-03-24 05:09:16
【问题描述】:

我读到使用strlen 比这样的测试更昂贵:

我们有一个字符串x 100 个字符长。

我认为

for (int i = 0; i < strlen(x); i++)

比这段代码更贵:

for (int i = 0; x[i] != '\0'; i++)

这是真的吗?也许第二个代码在某些情况下不起作用,那么使用第一个更好吗?

下面的会更好吗?

for (char *tempptr = x; *tempptr != '\0'; tempptr++)

【问题讨论】:

  • 第二个循环大致完成了strlen内部的工作;我仍然没有看到问题的重点
  • 编写并测试它。指标是了解哪个更好的最佳方式。
  • 如果您不得不花时间和精力来担心这样的事情,您可以通过购买速度更快的计算机来省钱。

标签: c strlen string-length


【解决方案1】:
for (int i=0;i<strlen(x);i++)

这段代码每次迭代都会调用strlen(x)。因此,如果x 的长度为100,则strlen(x) 将被调用100 次。这是非常昂贵的。此外,strlen(x) 也每次都以与 for 循环相同的方式迭代 x。这使其复杂度为 O(n^2)。

for (int i=0;x[i]!='\0';i++)

此代码不调用任何函数,因此将比前面的示例快得多。由于它只遍历循环一次,因此复杂度为 O(n)。

【讨论】:

  • 如果字符串的长度在迭代过程中发生变化,这些方法可能会产生不同的结果,否则它们会做同样的事情。
  • 大多数编译器(包括 gcc)都足够聪明,可以缓存 strlen 的结果,因此两个循环的性能几乎相同。
  • @Paul R:第二个仍然会更快,因为 strlen 迭代一次,然后 OP 在他的循环中再次迭代,而在第二个循环中只有一次迭代。然而,就事物的规模而言,这是一个非常微小的优化。
  • @PaulR 你确定 GCC 在所有情况下都会这样做吗?它无法知道使用多个线程和全局字符串时会发生什么;另一个线程可以在循环时修改字符串。或者,如果您使用全局字符串,它可能不会进行优化。
  • @PaulR 我在循环期间更改字符串怎么样? strlen 不保证是一个常量。
【解决方案2】:

第一个代码在i的每次迭代中检查x的长度,找到最后一个0需要O(n),所以需要O(n^2),第二种情况是O(n)

【讨论】:

  • 所以人们很高兴地投票,因为 O(n^2) > O(n) ? ;)
  • @Gregory Pakosz 请他们帮助我,因为我不知道他们之间的区别,所以为什么这么生气?我很高兴,因为他们帮助我不是因为支持
  • 我没有生气,我是在帮助你,让你意识到你的问题不是一个真正的问题,因此这不是一个“真正的答案”,尽管 @ 没有任何问题。 Artyom 说
【解决方案3】:

是的,您的第二个可能无法 100% 工作,但它会稍微完全正常。这是因为在使用 strlen() 时,您必须每次都调用该方法。更好的方法是这样

int strLength = strlen(x);
for (int i = 0; i < strLength; i++)

希望这会有所帮助。

【讨论】:

  • 令人着迷。您想到的第二个版本不起作用但第一个版本起作用的场景是什么? (假设省略的循环内容不包含对i 或字符串的进一步修改。)
  • 例如取字符串“world is best” ' '呢?
  • @davit: ' ' 是空格,不是空字符
  • @mb14:重新阅读我评论中所述的假设。考虑到当循环体改变字符串长度时,Ash 的建议也与 OP 的第一个版本不同,人们假设他有其他想法,“你的第二个可能无法 100% 工作”。 (实际上,由于省略了循环内容,所有的赌注都被取消了......但这个问题只有在循环不修改字符串或进一步修改 i 时才有趣,所以我认为出于讨论的目的假设不是不合理的就是这样。)
  • 可能有单独的线程修改字符串内容。
【解决方案4】:

我可以假设在第一个变体中,您会在每次迭代中找到 strlen,而在第二个变体中,您不会这样做。
试试这个来检查:

int a = strlen(x); 
for (int i = 0; i < a; i++) {...}

【讨论】:

  • 很好,因为它不信任编译器,因此可以被其他程序员利用。假设我们总是想要严格的字符串长度,而我们可能会使用互补跨度来限定 EOL 或其他标记值。快10年了,所以它必须有一些观点。
【解决方案5】:

如果没有,则没有编译优化。是的,因为 strlen 每次都会迭代字符串的每个字节,而第二个实现只会执行一次。

【讨论】:

    【解决方案6】:

    我猜编译器可以优化(检查 Gregory Pakosz 评论)你的第一个 for 循环,这样你就会像这样:

    int len = strlen(x);
    for (int i = 0; i < len; i++)
    

    仍然是O(n)。

    无论如何,我认为你的第二个for 循环会更快。

    【讨论】:

    • 编译器不能将 strlen() 调用移出循环,除非它知道 strlen() 没有副作用并且字符串的长度不能在循环内改变。
    • 其实GCC可以在循环外优化strlen,因为它被识别为内置函数,见ridiculousfish.com/blog/archives/2010/07/23/will-it-optimize
    • 但不是2*O(n),而是O(n**2)。它每次迭代运行 O(n) 次迭代。
    • @Dave McClelland 呃,不是吗?如果 strlen 在循环外优化,它会运行一次,所以它只执行 O(n) 操作,然后执行循环,另一个 O(n) 操作,因为它现在只是进行比较。你真的读过答案吗?
    【解决方案7】:

    值得注意的是,如果您不根据包含字符串的缓冲区大小测试索引,您可能会面临缓冲区溢出问题。根据您使用此代码所做的工作,这可能是一个实际问题,也可能不是,但进行额外检查很少会造成伤害,这是一个养成的好习惯。

    我建议: for (i = 0; i

    地点:

    • buf_size 是缓冲区的预定大小。如果x 是一个数组,这将是sizeof(x);如果x 是一个指针,你应该有可用的缓冲区大小。
    • 我已经从循环中删除了i 的声明,因为它只是有效的c99。如果您只是在 c99 下编译,请随时将其放回原处。

    【讨论】:

      【解决方案8】:

      当然,第一个比第二个需要更长的时间。他们实际上根本不做同样的事情——第一个是每次都计算字符串的长度;第二个是(有效地)只做一次。

      第一个版本相当于这样:

      for (int i=0; x[i] != 0; i++)
        for (int j=0; j<i; j++)
          ;
      

      也许您的意思是“调用库 strlen() 比实现我自己的库更有效吗?”答案当然是“视情况而定”。你的编译器可能有 strlen() 的内在版本,这些版本的优化超出了你对源代码的期望,你可能在一个函数调用很昂贵的平台上(现在很少见)等等。

      唯一通用且正确的答案是“分析并找出答案”。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-14
        • 2015-08-30
        • 2021-04-18
        • 1970-01-01
        • 2023-03-06
        • 2014-08-03
        • 1970-01-01
        • 2014-09-23
        相关资源
        最近更新 更多