2016-07-27 14 views
8

Jeśli mam następujące w strlen.c:Wbudowane w Clang nie tak wbudowane?

int call_strlen(char *s) { 
    return __builtin_strlen(s); 
} 

A potem skompilować go zarówno z gcc i brzękiem jak ten:

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

clang -c -o strlen-clang.o strlen.c 

Jestem zaskoczony, aby zobaczyć, że strlen-clang.o zawiera odniesienie do "strlen", podczas gdy gcc oczekiwał funkcji i nie ma takiego odniesienia. (patrz objdumps poniżej). Czy to błąd w klang? Przetestowałem to w kilku wersjach kompilatora clang, w tym 3.8.

Edycja: powodem, dla którego jest to dla mnie ważne, jest to, że łączę się z -nostdlib, a wersja skompresowana clang daje mi błąd łącza, którego nie znaleziono.

Clang

@> 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 

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 
+1

Nie ustawiłeś kompilatorów do optymalizacji, więc klang nie został zoptymalizowany. Wstrząsający. – EOF

+2

@EOF Jak to jest istotne? Na pewno, jeśli zostanie poproszony o skorzystanie z wbudowanego, powinien użyć wbudowanego - nie "użyj wbudowanego w tryb optymalizacji, być może, jeśli możesz się tym przejmować". Czy 'builtin' to opcjonalne żądanie typu' inline'? Jeśli tak, czy możesz połączyć nas z bitem w dokumentacji Clang, która to określa? –

+2

@EOF FYI Kompilowałem z '-O3', a zestaw jest dużo krótszy, ale nadal zawiera' jmp _strlen'. – Siguza

Odpowiedz

4

Wystarczy, aby uzyskać optymalizację z drogi:

Z 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 

Z clang -O3

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

teraz na ten problem:

Dokumentacja dzyń twierdzi, że clang wsparcie wszystkich wspieranych builtins GCC.
Jednak GCC documentation wydaje się traktować wbudowanych funkcji i nazwy ich ekwiwalentów bibliotecznych jako synonimy:

Obie formy mają ten sam typ (w tym prototypie), tym samym adresem (gdy ich adres jest pobierany), a to samo znaczenie co funkcje biblioteki C [...].

także nie gwarantuje wbudowaną funkcję o równoważnej biblioteki (jak ma to miejsce w przypadku strlen) do rzeczywiście dostać zoptymalizowany:

Wiele z tych funkcji są zoptymalizowane tylko w niektórych przypadkach; jeśli nie są zoptymalizowane w konkretnym przypadku, wywoływane jest wywołanie funkcji bibliotecznej.

Ponadto clang internals manual wymienia __builtin_strlen tylko raz

  • __builtin_strlen i strlen: są to stałe złożone jako liczba całkowita wyrażenia stała, jeżeli argument jest Łańcuch znaków.

Poza tym nie wydają żadnych obietnic.

Ponieważ w twoim przypadku argument do __builtin_strlen nie jest literałem łańcuchowym, a ponieważ dokumentacja GCC pozwala na wywoływanie wywołań wbudowanych funkcji do konwersji na wywołania funkcji bibliotecznych, zachowanie klanu wydaje się być całkowicie poprawne.

"patch for review" on the clang developers mailing list mówi:

[...] To będzie nadal spadać z powrotem do użytku wykonawczego z strlen biblioteki, jeśli ocena kompilacji nie jest możliwe/konieczne [...].

To było w 2012 roku, ale tekst wskazuje, że przynajmniej wtedy była obsługiwana tylko ocena podczas kompilacji.

Teraz widzę dwie opcje:

  • Jeśli wystarczy skompilować program samodzielnie, a następnie użyć i/lub rozpowszechniać go, proponuję po prostu użyć gcc.
  • Jeśli potrzebujesz innych, aby móc skompilować swój kod pod gcc i clang, sugeruję dodanie biblioteki C jako zależności do łączenia statycznego.

zdecydowanie doradzamy przeciwko toczenia własne implementacje standardowych funkcji bibliotecznych, nawet pozornie prostych przypadkach (jeśli nie zgadzasz, spróbuj pisanie własnego strlen wdrożenia, a następnie porównać go do the glibc one).

+0

Ten glibc próbuje uzyskać lepszą wydajność: fajnie! –

+0

"Zdecydowanie odradzam toczenie własnych implementacji standardowych funkcji bibliotecznych" - Jakiś inny powód poza wydajnością? Zauważ, że łatwo jest mieć tryb debugowania, w którym uruchamiasz obie funkcje i sprawdzasz, czy wysyłają one to samo. – TLW

+1

@TLW Błędy i zgodność ze specyfikacją. Podstawowa funkcjonalność nie jest zbyt trudna, aby uzyskać prawo lub debugowanie, ale przypadki skrajne mogą być trudne do wykrycia. Nawet proste funkcje mogą się skomplikować, jeśli próbujesz zoptymalizować wydajność. A jeśli wejdą w kontakt z danymi wprowadzonymi przez użytkownika, po prostu [tyle błędów pamięci można wykorzystać] (https://github.com/struct/mms/blob/master/Modern_Memory_Safety_In_C_CPP.pdf). W przeciwieństwie do tego, standardowe funkcje biblioteki C były kontrolowane od dziesięcioleci. – Siguza

4

Ani GCC ani Clang nie obiecują wbudować tego wbudowanego. You quoted część dokumentacji GCC zdaje się zrobić taką obietnicę:

... GCC wbudowane funkcje są zawsze rozszerzoną inline ...

ale jest to fragment zdanie wyciągnięta z kontekstu. The complete sentence jest

Z wyjątkiem wbudowanych dodatków, które mają odpowiedniki biblioteki takich jak standardowe funkcje biblioteki C omówione poniżej lub, które rozszerzają się w celu połączenia z biblioteki, GCC wbudowane funkcje zawsze rozszerzony stylei w związku z tym nie ma odpowiednich punktów wejścia i ich adres nie może zostać uzyskany.

__builtin_strlen ma odpowiednik biblioteczny strlen, więc to zdanie nie daje żadnych obietnic dotyczących tego, czy zostanie on zainicjowany.