在Clang建造的不是那么内置?

bro*_*s94 9 c gcc clang

如果我有以下内容strlen.c:

int call_strlen(char *s) {
  return __builtin_strlen(s);
}
Run Code Online (Sandbox Code Playgroud)

然后像这样用gcc和clang编译它:

gcc -c -o strlen-gcc.o strlen.c

clang -c -o strlen-clang.o strlen.c
Run Code Online (Sandbox Code Playgroud)

我很惊讶地看到strlen-clang.o包含对"strlen"的引用,而gcc预计会内联函数并且没有这样的引用.(见下面的objdumps).这是clang中的错误吗?我已经在clang编译器的几个版本中测试了它,包括3.8.

编辑:这对我来说很重要的原因是我正在链接-nostdlib,并且clang编译的版本给我一个链接错误,找不到strlen.

@> objdump -d strlen-clang.o 

strlen-clang.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <call_strlen>:
   0: 55                    push   %rbp
   1: 48 89 e5              mov    %rsp,%rbp
   4: 48 83 ec 10           sub    $0x10,%rsp
   8: 48 89 7d f8           mov    %rdi,-0x8(%rbp)
   c: 48 8b 7d f8           mov    -0x8(%rbp),%rdi
  10: e8 00 00 00 00        callq  15 <call_strlen+0x15>
  15: 89 c1                 mov    %eax,%ecx
  17: 89 c8                 mov    %ecx,%eax
  19: 48 83 c4 10           add    $0x10,%rsp
  1d: 5d                    pop    %rbp
  1e: c3                    retq   


@> objdump -t strlen-clang.o 

strlen-clang.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000 strlen.c
0000000000000000 l    d  .text  0000000000000000 .text
0000000000000000 l    d  .data  0000000000000000 .data
0000000000000000 l    d  .bss 0000000000000000 .bss
0000000000000000 l    d  .comment 0000000000000000 .comment
0000000000000000 l    d  .note.GNU-stack  0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame  0000000000000000 .eh_frame
0000000000000000 g     F .text  000000000000001f call_strlen
0000000000000000         *UND*  0000000000000000 strlen
Run Code Online (Sandbox Code Playgroud)

GCC

@> objdump -d strlen-gcc.o 

strlen-gcc.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <call_strlen>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 89 7d f8             mov    %rdi,-0x8(%rbp)
   8:   48 8b 45 f8             mov    -0x8(%rbp),%rax
   c:   48 c7 c1 ff ff ff ff    mov    $0xffffffffffffffff,%rcx
  13:   48 89 c2                mov    %rax,%rdx
  16:   b8 00 00 00 00          mov    $0x0,%eax
  1b:   48 89 d7                mov    %rdx,%rdi
  1e:   f2 ae                   repnz scas %es:(%rdi),%al
  20:   48 89 c8                mov    %rcx,%rax
  23:   48 f7 d0                not    %rax
  26:   48 83 e8 01             sub    $0x1,%rax
  2a:   5d                      pop    %rbp
  2b:   c3                      retq   

@> objdump -t strlen-gcc.o 

strlen-gcc.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000 strlen.c
0000000000000000 l    d  .text  0000000000000000 .text
0000000000000000 l    d  .data  0000000000000000 .data
0000000000000000 l    d  .bss 0000000000000000 .bss
0000000000000000 l    d  .note.GNU-stack  0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame  0000000000000000 .eh_frame
0000000000000000 l    d  .comment 0000000000000000 .comment
0000000000000000 g     F .text  000000000000002c call_strlen
Run Code Online (Sandbox Code Playgroud)

Sig*_*uza 6

只是为了避免优化:

clang -O0

t.o:
(__TEXT,__text) section
_call_strlen:
0000000000000000    pushq   %rbp
0000000000000001    movq    %rsp, %rbp
0000000000000004    subq    $0x10, %rsp
0000000000000008    movq    %rdi, -0x8(%rbp)
000000000000000c    movq    -0x8(%rbp), %rdi
0000000000000010    callq   _strlen
0000000000000015    movl    %eax, %ecx
0000000000000017    movl    %ecx, %eax
0000000000000019    addq    $0x10, %rsp
000000000000001d    popq    %rbp
000000000000001e    retq
Run Code Online (Sandbox Code Playgroud)

clang -O3

t.o:
(__TEXT,__text) section
_call_strlen:
0000000000000000    pushq   %rbp
0000000000000001    movq    %rsp, %rbp
0000000000000004    popq    %rbp
0000000000000005    jmp _strlen
Run Code Online (Sandbox Code Playgroud)

现在,解决问题:

clang文档声称clang支持所有GCC支持的内置程序。
但是,GCC文档似乎将内置函数及其库等效项的名称视为同义词:

两种形式都具有与C库函数相同的类型(包括原型),相同的地址(采用地址时)和相同的含义。

此外,它也不保证具有等效库的内置函数(如的情况strlen)确实可以得到优化:

其中许多功能仅在某些情况下进行了优化。如果在特定情况下未对它们进行优化,则会发出对库函数的调用。

此外,clang内部手册__builtin_strlen仅提及一次:

  • __builtin_strlenstrlen:如果参数为字符串文字,则将它们折叠为整数常量表达式。

除此之外,他们似乎没有任何承诺。

由于在您的情况下,to的参数__builtin_strlen不是字符串文字,并且由于GCC文档允许将对内置函数的调用转换为库函数调用,因此clang的行为似乎是完全有效的。

一个铛开发人员邮件列表上的“补丁审查”也说:

[...]如果无法/不需要编译时评估,它仍然可以在运行时使用strlen库。

那是在2012年,但文字表明至少在那时,仅支持编译时评估。

现在,我看到两个选择:

  • 如果您只需要自己编译程序,然后使用和/或分发它,我建议您只使用gcc。
  • 如果您需要其他人能够同时在gcc和clang下编译代码,建议您添加C库作为静态链接的依赖项。

我强烈建议不要滚动自己的标准库函数实现,即使在看似简单的情况下(如果您不同意,请尝试编写自己的strlen实现,然后将其与glibc比较)。


use*_*ica 6

GCC和Clang都没有承诺内联此内置函数。您引用了一些GCC文档,这些文档似乎做出了这样的承诺:

... GCC内置功能始终是内联扩展的...

但这是脱离上下文的句子片段。完整的句子

除了具有库等效项(例如下面讨论的标准C库函数)或扩展为库调用的内置插件之外 GCC内置函数始终始终内联扩展,因此没有相应的入口点,因此它们的地址不能为获得。

__builtin_strlen具有等效的库strlen,因此此语句不保证是否内联。