匿名字符串文字效率低下吗?

时间:2019-07-12 07:30:11

标签: c string

考虑以下代码:

bool isFoo(const char* bar) {
    return !strcmp(bar, "some_long_complicated_name");
}

在这里,字符串文字"some_long_complicated_name"立即传递给strcmp。这是否意味着每次调用isFoo时,都会在该堆栈帧上分配此字符串文字的许多字节?如果是这样,不是吗?

const char FOO_NAME[] = "some_long_complicated_name";
bool isFoo(const char* bar) {
    return !strcmp(bar, FOO_NAME);
}

更有效率吗?

3 个答案:

答案 0 :(得分:7)

不,它们并非效率低下。通常将它们放置在已编译二进制文件的只读存储器部分中,因为它们的大小在编译时就已知,并且在运行时无法修改。

字符串的昂贵部分(就运行时性能而言)是内存分配。在isFoo的两个版本中,都没有发生内存分配,因此我认为很难衡量两者之间的性能差异。 FOO_NAME从技术上讲在某些地方占用了一些字节,但是很可能会被编译器优化掉。

Here都是编译器资源管理器上的两个版本。与-O3的程序集并不相同,但是老实说,我无法进一步利用这些结果。

答案 1 :(得分:2)

常量字符串不会分配,它们仅存储在编译的二进制文件中,并可以通过指针进行访问。因此,没有,两种方法之间的速度没有差异。

答案 2 :(得分:1)

编译后的文件绝对没有变化。它将产生完全相同的二进制文件!

如果您在这样的单个可执行文件中编译两个版本:

bool isFoo(const char* bar) {
    return !strcmp(bar, "some_long_complicated_name");
}   

const char FOO_NAME[] = "some_long_complicated_name";
bool isFoo2(const char* bar) {
    return !strcmp(bar, FOO_NAME);
}   

int main()
{
    isFoo( "nnn" );
    isFoo2( "nnn" );
}

您可以研究二进制文件:

0000000000401156 <isFoo(char const*)>:
  401156:   55                      push   %rbp
  401157:   48 89 e5                mov    %rsp,%rbp
  40115a:   48 83 ec 10             sub    $0x10,%rsp
  40115e:   48 89 7d f8             mov    %rdi,-0x8(%rbp)
  401162:   48 8b 45 f8             mov    -0x8(%rbp),%rax
  401166:   be c0 20 40 00          mov    $0x4020c0,%esi
  40116b:   48 89 c7                mov    %rax,%rdi
  40116e:   e8 cd fe ff ff          callq  401040 <strcmp@plt>
  401173:   85 c0                   test   %eax,%eax
  401175:   0f 94 c0                sete   %al 
  401178:   c9                      leaveq 
  401179:   c3                      retq   

000000000040117a <isFoo2(char const*)>:
  40117a:   55                      push   %rbp
  40117b:   48 89 e5                mov    %rsp,%rbp
  40117e:   48 83 ec 10             sub    $0x10,%rsp
  401182:   48 89 7d f8             mov    %rdi,-0x8(%rbp)
  401186:   48 8b 45 f8             mov    -0x8(%rbp),%rax
  40118a:   be e0 20 40 00          mov    $0x4020e0,%esi
  40118f:   48 89 c7                mov    %rax,%rdi
  401192:   e8 a9 fe ff ff          callq  401040 <strcmp@plt>
  401197:   85 c0                   test   %eax,%eax
  401199:   0f 94 c0                sete   %al 
  40119c:   c9                      leaveq 
  40119d:   c3                      retq   

和字符串在这里:

4020c0 736f6d65 5f6c6f6e 675f636f 6d706c69  some_long_compli
4020d0 63617465 645f6e61 6d650000 00000000  cated_name......
4020e0 736f6d65 5f6c6f6e 675f636f 6d706c69  some_long_compli
4020f0 63617465 645f6e61 6d65006e 6e6e00    cated_name.nnn. 

您还可以在此处看到“ nnn”字符串!

输出是通过以下方式生成的:

  

objdump -s -S go | c ++ filt> x

注意:您必须使用-O0进行编译,否则编译器足够聪明,可以执行编译时已经存在的所有工作。如果我使用-O2,则无法再看到任何字符串,并且所有调用结果都已存在于二进制文件中。很高兴看到编译器可以在编译时完成多少工作!

因此完全没有区别,完全相同的二进制代码。但是通过标准优化,编译时就不会生成用于字符串比较的代码!

我修改了main,以查看比较结果用于以下地方:

    int main()
    {   
        volatile bool x;
        x = isFoo( "nnn" );
        x = isFoo2( "nnn" );
    }

生成的二进制文件:

0000000000401060 <main>:
    }

    int main()
    {
        volatile bool x;
        x = isFoo( "nnn" );
  401060:   c6 44 24 ff 00          movb   $0x0,-0x1(%rsp)
        x = isFoo2( "nnn" );
    }
  401065:   31 c0                   xor    %eax,%eax
        x = isFoo2( "nnn" );
  401067:   c6 44 24 ff 00          movb   $0x0,-0x1(%rsp)
    }
  40106c:   c3                      retq

如您所见,比较结果已经存在于编译后的代码中。在运行时不再比较字符串。

有关速度和内存使用的所有问题:测量!如您在示例中看到的,结果与我们在其他答案中看到的大多数假设不同。如果速度或内存占用量确实很重要:查看编译器生成的结果。通常,它比您想的要完美得多!