Pracuję nad projektem, który ma dużo starszego kodu C. Zaczęliśmy pisać w C ++ z zamiarem ostatecznej konwersji starszego kodu. Jestem trochę zdezorientowany co do interakcji między C i C ++. Rozumiem, że poprzez owinięcie C kodu z extern "C"
C ++ kompilator nie będzie Magiel C pseudonimów, ale nie jestem do końca pewien, jak zaimplementować to.
Tak więc u góry każdego pliku nagłówka C (po strażnikach dołączania) mamy
#ifdef __cplusplus
extern "C" {
#endif
a na dole piszemy
#ifdef __cplusplus
}
#endif
Pomiędzy nimi mamy wszystkie prototypy dołączania, typedef i funkcji. Mam kilka pytań, aby sprawdzić, czy rozumiem to poprawnie:
Jeśli mam plik C ++ A.hh, który zawiera plik nagłówka C Bh, zawiera inny plik C nagłówka Ch, jak to działa? Myślę, że kiedy kompilator wejdzie w Bh,
__cplusplus
zostanie zdefiniowany, więc otoczy kodextern "C"
(i__cplusplus
nie zostanie zdefiniowany w tym bloku). Tak więc, gdy wejdzie w Ch,__cplusplus
nie zostanie zdefiniowany, a kod nie zostanie zawiniętyextern "C"
. Czy to jest poprawne?Czy jest coś złego w pakowaniu kodu
extern "C" { extern "C" { .. } }
? Co zrobi drugiextern "C"
?Nie umieszczamy tego opakowania wokół plików .c, tylko pliki .h. Co się stanie, jeśli funkcja nie będzie miała prototypu? Czy kompilator uważa, że jest to funkcja C ++?
Używamy również kodu innej firmy, który jest napisany w C i nie ma tego rodzaju opakowania. Za każdym razem, gdy dołączam nagłówek z tej biblioteki, umieszczam
extern "C"
wokół #include. Czy to właściwy sposób, aby sobie z tym poradzić?Wreszcie, czy to dobry pomysł? Czy jest coś jeszcze, co powinniśmy zrobić? Będziemy mieszać C i C ++ w dającej się przewidzieć przyszłości i chcę się upewnić, że zajmiemy się wszystkimi naszymi bazami.
źródło
To ensure that the names declared in that portion of code have C linkage, and thus C++ name mangling is not performed.
(mam to z linku )Odpowiedzi:
extern "C"
tak naprawdę nie zmienia sposobu, w jaki kompilator odczytuje kod. Jeśli twój kod znajduje się w pliku .c, zostanie skompilowany jako C, jeśli znajduje się w pliku .cpp, zostanie skompilowany jako C ++ (chyba że zrobisz coś dziwnego w swojej konfiguracji).To,
extern "C"
co wpływa na połączenie. Po kompilacji funkcje C ++ mają zmienione nazwy - właśnie dlatego możliwe jest przeładowanie. Nazwa funkcji jest modyfikowana na podstawie typów i liczby parametrów, dzięki czemu dwie funkcje o tej samej nazwie będą miały różne nazwy symboli.Kod wewnątrz
extern "C"
jest nadal kodem C ++. Istnieją ograniczenia dotyczące tego, co można zrobić w zewnętrznym bloku „C”, ale wszystkie dotyczą łączenia. Nie można zdefiniować żadnych nowych symboli, których nie można zbudować za pomocą połączenia C. Oznacza to na przykład brak klas lub szablonów.extern "C"
bloki ładnie się zagnieżdżają. Jest też,extern "C++"
jeśli znajdziesz się beznadziejnie uwięziony wextern "C"
regionach, ale nie jest to dobry pomysł z punktu widzenia czystości.Teraz, szczególnie w odniesieniu do ponumerowanych pytań:
Odnośnie # 1: __cplusplus pozostanie zdefiniowany wewnątrz
extern "C"
bloków. Nie ma to jednak znaczenia, ponieważ bloki powinny być starannie zagnieżdżone.Odnośnie # 2: __cplusplus zostanie zdefiniowany dla każdej jednostki kompilacji uruchomionej przez kompilator C ++. Ogólnie oznacza to, że pliki .cpp i wszelkie pliki zawarte w tym pliku .cpp. To samo .h (lub .hh lub .hpp lub what-have-you) może być interpretowane jako C lub C ++ w różnych momentach, jeśli zawierają je różne jednostki kompilacji. Jeśli chcesz, aby prototypy w pliku .h odwoływały się do nazw symboli C, muszą one mieć,
extern "C"
gdy są interpretowane jako C ++, a nie powinny,extern "C"
gdy są interpretowane jako C - stąd#ifdef __cplusplus
sprawdzanie.Aby odpowiedzieć na pytanie nr 3: funkcje bez prototypów będą miały powiązanie z C ++, jeśli znajdują się w plikach .cpp, a nie w
extern "C"
bloku. Jest to w porządku, ponieważ jeśli nie ma prototypu, może być wywoływane tylko przez inne funkcje w tym samym pliku, a wtedy ogólnie nie obchodzi Cię, jak wygląda połączenie, ponieważ nie planujesz mieć tej funkcji i tak może zostać wywołany przez cokolwiek poza tą samą jednostką kompilacji.W przypadku nr 4 masz to dokładnie. Jeśli dołączasz nagłówek kodu z łączeniem C (np. Kod skompilowany przez kompilator C), musisz
extern "C"
nagłówek - w ten sposób będziesz mógł połączyć się z biblioteką. (W przeciwnym razie twój linker szukałby funkcji o nazwach takich jak_Z1hic
wtedy, gdy szukałeśvoid h(int, char)
5: Tego rodzaju miksowanie jest częstym powodem do używania
extern "C"
i nie widzę nic złego w robieniu tego w ten sposób - po prostu upewnij się, że rozumiesz, co robisz.źródło
extern "C++"
__cplusplus
to, aby określić, czyC++
jest używany, czy teżC
, więc zdefiniowanie go ręcznie / wyraźnie przeciwstawia się jego celowi ...extern "C"
nie zmienia obecności ani nieobecności__cplusplus
makra. To po prostu zmienia powiązanie i zniekształcanie nazw zapakowanych deklaracji.Możesz zagnieżdżać
extern "C"
bloki całkiem szczęśliwie.Jeśli skompilujesz swoje
.c
pliki jako C ++, wszystko, co nie jest wextern "C"
bloku i bezextern "C"
prototypu, będzie traktowane jako funkcja C ++. Jeśli skompilujesz je jako C, wtedy oczywiście wszystko będzie funkcją C.tak
W ten sposób możesz bezpiecznie mieszać C i C ++.
źródło
.c
pliki jako C ++, wszystko jest kompilowane jako kod C ++, nawet jeśli jest wextern "C"
bloku.extern "C"
Kod nie może korzystać z funkcji zależnych od C ++ konwencji wywołań (np przeciążenia sieci operatora), lecz z korpusem funkcją jest sporządzonych jak C ++, ze wszystkim, co za sobą pociąga.Kilka błędów, które są obietnicami doskonałej odpowiedzi Andrew Shelansky'ego i trochę się z tym nie zgadzać, tak naprawdę nie zmienia sposobu, w jaki kompilator odczytuje kod
Ponieważ prototypy funkcji są kompilowane jako C, nie można przeciążać tych samych nazw funkcji różnymi parametrami - jest to jedna z kluczowych cech mieszania nazw kompilatora. Jest to opisywane jako problem z połączeniem, ale nie jest to do końca prawdą - będziesz otrzymywać błędy zarówno od kompilatora, jak i linkera.
Błędy kompilatora wystąpią, jeśli spróbujesz użyć funkcji C ++ deklaracji prototypu, takich jak przeciążenie.
Błędy linkera pojawią się później, ponieważ wydaje się, że Twoja funkcja nie została znaleziona, jeśli nie masz zewnętrznego „C” opakowania wokół deklaracji, a nagłówek jest zawarty w mieszaninie źródeł C i C ++.
Jednym z powodów, aby zniechęcać ludzi do używania kompilacji C jako ustawienia C ++ jest to, że oznacza to, że ich kod źródłowy nie jest już przenośny. To ustawienie jest ustawieniem projektu, więc jeśli plik .c zostanie upuszczony do innego projektu, nie zostanie skompilowany jako c ++. Wolałbym, żeby ludzie poświęcili czas na zmianę nazwy sufiksów plików na .cpp.
źródło
Chodzi o ABI, aby zarówno aplikacja C, jak i C ++ mogły bez problemu korzystać z interfejsów C.
Ponieważ język C jest bardzo łatwy, generowanie kodu było stabilne przez wiele lat dla różnych kompilatorów, takich jak GCC, Borland C \ C ++, MSVC itp.
Podczas gdy C ++ staje się coraz bardziej popularny, do nowej domeny C ++ należy dodać wiele rzeczy (na przykład ostatecznie Cfront został porzucony w AT&T, ponieważ C nie mógł obejmować wszystkich potrzebnych funkcji). Tak jak w przeszłości funkcja szablonu i generowanie kodu czasu kompilacji, różni dostawcy różnych kompilatorów faktycznie wykonywali osobną implementację kompilatora i linkera C ++ osobno, rzeczywiste ABI nie są w ogóle kompatybilne z programem C ++ na różnych platformach.
Ludzie mogą nadal chcieć zaimplementować rzeczywisty program w C ++, ale nadal zachowują stary interfejs C i ABI jak zwykle, plik nagłówkowy musi zadeklarować zewnętrzny „C” {} , informuje kompilator, aby wygenerował zgodny / stary / prosty / łatwy C ABI dla funkcji interfejsu, jeśli kompilatorem jest kompilator C, a nie kompilator C ++.
źródło