在C中连接字符串,哪种方法更有效?

时间:2009-09-05 15:53:17

标签: c string performance concatenation

我遇到了这两种连接字符串的方法:

共同部分:

char* first= "First";
char* second = "Second";
char* both = malloc(strlen(first) + strlen(second) + 2);

方法1:

strcpy(both, first);
strcat(both, " ");       // or space could have been part of one of the strings
strcat(both, second);

方法2:

sprintf(both, "%s %s", first, second);

在这两种情况下,both的内容都是"First Second"

我想知道哪一个更有效(我必须执行多个连接操作),或者如果你知道更好的方法。

10 个答案:

答案 0 :(得分:72)

为了便于阅读,我会选择

char * s = malloc(snprintf(NULL, 0, "%s %s", first, second) + 1);
sprintf(s, "%s %s", first, second);

如果您的平台支持GNU扩展,您还可以使用asprintf()

char * s = NULL;
asprintf(&s, "%s %s", first, second);

如果您遇到MS C Runtime,则必须使用_scprintf()来确定结果字符串的长度:

char * s = malloc(_scprintf("%s %s", first, second) + 1);
sprintf(s, "%s %s", first, second);

以下很可能是最快的解决方案:

size_t len1 = strlen(first);
size_t len2 = strlen(second);

char * s = malloc(len1 + len2 + 2);
memcpy(s, first, len1);
s[len1] = ' ';
memcpy(s + len1 + 1, second, len2 + 1); // includes terminating null

答案 1 :(得分:24)

不要担心效率:使代码可读和可维护。我怀疑这些方法之间的差异在你的程序中是否重要。

答案 2 :(得分:18)

这对你来说有些疯狂,我实际上是去测量它。血淋淋的地狱,想象一下。我想我得到了一些有意义的结果。

我使用双核P4,运行Windows,使用mingw gcc 4.4,使用“gcc foo.c -o foo.exe -std = c99 -Wall -O2”构建。

我测试了原帖中的方法1和方法2。最初将malloc保留在基准循环之外。方法1比方法2快48倍。奇怪的是,从构建命令中删除-O2使得生成的exe快30%(尚未调查原因)。

然后我在循环中添加了一个malloc并且自由了。这使方法1减慢了4.4倍。方法2减慢了1.1倍。

所以,malloc + strlen + free不会在配置文件中占主导地位,足以让sprintf值得一试。

这是我使用的代码(除了使用<而不是!=实现循环,但这打破了这篇文章的HTML呈现):

void a(char *first, char *second, char *both)
{
    for (int i = 0; i != 1000000 * 48; i++)
    {
        strcpy(both, first);
        strcat(both, " ");
        strcat(both, second);
    }
}

void b(char *first, char *second, char *both)
{
    for (int i = 0; i != 1000000 * 1; i++)
        sprintf(both, "%s %s", first, second);
}

int main(void)
{
    char* first= "First";
    char* second = "Second";
    char* both = (char*) malloc((strlen(first) + strlen(second) + 2) * sizeof(char));

    // Takes 3.7 sec with optimisations, 2.7 sec WITHOUT optimisations!
    a(first, second, both);

    // Takes 3.7 sec with or without optimisations
    //b(first, second, both);

    return 0;
}

答案 3 :(得分:6)

size_t lf = strlen(first);
size_t ls = strlen(second);

char *both = (char*) malloc((lf + ls + 2) * sizeof(char));

strcpy(both, first);

both[lf] = ' ';
strcpy(&both[lf+1], second);

答案 4 :(得分:2)

他们应该差不多了。差别并不重要。我会选择sprintf,因为它需要更少的代码。

答案 5 :(得分:2)

差异不太重要:

  • 如果您的字符串很小, malloc 将会淹没字符串连接。
  • 如果您的字符串很大,复制数据所花费的时间将淹没 strcat / sprintf 之间的差异。

正如其他海报所提到的,这是一个不成熟的优化。专注于算法设计,只有在分析表明它是性能问题时才会回到这一点。

那说......我怀疑方法1会更快。解析 sprintf 格式字符串有一些---无可否认的小---开销。并且 strcat 更可能是“内联”。

答案 6 :(得分:1)

sprintf()旨在处理的不仅仅是字符串,strcat()是专家。但我怀疑你是在冒汗。 C字符串在根本上是低效的,使得这两种方法之间的差异无关紧要。请阅读Joel Spolsky的"Back to Basics"了解详情。

这是一个C ++通常比C表现更好的实例。对于使用std :: string进行重量级的字符串处理可能更有效,更安全。

[编辑]

[第二次编辑]更正的代码(C字符串实现中的迭代次数过多),时间和结论会相应更改

我很惊讶Andrew Bainbridge的评论说std :: string比较慢,但他没有发布这个测试用例的完整代码。我修改了他的(自动化时间)并添加了一个std :: string测试。该测试采用VC ++ 2008(本机代码),默认为“Release”选项(即优化),Athlon双核,2.6GHz。结果:

C string handling = 0.023000 seconds
sprintf           = 0.313000 seconds
std::string       = 0.500000 seconds

所以这里strcat()到目前为止更快(你的milage可能因编译器和选项而异),尽管C字符串约定固有的低效率,并且支持我的原始建议sprintf()带有很多不需要的包袱以此目的。然而,它仍然是最不可读和安全的,所以当性能不是很关键时,IMO没什么好处。

我还测试了一个std :: stringstream实现,它再次慢得多,但对于复杂的字符串格式仍然有优点。

更正后的代码如下:

#include <ctime>
#include <cstdio>
#include <cstring>
#include <string>

void a(char *first, char *second, char *both)
{
    for (int i = 0; i != 1000000; i++)
    {
        strcpy(both, first);
        strcat(both, " ");
        strcat(both, second);
    }
}

void b(char *first, char *second, char *both)
{
    for (int i = 0; i != 1000000; i++)
        sprintf(both, "%s %s", first, second);
}

void c(char *first, char *second, char *both)
{
    std::string first_s(first) ;
    std::string second_s(second) ;
    std::string both_s(second) ;

    for (int i = 0; i != 1000000; i++)
        both_s = first_s + " " + second_s ;
}

int main(void)
{
    char* first= "First";
    char* second = "Second";
    char* both = (char*) malloc((strlen(first) + strlen(second) + 2) * sizeof(char));
    clock_t start ;

    start = clock() ;
    a(first, second, both);
    printf( "C string handling = %f seconds\n", (float)(clock() - start)/CLOCKS_PER_SEC) ;

    start = clock() ;
    b(first, second, both);
    printf( "sprintf           = %f seconds\n", (float)(clock() - start)/CLOCKS_PER_SEC) ;

    start = clock() ;
    c(first, second, both);
    printf( "std::string       = %f seconds\n", (float)(clock() - start)/CLOCKS_PER_SEC) ;

    return 0;
}

答案 7 :(得分:0)

我不知道在第二种情况下有任何真正的连接。将它们背靠背打印不构成连接。

告诉我,这会更快:

1)a)将字符串A复制到新缓冲区 b)将字符串B复制到缓冲区 c)将缓冲区复制到输出缓冲区

1)将字符串A复制到输出缓冲区 b)将字符串b复制到输出缓冲区

答案 8 :(得分:0)

    与sprintf相比,
  • strcpy和strcat更简单,后者需要解析格式字符串
  • strcpy和strcat很小,所以它们通常会被编译器内联,甚至可以节省一个额外的函数调用开销。例如,在llvm中strcat将使用strlen内联来查找复制起始位置,然后是简单的存储指令

答案 9 :(得分:-1)

由于两种方法都必须计算字符串长度或每次扫描它,因此两者都不是非常有效。相反,既然你计算了各个字符串的strlen()s,那么把它们放在变量中然后只是strncpy()两次。