Nie sądzę, że standard idzie w tak wiele szczegółów. Mówi jedynie, że z grubsza, jeśli symbol ma zewnętrzne powiązanie w różnych jednostkach tłumaczeniowych, jest to ten sam symbol. To sprawia, że wersja klangowa jest poprawna.
Od tego momentu, zgodnie z moją najlepszą wiedzą, jesteśmy poza standardem. Wybory kompilatorów różnią się w zależności od tego, co uważają za przydatne.
Zauważ, że g++ -c -std=c++11 -O3 -fPIE
wyjścia:
0000000000000000 <_Z4nextv>:
0: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 6 <_Z4nextv+0x6>
6: 83 c0 01 add $0x1,%eax
9: 89 05 00 00 00 00 mov %eax,0x0(%rip) # f <_Z4nextv+0xf>
f: c3 retq
0000000000000010 <_Z5indexi>:
10: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 16 <_Z5indexi+0x6>
16: 89 f9 mov %edi,%ecx
18: 83 c0 01 add $0x1,%eax
1b: 89 05 00 00 00 00 mov %eax,0x0(%rip) # 21 <_Z5indexi+0x11>
21: d3 e0 shl %cl,%eax
23: c3 retq
Więc GCC robi wiedzieć, jak zoptymalizować tego. Po prostu nie wybiera, kiedy używasz -fPIC
. Ale dlaczego? Widzę tylko jedno wytłumaczenie: pozwól na przesłonięcie symbolu podczas dynamicznego łączenia i spójrz spójnie efekty. Ta technika znana jest pod nazwą symbol interposition.
W udostępnionej biblioteki, jeśli index
rozmowy next
, jak next
jest widoczne globalnie, gcc musi rozważyć możliwość, że next
może zostać wstawiona. Więc używa PLT. Podczas korzystania z -fPIE
nie można jednak wstawiać symboli, więc gcc włącza optymalizację.
Czy błąd jest nieprawidłowy? Nie. Ale gcc wydaje się zapewniać lepsze wsparcie dla interpozycji symboli, co jest przydatne przy instrumentowaniu kodu. Czyni to kosztem pewnych kosztów ogólnych, jeśli zamiast tego używa się -fPIC
zamiast -fPIE
do budowania jego pliku wykonywalnego.
Dodatkowe uwagi:
W this blog entry z jednego z gcc programistów, wspomina, po zakończeniu postu:
Porównując niektóre odniesienia do dzyń, zauważyłem, że szczęk faktycznie ignoruj reguły interpolacji ELF. Chociaż jest to błąd, postanowiłem dodać flagę GCC, aby uzyskać podobne zachowanie. Jeśli interpozycja nie jest pożądana, oficjalną odpowiedzią ELF jest użycie ukrytej widoczności i jeśli symbol ma zostać wyeksportowany, zdefiniuj alias. Nie zawsze jest to praktyczne zadanie ręczne.
Po tym wyjściu wylądował mnie na x86-64 ABI spec. W sekcji 3.5.5 nakazuje, aby wszystkie funkcje wywołujące widoczne na całym świecie symbole musiały przejść przez PLT (idzie to aż do określenia dokładnej sekwencji instrukcji do użycia w zależności od modelu pamięci).
Tak więc, chociaż nie narusza standardu C++, ignorowanie interpozycji semantycznej wydaje się naruszać ABI.
Ostatnie słowo: nie wiedziałem, gdzie to umieścić, ale może cię zainteresować. Oszczędzę ci wysypisk, ale moje testy z opcji objdump i kompilatora wykazały, że:
Na gcc stronie rzeczy:
gcc -fPIC
: uzyskuje dostęp do last
przechodzi przez GOT, wzywa do next()
przechodzi przez PLT.
gcc -fPIC -fno-semantic-interposition
: last
przechodzi przez GOT, next()
jest zakreślony.
gcc -fPIE
:last
jest względne IP, next()
jest wstawione.
-fPIE
implikuje -fno-semantic-interposition
Na stronie dzyń rzeczy:
clang -fPIC
:last
przechodzi przez GOT, next()
jest inlined.
clang -fPIE
:last
przechodzi przez GOT, next()
jest wstawiony.
I zmodyfikowana wersja, która kompiluje do IP-krewnego inlined obu kompilatorów:
// foo.cxx
int last_ __attribute__((visibility("hidden")));
extern int last __attribute__((alias("last_")));
int __attribute__((visibility("hidden"))) next_()
{
return ++last_;
}
// This one is ugly, because alias needs the mangled name. Could extern "C" next_ instead.
extern int next() __attribute__((alias("_Z5next_v")));
int index(int scale) {
return next_() << scale;
}
Zasadniczo, to wyraźnie zaznacza, że pomimo ich udostępnianie na całym świecie, używamy ukryty wersję tych symboli, które będą zignorować każdy rodzaj interpozycji. Oba kompilatory następnie w pełni optymalizują dostęp, niezależnie od przekazanych opcji.
@Barry> Cieszę się, że okazało się to przydatne, nauczyłem się interesujących rzeczy badających to pytanie. Ciekawiło mnie również "ostatnie" i wykonałem kilka testów; dodał kilka ustaleń do postu. – spectras
Jaka jest przewaga atrybutu aliasu nad 'widocznością (" ukryty ")'? – Barry
@Barry> to, co tylko po to, aby atrybut był widoczny, tak aby semantyczna pozostała równoważna, ale z powodu braku wsparcia interpozycji. Oczywiście, jeśli nie ma zamiaru ich eksportować, można po prostu zlikwidować aliasy i mieć widoczność ("ukryte"). Nie testowałem tego, ale uważam, że kompilowanie z '-fvisibility = hidden -fvisibility-inlines-hidden' i ręczne oznaczanie odpowiednich symboli za pomocą' __attribute __ ((widoczność ("default"))) powinno dać ten sam wynik. – spectras