Niedawno przeprowadziłem wywiad i zadałem jedno pytanie, jakie jest zastosowanie extern "C"
w kodzie C ++. Odpowiedziałem, że jest to użycie funkcji C w kodzie C ++, ponieważ C nie używa zniekształcania nazw. Zapytano mnie, dlaczego C nie używa przekłamywania nazwisk i szczerze mówiąc, nie mogłem odpowiedzieć.
Rozumiem, że gdy kompilator C ++ kompiluje funkcje, nadaje funkcji specjalną nazwę, głównie dlatego, że możemy mieć przeciążone funkcje o tej samej nazwie w C ++, które muszą zostać rozwiązane w czasie kompilacji. W języku C nazwa funkcji pozostanie taka sama, a może z _ przed nią.
Moje zapytanie brzmi: co jest złego w zezwalaniu kompilatorowi C ++ na manipulowanie funkcjami C również? Przypuszczałbym, że nie ma znaczenia, jakie nazwy nada im kompilator. Funkcje wywołujemy w ten sam sposób w C i C ++.
źródło
extern "C"
mówi, aby zmienić nazwę w taki sam sposób, jak zrobiłby to „” kompilator C.Odpowiedzi:
Odpowiedzi udzielono powyżej, ale spróbuję umieścić rzeczy w kontekście.
Najpierw C. był pierwszy. W związku z tym to, co robi C, jest niejako „domyślne”. Nie zmienia nazw, bo po prostu tego nie robi. Nazwa funkcji to nazwa funkcji. Globalny jest globalny i tak dalej.
Potem pojawił się C ++. C ++ chciał móc używać tego samego linkera co C i móc łączyć się z kodem napisanym w C. Ale C ++ nie mógł pozostawić „zniekształcenia” C (lub jego braku) takim, jakim jest. Sprawdź następujący przykład:
W C ++ są to odrębne funkcje z różnymi treściami. Jeśli żaden z nich nie zostanie zniekształcony, oba będą nazywane „funkcją” (lub „_funkcją”), a linker będzie narzekał na przedefiniowanie symbolu. Rozwiązanie C ++ polegało na zmianie typów argumentów na nazwę funkcji. Tak więc, jeden jest wywoływany,
_function_int
a drugi jest wywoływany_function_void
(nie jest to rzeczywisty schemat zniekształcenia), co pozwala uniknąć kolizji.Teraz mamy problem. Jeśli
int function(int a)
został zdefiniowany w module C, a my po prostu pobieramy jego nagłówek (tj. Deklarację) w kodzie C ++ i używamy go, kompilator wygeneruje instrukcję dla konsolidatora do zaimportowania_function_int
. Kiedy funkcja została zdefiniowana, w module C nie była tak nazywana. Nazywało się_function
. Spowoduje to błąd konsolidatora.Aby uniknąć tego błędu, podczas deklarowania funkcji mówimy kompilatorowi, że jest to funkcja zaprojektowana do łączenia lub kompilowania przez kompilator C:
Kompilator C ++ wie teraz, że
_function
zamiast importować_function_int
, i wszystko jest w porządku.źródło
-std=c++11
i unikać używania czegokolwiek spoza standardu. To to samo, co deklarowanie wersji Javy (chociaż nowsze wersje Javy są wstecznie kompatybilne). Nie jest to wina standardów, które ludzie używają rozszerzeń specyficznych dla kompilatora i kodu zależnego od platformy. Z drugiej strony nie można ich za to winić, ponieważ w standardzie brakuje wielu rzeczy (szczególnie IO, takich jak gniazda). Wydaje się, że komisja powoli to dogania. Popraw mnie, jeśli coś przeoczyłem.Nie chodzi o to, że „nie mogą” , w ogóle nie są .
Jeśli chcesz wywołać funkcję w wywoływanej bibliotece C
foo(int x, const char *y)
, nie jest dobrze pozwalać kompilatorowi C ++ zmienić ją nafoo_I_cCP()
(lub cokolwiek, po prostu wymyślił schemat zniekształcenia na miejscu) tylko dlatego, że może.Ta nazwa nie zostanie rozwiązana, funkcja jest w C, a jej nazwa nie zależy od listy typów argumentów. Dlatego kompilator C ++ musi to wiedzieć i oznaczyć tę funkcję jako C, aby uniknąć zniekształcenia.
Pamiętaj, że wspomniana funkcja C może znajdować się w bibliotece, której kodu źródłowego nie masz, wszystko, co masz, to wstępnie skompilowany plik binarny i nagłówek. Więc Twój kompilator C ++ nie może zrobić „swojej własnej rzeczy”, nie może w końcu zmienić tego, co jest w bibliotece.
źródło
extern "C"
:)Nie byłyby już funkcjami C.
Funkcja to nie tylko podpis i definicja; sposób działania funkcji zależy w dużej mierze od czynników, takich jak konwencja wywoływania. „Interfejs binarny aplikacji” przeznaczony do użycia na Twojej platformie opisuje, w jaki sposób systemy komunikują się ze sobą. ABI języka C ++ używany przez system określa schemat zniekształcania nazw, dzięki czemu programy w tym systemie wiedzą, jak wywoływać funkcje w bibliotekach i tak dalej. (Świetny przykład można znaleźć w C ++ Itanium ABI. Szybko przekonasz się, dlaczego jest to konieczne).
To samo dotyczy C ABI w twoim systemie. Niektóre C ABI faktycznie mają schemat zniekształcania nazw (np. Visual Studio), więc nie chodzi tu o „wyłączanie zniekształcania nazw”, a więcej o przełączanie się z C ++ ABI na C ABI dla niektórych funkcji. Oznaczamy funkcje C jako funkcje C, do których odnosi się C ABI (zamiast C ++ ABI). Deklaracja musi pasować do definicji (czy to w tym samym projekcie, czy w jakiejś zewnętrznej bibliotece), w przeciwnym razie deklaracja nie ma sensu. Bez tego twój system po prostu nie będzie wiedział, jak zlokalizować / wywołać te funkcje.
Jeśli chodzi o to, dlaczego platformy nie definiują interfejsów ABI C i C ++ jako takich samych i pozbywają się tego „problemu”, jest to częściowo historyczne - oryginalne ABI C nie były wystarczające dla C ++, który ma przestrzenie nazw, klasy i przeciążenie operatorów, wszystko które muszą być w jakiś sposób przedstawione w nazwie symbolu w sposób przyjazny dla komputera - ale można również argumentować, że dostosowywanie programów C teraz do C ++ jest niesprawiedliwe dla społeczności C, która musiałaby znosić znacznie bardziej skomplikowane ABI tylko ze względu na innych ludzi, którzy chcą interoperacyjności.
źródło
+int(PI/3)
, ale z jednym przymrużeniem oka: byłbym bardzo ostrożny, mówiąc o „C ++ ABI” ... AFAIK, są próby zdefiniowania C ++ ABI, ale nie ma rzeczywistych standardów de facto / de iure - jak isocpp.org/files /papers/n4028.pdf stwierdza (i całkowicie się z tym zgadzam), cytuję, to głęboko ironiczne, że C ++ faktycznie zawsze wspierał sposób publikowania API ze stabilnym binarnym ABI - uciekając się do podzbioru C C ++ poprzez zewnętrzne „C ”. .C++ Itanium ABI
jest tylko to - trochę C ++ ABI dla Itanium ... jak omówiono na stackoverflow.com/questions/7492180/c-abi-issues-listW rzeczywistości MSVC zmienia nazwy w C, chociaż w prosty sposób. Czasami dołącza
@4
lub inna mała liczba. Dotyczy to konwencji wywoływania i potrzeby czyszczenia stosu.Więc założenie jest po prostu błędne.
źródło
_
?/Gd, /Gr, /Gv, /Gz
. (Oznacza to, że używana jest standardowa konwencja wywoływania, chyba że deklaracja funkcji wyraźnie określa konwencję wywoływania). Zastanawiasz się,__cdecl
która jest domyślną standardową konwencją wywoływania.Bardzo często istnieją programy, które są częściowo napisane w C, a częściowo napisane w innym języku (często w języku asemblerowym, ale czasami w Pascalu, FORTRAN lub w czymś innym). Często zdarza się, że programy zawierają różne komponenty napisane przez różne osoby, które mogą nie mieć kodu źródłowego wszystkiego.
Na większości platform istnieje specyfikacja - często nazywana ABI [Application Binary Interface], która opisuje, co kompilator musi zrobić, aby utworzyć funkcję o określonej nazwie, która przyjmuje argumenty określonego typu i zwraca wartość określonego typu. W niektórych przypadkach ABI może zdefiniować więcej niż jedną „konwencję wywoływania”; kompilatory dla takich systemów często zapewniają środki wskazujące, która konwencja wywoływania powinna być używana dla określonej funkcji. Na przykład na komputerach Macintosh większość procedur Toolbox używa konwencji wywoływania Pascal, więc prototyp czegoś takiego jak „LineTo” wyglądałby tak:
Gdyby cały kod w projekcie został skompilowany przy użyciu tego samego kompilatora, nie miałoby znaczenia, jaką nazwę kompilator wyeksportował dla każdej funkcji, ale w wielu sytuacjach konieczne będzie, aby kod C wywoływał funkcje, które zostały skompilowane przy użyciu innych narzędzi i nie może być ponownie skompilowany za pomocą obecnego kompilatora [i może nawet nie być w C]. Możliwość zdefiniowania nazwy konsolidatora jest zatem krytyczna dla korzystania z takich funkcji.
źródło
Dodam jeszcze jedną odpowiedź, aby odnieść się do niektórych tangencjalnych dyskusji, które miały miejsce.
C ABI (interfejs binarny aplikacji) pierwotnie wywoływany do przekazywania argumentów na stosie w odwrotnej kolejności (tj. - wypychany od prawej do lewej), gdzie wywołujący również zwalnia pamięć stosu. Współczesny ABI faktycznie używa rejestrów do przekazywania argumentów, ale wiele z zagadnień związanych z zniekształcaniem wraca do tego oryginalnego przekazywania argumentów stosu.
W przeciwieństwie do tego oryginalny Pascal ABI przesuwał argumenty od lewej do prawej, a wywoływany musiał przełamać argumenty. Oryginalny C ABI jest lepszy od oryginalnego Pascala ABI w dwóch ważnych punktach. Kolejność wypychania argumentów oznacza, że przesunięcie stosu pierwszego argumentu jest zawsze znane, co pozwala funkcjom, które mają nieznaną liczbę argumentów, gdzie wczesne argumenty kontrolują liczbę innych argumentów (ala
printf
).Drugim sposobem przewodzenia C ABI jest zachowanie w przypadku, gdy dzwoniący i wywoływany nie zgadzają się co do liczby argumentów. W przypadku C, o ile nie masz dostępu do argumentów poza ostatnim, nic złego się nie dzieje. W Pascalu niewłaściwa liczba argumentów jest usuwana ze stosu i cały stos jest uszkodzony.
Oryginalny system Windows 3.1 ABI był oparty na Pascalu. Jako taki, używał Pascal ABI (argumenty w kolejności od lewej do prawej, callee pops). Ponieważ każda niezgodność w liczbie argumentów może prowadzić do uszkodzenia stosu, powstał schemat zniekształcenia. Każda nazwa funkcji została zniekształcona liczbą wskazującą rozmiar w bajtach jej argumentów. Tak więc na komputerze 16-bitowym następująca funkcja (składnia C):
Został zniekształcony
function@2
, ponieważint
ma dwa bajty. Zrobiono to tak, że jeśli deklaracja i definicja są niezgodne, konsolidator nie znajdzie funkcji, a nie uszkodzi stosu w czasie wykonywania. I odwrotnie, jeśli program łączy się, możesz być pewien, że poprawna liczba bajtów jest pobierana ze stosu na końcu wywołania.32-bitowy system Windows i nowsze wersje używają rozszerzenia
stdcall
zamiast tego ABI. Jest podobny do Pascala ABI, z wyjątkiem tego, że kolejność push jest podobna do C, od prawej do lewej. Podobnie jak w przypadku Pascala ABI, zniekształcanie nazwy zmienia rozmiar bajtów argumentów na nazwę funkcji, aby uniknąć uszkodzenia stosu.W przeciwieństwie do oświadczeń przedstawionych w innym miejscu tutaj, C ABI nie zmienia nazw funkcji, nawet w programie Visual Studio. I odwrotnie, funkcje zniekształcające udekorowane
stdcall
specyfikacją ABI nie są unikalne dla VS. GCC obsługuje również ten ABI, nawet podczas kompilacji dla Linuksa. Jest to szeroko używane przez Wine , który używa własnego modułu ładującego, aby umożliwić łączenie w czasie wykonywania skompilowanych plików binarnych Linuksa ze skompilowanymi bibliotekami DLL systemu Windows.źródło
Kompilatory C ++ używają zniekształcania nazw, aby umożliwić unikalne nazwy symboli dla przeciążonych funkcji, których sygnatura byłaby taka sama. Zasadniczo koduje również typy argumentów, co pozwala na polimorfizm na poziomie funkcji.
C nie wymaga tego, ponieważ nie pozwala na przeciążenie funkcji.
Zauważ, że zniekształcanie nazw jest jednym (ale z pewnością nie jedynym!) Powodem, dla którego nie można polegać na 'C ++ ABI'.
źródło
C ++ chce mieć możliwość współdziałania z kodem C, który łączy się z nim lub z którym łączy się.
C oczekuje nazw funkcji o niezmienionych nazwach.
Jeśli C ++ to zniekształci, nie znajdzie wyeksportowanych niezmienionych funkcji z C lub C nie znajdzie funkcji wyeksportowanych z C ++. Konsolidator C musi otrzymać nazwę, której sam oczekuje, ponieważ nie wie, że pochodzi z C ++ lub do niego trafia.
źródło
Zmiana nazw funkcji i zmiennych języka C umożliwiłaby sprawdzenie ich typów w czasie łączenia. Obecnie wszystkie implementacje (?) C pozwalają na zdefiniowanie zmiennej w jednym pliku i wywołanie jej jako funkcji w innym. Lub możesz zadeklarować funkcję z nieprawidłowym podpisem (np.
void fopen(double)
A następnie ją wywołać.Zaproponowałem schemat bezpiecznego dla typu łączenia zmiennych C i funkcji poprzez użycie zniekształcenia w 1991 roku. Schemat nigdy nie został przyjęty, ponieważ, jak zauważyli inni tutaj, zniszczyłoby to kompatybilność wsteczną.
źródło