Dlaczego C zapewnia „powiązania” językowe, w których C ++ jest niedostateczne?

60

Ostatnio zastanawiałem się, kiedy używać C zamiast C ++ i vice versa? Na szczęście ktoś już mnie pobił i chociaż zajęło mi to trochę czasu, byłem w stanie przetrawić wszystkie odpowiedzi i komentarze do tego pytania.

Jednak jeden element w tym poście jest ciągle adresowany bez żadnego przykładu, weryfikacji lub wyjaśnienia:

„Kod C jest przydatny, gdy chcesz mieć wiele powiązań językowych dla swojej biblioteki”

To parafraza. Powinienem zauważyć, że kilka osób wskazuje, że w C ++ możliwe jest wiele powiązań językowych (za pośrednictwem niektórych externfunkcji), ale mimo to, jeśli czytasz ten post w całości, oczywiste jest, że C jest idealny do przenoszenia / wiązania językowego. Moje pytanie brzmi: dlaczego?

Czy ktoś może podać konkretne powody, dla których pisanie bibliotek w C pozwala na łatwiejsze powiązania i / lub przenośność w innych językach?

smeeb
źródło
6
Głównie ze względów historycznych i społecznych. Większość implementacji językowych i systemów wykonawczych jest zbudowanych powyżej C. Na przykład, środowiska wykonawcze Ocaml, SBCL, Haskell są kodowane w C (i nie zyskają wiele dzięki przekodowaniu w C ++)
Basile Starynkevitch
8
W praktyce przyklejanie oryginalnych bibliotek C ++ (pomyśl o Qt) do tych środowisk wykonawczych jest bolesne.
Basile Starynkevitch
3
@Basile: Qt nie jest prawdziwą biblioteką C ++. Ponadto, nawet w przypadku bardziej bolesnych bibliotek, jest to bolesne tylko dlatego, że wyrażają one faktycznie użyteczną semantykę.
DeadMG
2
Przeczytaj Essential COM autorstwa Dona Boxa. Wyjaśnia proces tworzenia ABI w C ++. COM był sposobem Microsoftu na stworzenie ABI. tj. biblioteki COM C ++ mogą być używane z C, VB lub innych języków.
user93353

Odpowiedzi:

69

C ma znacznie, znacznie prostszy interfejs, a jego zasady przekształcania interfejsu kodu źródłowego w interfejs binarny są na tyle proste, że generowanie zewnętrznych interfejsów do wiązania odbywa się w dobrze ustalony sposób. Z drugiej strony C ++ ma niezwykle skomplikowany interfejs, a zasady wiązania ABI nie są w ogóle standaryzowane, ani formalnie, ani w praktyce. Oznacza to, że prawie każdy kompilator dla dowolnego języka dla dowolnej platformy może powiązać się z zewnętrznym interfejsem C i dokładnie wiedzieć, czego się spodziewać, ale dla interfejsu C ++ jest to zasadniczo niemożliwe, ponieważ reguły zmieniają się w zależności od tego, który kompilator, która wersja i który platforma, na której zbudowano kod C ++.

W C nie ma też standardowych reguł implementacji języka binarnego, ale jest to rząd wielkości prostszy i w praktyce kompilatory używają tych samych reguł. Innym powodem utrudniającym debugowanie kodu C ++ jest wspomniana wyżej skomplikowana gramatyka, ponieważ debuggery często nie radzą sobie z wieloma funkcjami językowymi (umieszczanie punktów przerwania w szablonach, komendy rzutowania wskaźnika w oknach wyświetlania danych itp.).

Brak standardowego ABI (binarnego interfejsu aplikacji) ma jeszcze jedną konsekwencję - sprawia, że ​​wysyłanie interfejsów C ++ do innych zespołów / klientów jest niepraktyczne, ponieważ kod użytkownika nie będzie działał, chyba że zostanie skompilowany z tymi samymi narzędziami i opcjami kompilacji. Widzieliśmy już inne źródło tego problemu - niestabilność interfejsów binarnych z powodu braku enkapsulacji czasu kompilacji.

- Wadliwy C ++

Mason Wheeler
źródło
4
Należy zauważyć, że chociaż możliwe jest zdefiniowanie interfejsu API podobnego do C zaimplementowanego w C ++ ( extern "C"jest jeszcze więcej dodatkowych prac, które należy zrównoważyć z funkcjami dostarczonymi przez C ++)
jhominal
5
C ++ ABI nie ma znaczenia w kontekście pytania, gdzie można łatwo eksportować biblioteki C ++ extern "C".
DeadMG
12
@jhominal: Jest o wiele lepszy niż pisanie go w C, gdzie musisz zdefiniować interfejs C, a następnie musisz go zaimplementować w C, podczas gdy w C ++ możesz zdefiniować interfejs C, a następnie nie musisz implementować w C. Bez względu na to, w jakim języku zaimplementujesz, nadal musisz zdefiniować interfejs C - jest to oczywiście prawda w C ++, ponieważ jest w C lub w innym języku, który może ujawniać wiązania C.
DeadMG
9
Czy można dodać wyłączenie odpowiedzialności do łącza do tego napędu FQA?
Martin Ba
2
@DocBrown: Na pewno wygląda na to, że pytanie dotyczy powiązań języka C vs. powiązań języka C ++.
Mason Wheeler
32

Jeśli próbujesz komunikować się z mówcą w innym języku, pidgin jest łatwiejszy niż szekspirowski angielski.

Pojęcia C - wywołania funkcji, wskaźniki, ciągi zakończone znakiem NULL - są bardzo proste, więc inne języki mogą łatwo je zaimplementować wystarczająco dobrze, aby wywoływać funkcje C. Ze względów historycznych wiele innych języków jest zaimplementowanych w C, co jeszcze bardziej ułatwia wywoływanie funkcji C.

C ++ dodaje całkiem sporo rzeczy - klas, z dziedziczeniem i vtables oraz modyfikatorami dostępu; wyjątki, z rozwijaniem stosu i zmienianiem przepływu kontroli; szablony. Wszystko to utrudnia innym językom korzystanie z powiązań C ++: w najlepszym przypadku jest więcej „kleju” lub kodu interoperacyjności do wdrożenia, aw najgorszym przypadku koncepcje nie tłumaczą bezpośrednio (z powodu różnic w modelach klas, obsłudze wyjątków, itp.). W szczególności w przypadku szablonów po prostu ich użycie (utworzenie wystąpienia) zazwyczaj wymaga etapu kompilacji za pomocą kompilatora C ++, co znacznie komplikuje korzystanie z nich z innych środowisk.

Biorąc to wszystko pod uwagę, można przecenić trudność w udostępnianiu powiązań z biblioteki C ++ do innego języka:

  • Powiązania C ++ mogą być tak samo kompatybilne jak C, jeśli chcesz nad tym pracować. Jak wskazuje @DeadMG, C ++ obsługuje extern "C", więc możesz eksportować powiązania językowe w stylu C (z całą prostotą i kompatybilnością powiązań C) z biblioteki C ++ (z tym, że nie możesz ujawnić żadnej funkcji specyficznej dla C ++) .
  • Innym powszechnym sprzeciwem wobec powiązań języka C ++ jest brak stabilności ABI dla C ++, ale to też jest zawyżone; C ++ ABI są mniej znormalizowane niż C ABI, ale istnieją standardy i standardy de facto (Itanium C ++ ABI, który jest również używany w OS X ; de facto standard GCC dla Linuksa). Windows jest gorszy, ale nawet w systemie Windows pozostanie w jednej wersji Visual C ++ powinno działać dobrze.
Josh Kelley
źródło
1
Innym problemem związanym z udostępnianiem powiązania z biblioteki C ++ w innym języku jest to, że inny język może wymagać implementacji powiązań w C. Lub, w przypadku języków, które mają coś w rodzaju (.NET) P / Invoke lub (python) ctypes, to może nie zapewniać żadnych narzędzi do korzystania z C ++ ABI.
Random832
6
@ Random832: Co jest całkowicie nieistotne, gdy strona C ++ może po prostu zaoferować interfejs C. Nie musisz implementować wiązania w C, aby zaoferować wiązanie C.
DeadMG
21

C jest jednym z najstarszych wciąż istniejących języków. Jego ABI jest prosty i napisano w nim praktycznie każdy wciąż używany system operacyjny . Podczas gdy niektóre z tych systemów operacyjnych mogły dodawać elementy, np. W C # / .NET lub cokolwiek na górze, poniżej są bardzo mocno zanurzone w C.

Oznacza to, że w celu wykorzystania możliwości oferowanych przez system operacyjny, praktycznie każdy język programowania tam potrzebne drogę do współpracy z bibliotekami C w każdym razie . Perl, Java, C ++, wszyscy natywnie zapewniają sposoby „mówienia w C”, ponieważ musieli, jeśli nie chcieli wymyślać każdego koła z osobna.

To sprawia, że ​​C jest łaciną języków programowania. (Ile lat internetu przed tą metaforą musi być „angielski języków programowania”?)


Kiedy piszesz swoją bibliotekę w C, dostajesz interfejs kompatybilny z C za darmo (oczywiście). Jeśli piszesz biblioteki w C ++, to może dostać wiązań C, poprzez extern "C"deklaracje jak wspomniano.

Jednakże , można dostać te wiązania tylko na funkcjonalność , która może być wyrażona w ° C .

Dlatego interfejs API Twojej biblioteki nie może korzystać z ...

  • szablony,
  • zajęcia,
  • wyjątki,
  • wszelkie funkcje przyjmujące lub zwracające przedmioty.

Jednym prostym przykładem byłoby zmuszenie eksportowanych funkcji do przyjmowania i zwracania tablic ( []) zamiast std::vector(lub std::stringw tym przypadku).

Zatem nie tylko nie byłbyś w stanie zapewnić żadnej z dobrych rzeczy, które C ++ ma do zaoferowania klientom biblioteki, ale musiałbyś również podjąć dodatkowe wysiłki, aby „przetłumaczyć” swój interfejs API biblioteki z C ++ na „kompatybilny z C” ( extern "C").

Dlatego można stwierdzić, że C jest lepszym wyborem do implementacji biblioteki. Osobiście uważam, że zalety C ++ wciąż przewyższają konieczny wysiłek związany z extern "C"API, ale to tylko ja.

DevSolar
źródło
Wygląda na to, że Windows próbuje się oprzeć na platformie .NET, Android oparty jest na Javie (również C jako szczegół implementacji niektórych API), a iOS / OSX opiera się na Objective-C.
user253751
1
Angielski jest już dominującym językiem w świecie programowania. Bardziej dominujący niż w innych zawodach.
Siyuan Ren,
3
@immibis: Windows, Linux / Android i BSD / OSX to jądra napisane w C, z interfejsami napisanymi w (i dla) C. Bez względu na to, co jest wbudowane, Java potrzebuje JNI, Perl potrzebuje wywołań C,. NET potrzebuje wywołań C, Python potrzebuje wywołań C, Objective-C potrzebuje wywołań C. Żadne z tych nie potrzebuje wywołań C ++, o co starałem się powiedzieć.
DevSolar
@DevSolar Wiele rzeczy na Androida napisanych jest natywnie w Javie i nie używa JNI (możesz użyć go „wstecz”, aby wywołać kod Java z C, ale to tylko potwierdza, że ​​jest to natywnie Java). Brak doświadczenia z iOS / OSX, ale słyszę, że są takie same z Objective-C.
user253751
3
@immibis: Ale zrobić znać różnicę między jądrem systemu operacyjnego i jego przestrzeni użytkownika, prawda? Poważnie wątpię, że przestrzeń użytkownika głównie w języku Java czyni Androida mniejszym jądrem Linuksa z wywołaniami systemowymi w stylu C niż jądro Linux działające na moim pulpicie. Wątpię również, aby używali Javy w jądrze lub w oprogramowaniu pośrednim. Właściwie wiem, że używają tam C. Jest to problem z kurami i jajami, na odwrót. Zostało to zrobione wcześniej w C, a więc o wiele łatwiej jest nadal to robić w C.
DevSolar
6

Pozostawiając szczegóły inne odpowiedzi już zapewniają:

Powodem, dla którego tak wiele języków udostępnia wiązanie C, jest to, że wszystkie systemy operacyjne * nix i Windows udostępniają większość swojego interfejsu API systemu operacyjnego za pośrednictwem interfejsu C. W związku z tym implementacja języka musi już współpracować z C, aby móc działać na głównych Oses. Dlatego łatwo jest również zaoferować bezpośrednią komunikację z dowolnym interfejsem C z samego języka.

Martin Ba
źródło
5

Nie ma powodu. Jeśli semantyka, którą próbujesz wyrazić, jest zasadniczo zgodna z językiem C, a nie czymś podobnym do szablonów, nie ma powodu, aby łatwiej wiązać, jeśli implementacja jest napisana w języku C. W rzeczywistości z definicji interfejs C może być wypełnione przez dowolne wdrożenie, które może spełnić umowę binarną - w tym wdrożenie w innym języku. Istnieją języki inne niż C ++, które mogą implementować umowy binarne C, które mogą działać w ten sposób.

Sprowadza się to do ludzi, którzy nie chcą uczyć się nowych języków lub pomysłów, które mają naprawdę przydatną semantykę lub cechują się desperacką próbą znalezienia jakiegokolwiek powodu, aby pozostać w erze dinozaurów.

DeadMG
źródło
4
Myślę, że masz rację, a downvoters źle zrozumieli pytanie, ale tak naprawdę twoja odpowiedź pomija podkreślenie tego potencjalnego nieporozumienia: pytanie nie dotyczy „biblioteki z interfejsem C” vs. „biblioteki z interfejsem C ++”, chodzi o „biblioteka napisana całkowicie w C” vs. „biblioteka z interfejsem C napisana w C ++”.
Doc Brown
@Snowman: Problematyczne powiązania C ++ nie mają absolutnie nic wspólnego z żadnym problemem z ujawnieniem powiązań C.
DeadMG
2
Więc wybierzesz język, który ma przydatną semantykę i funkcje, a następnie zmusisz się do przetłumaczenia ich na interfejs C. Podczas gdy klas i szablonów można uniknąć (ale przede wszystkim zastanawiasz się, dlaczego używasz C ++), każda funkcja z powiązaniem C musi wychwytywać wyjątki, zamiast pozwalać im na ucieczkę do kodu C. Nie wspominając już o tym, że każdy, kto korzysta z biblioteki kompatybilnej z C, musi teraz poradzić sobie z C ++ i jego kolizjami ABI i niezgodnościami biblioteki, oprócz kolizji ABI podłoża i niezgodności bibliotek.
prosfilaes
2
@prosfilaes: Przekształcanie wyjątków w kody powrotu jest banalne. Nie trzeba unikać korzystania z klas, ponieważ można je łatwo obniżyć do interfejsu API języka C i nie trzeba unikać używania szablonów w implementacji. A twoi użytkownicy nie muszą się przejmować konfliktami C ++ ABI, chyba że budują ze źródła, w takim przypadku musisz poradzić sobie z językiem źródłowym, bez względu na to, co to jest.
DeadMG
4
Odrzuciłem głos nie dlatego, że niekoniecznie trzeba pisać bibliotekę z C API w C ++, ale dlatego, że istnieją dobre powody, aby używać C poza byciem dinozaurem. Jeśli piszesz dla API w języku X, niech to będzie C, FORTRAN, COBOL, BLISS lub Java, zawsze będzie czas, kiedy będzie łatwiej, łatwiej i poprawniej pisać w tym języku niż pisać w bardziej wyszukany , bardziej zabawny język i interfejs.
prosfilaes
2

Istnieją dwie główne osie w przypadku interakcji z innym językiem:

  • koncepcje, które interfejs może przenosić: tylko wartości? Bibliografia? generyczne?
  • jak interfejs jest implementowany w „binariach” (zwanych ABI)

C ma przewagę nad C ++ na tych dwóch frontach:

  • C ma głównie proste pojęcia, które pojawiają się w większości co drugim języku 1
  • O ABI plików binarnych C decyduje OS 2

Teraz, dlaczego większość języków ma podobny zestaw pojęć niż C, ponieważ może być albo „prosty”, albo „wcześniejszy”; nie ma to jednak znaczenia, chodzi o to, że tak.

Wręcz przeciwnie, C ++ ma złożone koncepcje, a o ABI decyduje każdy kompilator (choć wiele stosuje Itanimum ABI, z wyjątkiem Windows ...). W rzeczywistości Herb Sutter zaproponował, aby systemy operacyjne naprawiły C ++ ABI (dla poszczególnych systemów operacyjnych) w celu częściowego rozwiązania tego problemu. Należy również zauważyć, że C ++ FFI jest możliwy, D próbuje go 3 .

1 Z wyjątkiem variadics ( ...), nie są one proste

2 Czy C ma standardowy ABI?

3 Interfejs D do starszego kodu C ++

Matthieu M.
źródło
0

Zasadniczo sprowadza się to do standaryzacji ABI. Chociaż ani C, ani C ++ nie ma znormalizowanego ABI, którego inne języki mogą używać do interfejsu między zapisanymi plikami binarnymi, C stał się de facto standardem, wszyscy o tym wiedzą i wszyscy inni mogą stosować te same, proste zasady, które język ma w odniesieniu do typy i wywołania funkcji.

C ++ może mieć standardowy ABI, ale Stroustrup powiedział, że nie widzi takiej potrzeby. Mówi także, że trudno byłoby uzyskać konsensus od autorów kompilatorów (choć wątpię, że - komitet standardowy C ++ wydałby ABI podobny do istniejących, a autorzy kompilatorów po prostu zmieniliby kolejną wersję swoich kompilatorów, które czasami są niezgodne z plikami binarnymi i tak zbudowane ze starych wersji ich kompilatorów - przypominam sobie, jak ponownie skompilowałem kilka bibliotek z nowym kompilatorem Sun i stwierdziłem, że nie działały one ze starymi)

Zauważysz, że niektóre firmy przeszły na stosowanie standardowego ABI, Microsoft rozpoczął ten proces z COM jeszcze w latach 90., a dziś udoskonalili go do WinRT ABI (nie mylić z innym WinRT, który odnosi się do typu systemu operacyjnego tabeli), który pozwala programom napisanym w C # komunikować się z bibliotekami napisanymi w C lub C ++ (tj. własna warstwa systemu operacyjnego Microsoft jest napisana w C ++, udostępniona za pomocą WinRT i zużywana przez aplikacje C # podczas wywoływania dowolnej procedury wykonawczej systemu operacyjnego)

Nikt nie może nic zrobić, chyba że organ normalizacyjny podejmie kroki i naprawi tę sytuację. Microsoft oczywiście widzi w tym wartość i podjął kroki w celu rozwiązania tej kwestii na swojej platformie.

Tak więc odpowiedź brzmi: C nie zapewnia powiązań językowych. Zdarza się, że nikt ich nie słuchał i nie zużywa.

gbjbaanb
źródło
-2

Wszystkie odpowiedzi nie odpowiadają prawdziwemu problemowi: kompilacja w C ++ wprowadza „zmianę nazwy”, więc pliki binarne są niezgodne z „prostymi” wywołaniami funkcji.

Wszystkie rzeczy ABI to tylko próba standaryzacji.

Zasadniczo nie ma gwarancji, że można krzyżować funkcje skompilowane z różnymi kompilatorami, nawet jeśli trzymasz się zwykłego C ++. Dawniej było pewne, że są niekompatybilne, ale obecnie wkrada się normalizacja;)

OTOH C został zaprojektowany właśnie z myślą o „montażu na wysokim poziomie” i umożliwieniu wszelkiego rodzaju łatwego łączenia. Nie powinno dziwić, że lepiej nadaje się do sympatii między językami.

Uwaga dodatkowa: oryginalny kompilator C ++ (front) faktycznie stworzył źródło C, które trzeba było skompilować, dokładnie tak jak gcc, który tworzy asembler „pod maską”.

ZioByte
źródło