如何高效地在C语言中计算字符串的长度?

25

如何在C中高效地计算字符串长度?

我目前使用的方法是:

int calculate_length(char *string) {
    int length = 0;
    while (string[length] != '\0') {
        length++;
    }
    return length;
}

然而与strlen()相比,它非常慢,有没有其他方法可以做到这一点?

谢谢。

编辑:我正在一个自由环境中工作,不允许使用包括"string.h"在内的任何外部库。


1
为什么不使用strlen呢?还是这只是一个练习? - Sam Post
2
这不是一项练习,我工作的环境不允许我包含其他“库”,包括“string.h”,因此我必须实现它,并希望在保持可维护性的同时尽可能高效。 - Carla Álvarez
您可能希望编辑原始帖子以提及您正在使用独立环境。 - Matthew Iselin
1
请注意,标准库也可以使用编译器优化进行编译,而您的代码则没有。 - Khelben
这里有很好的答案,但请记住这是微观优化,并且不是所有程序员都理解宏观优化的用途和重要性。这是一个看起来完全正常的代码40倍加速的例子:https://dev59.com/mnNA5IYBdhLWcg3whuV_#927773 - Mike Dunlavey
14个回答

45

来自FreeBSD源代码:

size_t
strlen(const char *str)
{
    const char *s;
    for (s = str; *s; ++s);
    return(s - str);
}

与您的代码相比,这个可能很容易映射到一条汇编指令,这可以解释大量性能差异。

编译器应该能够相当有效地进行优化,这意味着代码仍然可读,并且应该仍然运行得相当快。 - Matthew Iselin

10

strlen()。如果有人找到更好、更快的通用方法,那么strlen可能会被替换掉。


9

看一下标准libc中strlen的源代码。标准库中的函数通常高度优化。在这里(使用汇编语言编写),这来自GNU libc。

size_t
DEFUN(strlen, (str), CONST char *str)
{
  int cnt;

  asm("cld\n"                   /* Search forward.  */
      /* Some old versions of gas need `repne' instead of `repnz'.  */
      "repnz\n"                 /* Look for a zero byte.  */
      "scasb" /* %0, %1, %3 */ :
      "=c" (cnt) : "D" (str), "0" (-1), "a" (0));

  return -2 - cnt;
}

3
装配版本可能更快,但你需要一些数字来支持这个说法。请参阅http://leaf.dragonflybsd.org/mailarchive/commits/2011-11/msg00195.html。 - eradman

6
请查看GNU C库的strlen()源代码,网址为:http://www.stdlib.net/~colmmacc/strlen.c.html。该源代码使用了许多不明显的技巧来提高速度而不必降低到汇编语言,这些技巧包括:
  • 找到对齐的字符
  • 将已对齐的字符串部分读入int(或其他更大的数据类型)以一次读取多个字符
  • 使用位操作技巧来检查嵌入在那个字符块中的字符之一是否为零
等等。

1
当前的FreeBSD版本也使用类似的东西,也许也会派上用场:http://www.freebsd.org/cgi/cvsweb.cgi/src/lib/libc/string/strlen.c?rev=1.7.2.1.2.1;content-type=text%2Fplain - Xandy
你是什么意思说“不降到汇编语言”?在i386上,它确实使用汇编语言(请参见Sudhanshu的回复)。 - bortzmeyer
Sudhanshu的代碼不同於我提供的那個。當glibc為x86構建時,可能會使用Sudhanshu的代碼(我並不完全確定);然而,我指出的示例是一段純粹的C代碼,可以作為一些可能優化的示例。 - Michael Burr

3

最简单的方法是调用 strlen() 函数。这个函数已经被你的编译器和/或库供应商进行了优化,以便在你的体系结构下实现最快速度。

一种常见的优化方法是消除计数器的增加需求,并从指针中计算长度:

size_t my_strlen(const char *s)
{
  const char *anchor = s;

  while(*s)
   s++;

  return s - anchor;
}

3

C字符串本质上效率低下,使用ASCIZ约定有两个原因:

  • 标准C库使用它
  • 编译器将其用于文本字符串常量

在这种情况下,第一个原因是学术性的,因为您没有使用标准库,第二个原因可以通过创建函数或宏来提供从C字符串到更有效的约定(如Pascal字符串)的转换来轻松解决。重点是,如果您不使用C库,则无需成为C约定的奴隶。


++ 你说得没错,但有时我们在错误的地方寻找循环。在真实代码中,strlen 的速度甚至不在雷达上,考虑到通常使软件变慢的多种宏方式。 - Mike Dunlavey
1
@Mike:完全同意。这可能是过早的优化,但我链接的文章给出了一些现实世界的例子,其中它非常关键。对于Pascal字符串的strlen()函数既快速又确定性强。 - Clifford
C 字符串在许多使用情况下效率低下,但对于某些使用情况(例如 substring = &string[skipped];)优于 Pascal 字符串。在其他地方跟踪字符串长度(而不是将其前置到字符串本身中)可能比 Pascal 字符串和 C 字符串都更有效。 - Brendan

2

0

计算字符串长度的基本C程序。

#include <stdio.h>

/**
* Method to calculate string length.
* Returns -1 in case of null pointer, else return string length.
**/
int length(char *str) {

    int i = -1;
    // Check for NULL pointer, then return i = -1;
    if(str == NULL) return i;

    // Iterate till the empty character.
    while (str[++i] != '\0');
    return i;  // Return string length.
}

int main (int argc, char **argv) {

    int len = 0;
    char abc[] = "hello";
    len = length(abc);
    printf("%d", len);  
    return 0;
}

注意:为了更好的方式,我们应该总是将数组大小传递给函数,以避免越界访问的情况。例如,方法的原型应该是:
/**
* @desc calculate the length of str.
* @param1 *str pointer to base address of char array.
* @param2 size = capacity of str to hold characters.
* @return int -1 in case of NULL, else return string length.
**/
int length (char *str, int size);

0

我曾经遇到过同样的问题,但我已经解决了。关键在于for循环的第二个条件:

int longitud(char cad[]){

    int i, cont;

    cont = 0;

    for(i = 0; i < 30 && cad[i] != '\0'; i++){
        if(cad[i] != '\0'){
            if(cad[i] != ' '){
                cont++;
            }
        }
    }
    cont--;
    return cont;
}

0

以上的回答都很好,以下是我的观点。 有一个关键字叫做"register"

#include <stdio.h>
size_t strlenNew(char *s);

int main(int argc, char* argv[])
{
    printf("Size of \"Hello World\" is ::\t%d",strlenNew("Hello World"));
    return 0;
}

size_t strlenNew(char *s)
{
    register int i=0;
    while(s[i]!='\0') i++;
    return i;
}

阅读这里:http://gustedt.wordpress.com/2010/08/17/a-common-misconsception-the-register-keyword/http://msdn.microsoft.com/en-us/library/482s4fy9(v=vs.80).aspx

从第一个链接中:

这对于数组变量特别有用。数组变量很容易与指针变量混淆。除非它后面跟着一个 [expr] 或者 sizeof,否则它将计算第一个元素的地址。如果您声明了 register 数组,则所有这些用法都是被禁止的;我们只访问单个元素或请求总大小。这样一个寄存器数组就可以更容易地像优化器一样使用,就好像它只是一组变量。不会发生别名(通过不同的指针访问相同的变量)。

因此,有时可能会出现性能波动。个人而言,这是我最喜欢的实现之一,但Sudhanshu和Andomar也提供了一个很好的实现 :)


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