匿名字符串字面量是否低效?

3
请看以下代码:

考虑以下代码:

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);
}

如何更高效?


这些在内存使用方面几乎是等价的。 - mvp
@mvp 为什么几乎而不是精确地? - user2039981
它们被放置在内存中并且每次重复使用,因为它们是常量。 - Michael Chourdakis
1
为什么是“几乎”而不是“精确”?唯一的区别是(1)在第二个版本中,名称FOO_NAME将在符号表中可见,以及(2)在第一个版本中,在某些(通常较旧的)编译器下,该数组将不被标记为const。但除此之外,字符串字面值的实现是作为一个无名的静态字符数组。(是的,运行时内存使用可能是相同的。) - Steve Summit
作为 strcmp() 的第二个参数的数组将作为数组第一个字节的地址传递给函数。实际的数组将在编译时生成(并链接到只读内存)。因此,在这两种情况下,strcmp() 将只传递一个内存指针。 - user3629249
3个回答

6
不,它们不是低效的。它们通常位于编译后二进制文件的只读内存部分,因为它们的大小在编译时就已知,并且不能在运行时修改。
在字符串的运行性能方面,较昂贵的部分是内存分配。在isFoo的两个版本中,都没有进行内存分配,所以我认为很难测量出两者之间的性能差异。 FOO_NAME 在技术上会占用一些字节空间,但很可能会被编译器优化掉。
这里是编译器资源管理器上的两个版本的汇编代码。链接。使用-O3编译选项的汇编代码并不相同,但说实话,我无法进一步利用这些结果。

@super 我不这么认为。请注意,在长版本中,它根本没有调用 strcmp。编译器理解了字符串比较,并完全用 ASM 指令 (cmpsb 和带有 repz 的循环) 替换了对 strcmp 的调用;而在短版本中,由于编译器不能像其他版本那样轻松地推断出字符串比较,因此必须调用 strcmp,尽管它是一个真正优化的函数,但 可能 比其他版本的两个 asm 函数慢。 - LoPiTaL
好的观点。有趣的是,使用 clang -O3,两个版本都是相同的。 - lubgr

2

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

“Original Answer”翻译成中文为“最初的回答”。


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

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

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


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