Article View: pl.comp.lang.c
Article #267817Re: Ciemna strona C++
From: Sektor van Skijlen
Date: Mon, 26 Nov 2007 17:20
Date: Mon, 26 Nov 2007 17:20
325 lines
16780 bytes
16780 bytes
Dnia Sun, 25 Nov 2007 22:19:32 +0100, Seweryn Habdank-Wojewódzki skrobie:
> Witam
> Marcin ?Qrczak? Kowalczyk wrote:
> >> Teraz mam pytanie jaka funkcja systemowa to wykonuje i czemu ten .so
> >> umie być obsługiwany jak C++, a nie jak C?
> >
> > Nie rozumiem pytania. Pod Linuxem biblioteki C++ są obsługiwane tak samo
> > jak biblioteki C, tymi samymi mechanizmami. Linker nie musi rozróżniać,
> > czy ma do czynienia z C, czy z C++.
> No zatem czemu po użyciu dlopen w kodzie należy w zasadzie wszystko tworzyć
> od zera (extern "C", funkcje trampolinowe, fabryka obiektów), a kiedy mam
> swoje .so wołane przez mój program w C++ nie mam ANI JEDNEGO Z TYCH cudów.
Co trzeba znów tworzyć od zera?
Jeśli używasz dlopen, to potem masz taką akcję:
- wołasz dlsym
- dostajesz void*
- który jest wskaźnikiem do funkcji
Ponieważ biblioteki *.so są robione na tej samej zasadzie, co jest wymagane
dla języka C, to:
- obiekty (w tym "generowane przez kompilator C++") są obiektami
- funkcje, metody, metody pomocnicze itd. są funkcjami (manglowanie tylko do
tego, żeby je umieć rozróżnić)
I tyle.
Jeśli teraz masz aplikację, która wiąże się z *.so, to aplikacja (dokładnie to
kompilator) robi mniej więcej coś takiego:
- klasa nazywa się Klocek
- wołana metoda to set( int, int ) (zauważ brak typu zwracanego)
to znaczy, że (napiszę to wedle starej konwencji manglowania) program chce się
odwołać do funkcji o nazwie:
set__6Klocekii
I taki symbol jest potem poszukiwany w bibliotece, jako funkcja. Woła się
normalnie funkcje, funkcja zwraca co ma zwrócić, traktowanie w odpowiedni
sposób wartości zwracanej jest określone w nagłówko.
Do tego, żeby to wszystko działało aplikacja potrzebuje wiedzieć tylko tyle:
class Klocek
{
...
void set( int, int );
...
};
A biblioteka dynamiczna, z którą się wiąże musio mieć tylko tyle (napiszę to w
jakimś pseudojęzyku):
define set__6Klocekii
{
*(stackframe[0]+0) = *stackframe[1];
*(stackframe[0]+4) = *stackframe[2];
}
I tyle. Mając taki kod, linker tylko podwiązuje adres funkcji, której żąda
wpis w aplikacji i mamy normalnie związany kod. Linkre do tego celu nie
potrzebuje wiedzieć, czy kod jest pisany w C, czy C++, czy jeszcze jakimś
innym języku.
A jeśli biblioteka *.so potrzebuje wykonać jakiś kod na rozpoczęcie i
zakończenie - to, wedle Linuxa (a może nawet POSIX-a, nie pamiętam) powinna
eksportować symbole odpowiednio _init i _fini. Niewukluczone, że niektóre
biblioteki w C++ to robią, nie sprawdzałem.
> Nie rozumiem, co należy zrobić, aby w swoim programie móc użyć dlopen i nie
> musieć się bawić w C. Jak wiesz to powiedz. Jak chcesz wyślę Ci kod demo o
> co mi chodzi.
Nic się nie da zrobić.
Dopóki C++ nie dorobi się własnych bibliotek do obsługi bibliotek
dynamicznych, to będzie musiał - w razie chęci ręcznej obsługi - polegać na
dlopen, a tym samym bawić się w C.
A problem w przypadku C++ jest tutaj o wiele poważniejszy. Polega on na tym,
że - niestety - C++ (C zresztą również) nie definiuje możliwości konwersji
void* na wskaźnik do funkcji (a już na pewno metody). Tzn. rzutować możesz,
ale nikt ci nie zapewni, że tak zrzutowaną wartość możesz wołać jako funkcję.
To jest tylko lokalne zapewnienie POSIX-a, które należy traktować jako
rozszerzenie języka C (wszystkie POSIX-compliant kompilatory C muszą to
zagwarantować).
Nie ma to nic wspólnego z wiązaniem podczas uruchamiania aplikacji, które i
tak odbywa się na poziomie języka C. Problem jest ze współpracą pomiędzy dlsym
a językiem C++. Której po prostu nie ma.
To oznacza, że jeśli masz np. metodę Klocek::set(int,int), masz nagłówek do
klasy Klocek, to jeśli bibliotekę wczytujesz i używasz ręcznie, to jedyne, co
możesz zrobić, to:
void* sym = dlsym( library, "set__6Klocekii" );
typedef void Klocek_set_t( Klocek*, int, int );
Klocek_set_t* Klocek_set = (Klocek_set_t*)sym;
...
Klocek_set( k, 2, 5 );
Nie ma żadnej możliwości, żebyś mógł zrobić coś w stylu:
Klocek.*Klocek_set( 2, 5 );
bo nie jesteś w stanue za pomocą dlsym uzyskać wskaźnika do metody.
Rozszerzenie POSIX-a gwarantuje ci jedynie poprawne zrzutowanie void* na
wskaźnik do funkcji (o dowolnej sygnaturze), ale nie do metody. Bo i nie może:
wskaźnik do metody ma elementy zależne od implementacji i jest dłuższy od
wskaźnika do funkcji (w gcc ma 8 bajtów). To, co ci zwróci dlsym (te 4 bajty),
to wskaźnik na implementację, dzięki której owszem możesz wywołać tą metodę,
ale nie jako metodę.
Zauważ, że metodę prywatną też możesz w ten sposób zawołać.
> > Jedyna różnica jest taka, że biblioteka C++ może wymagać inicjalizacji
> > zanim wywołamy jakąś funkcję z niej i podobnie może zawierać kod do
> > wykonania przy zamykaniu programu.
> Odnoszę wrażenie, że nie tylko.
Tylko. I te dwie rzeczy załatwiają funkcje _init i _fini.
> > Nieprawda. Nazwy C++ nie są rozmanglowywane ani przy normalnym działaniu
> > programu, ani w kompilatorze (poza takimi narzędziami jak objdump i gdb,
> > gdzie zdemanglowane nazwy są pokazywane użytkownikowi), a manglowane są
> > tylko przez kompilator, nie w czasie działania. Linker nie wie, co to
> > jest klasa C++. Nie ma żadnego cxxdlopen, używany jest zwykły dlopen.
> To powiedz jak?
To jest na tej samej zasadzie, co crypt.
Nie ma dekodowania hasła, tylko jest kodowanie po jednej i po drugiej stronie
i porównanie zakodowanych wartości. Na tej samej zasadzie, nikt nie demangluje
symboli C++ (poza c++filt, w celach czysto wizualnych).
> > Masz jakieś fałszywe wyobrażenie na temat linkowania C++ (to już wyszło
> > parę miesięcy temu), ale nie wiem, na czym polega nieporozumienie.
> Na tym, że ja wiem co mi działa i próbuję zrozumieć dlaczego, a gro robi na
> odwrót -- zafixuje się, że coś się nie da zrobić i nawet nie próbują
> walczyć z tematem.
Jeśli chcemy obsługiwać biblioteki dynamiczne po C++-owemu, czyli mieć
możliwość wczytania klasy z pliku, to musimy mieć część informacji zawartej w
nagłówkach (noszę się ostatnio z zamiarem zdefiniowania dla C++ możliwości
dystrybucji bibliotek wraz z plikiem nagłówkowym) oraz mieć możliwość
"wczytania klasy", to znaczy możliwości pobrania informacji o obiekcie
charakterystycznym (nikt nie powiedział, że implementacja nie może sobie
umieszczać informacji o rozkładzie pól i ich typach w obiektach
charakterystycznych), liście metod, i reszcie spraw ich dotyczących.
I tak pomału dojdziemy do tego również, że skoro każda klasa i struktura może
mieć swój obiekt charaktertstyczny (nawet jeśli nie jest polimorficzna), to
wszystkie top-level physical entity (funkcje i zmienne globalne) też powinny
mieć swój obiekt charakterystyczny. Tym sposobem zbliżymy się do zdefiniowania
"klasy main", zawierającej wszystkie "wolnostojące" definicje. A stąd już
tylko krok do zdefiniowania metody kompilowania C++ na platformę JVM.
> A jest płacz, że im API C++ nie chce się listować w programach. Tylko, że
> generalnie wszyscy sprowadzają programy do API C.
> Pprzykładem jest decyzja projektowa koderów biblioteki ODE. Oni ODE piszą w
> C++, ale API wystawiają do C, aby C koderzy też mogli jej użyć, a potem C++
> koderzy muszą robić swoje wrapery w C++ -- to nie jest złe (ja tak
> zrobiłem), ale faktem jest, że typowe API C++ programowo nie często używane
> jako podstawa nawet w projektach OS, gdzie nie ma problemu z kompilacją i
> dziwnościami ABI.
Niekoniecznie - w VxWorksie jest systemowe API w C++ (jako dodatkowe,
oczywiście).
> > Oraz w jaki sposób należy zwalniać; w C++ to jest
> > mniejszy problem niż w C, bo destruktory i delete są ustandaryzowanym
> > interfejsem, a w C każda biblioteka stosuje własne konwencje.
> No więc czemu narzekasz.
Cicho! Nareszcie usłyszałem jakąś pozytywną opinię o C++ z ust Qrczaka - kiedy
to opijamy? :)
> >> > ani nie mówi o wyjątkach, które dana funkcja może rzucić (a jest to
> >> > potrzebne, żeby użyć tej funkcji i przetłumaczyć dowolny idący z niej
> >> > wyjątek na inny język, w przeciwieństwie do Javy, kiedy to wystarczy
> >> > złapać Throwable i rozpoznać typ w czasie wykonania).
> >>
> >> Rozumiem. I kto tłumaczy te wyjątki? Maszyna wie że JRantajWyjątek
> >> ma przenieść na PyWywaliłSięSystem?
> >
> > Można nawet nie tłumaczyć, byle przechować go w takiej formie, żeby po
> > wyjściu z powrotem do drugiego języka wyjątek był z powrotem taki sam.
> No ale (tak mi się wydaje) że jeśli złapiesz wyjątek i wyślesz go z powrotem
> do C++, to on go zrozumie.
Zgadza się, problem tutaj tylko sprowadza się do dylematu "jak w języku
C/Python/Tcl/Java złapać i nakazać ponowne rzucenie wyjątku C++". "Tylko".
Ja byłbym zwolennikiem takiego sposobu podejścia, że:
- albo język nie posiada mechanizmu wyjątków i każdą funkcję robimy na
zasadzie:
int funkcja_eksportowana( int arg1, int arg2 ) throw FunkcjaZglupiala ;
->
Status funkcja_importowana( int& retval, int arg1, int arg2 );
- albo język posiada wyjątki i wyjątki C++ jakimś sposobem tłumaczymy na
wyjątki tego języka. Wtedy sygnatura byłaby bardziej zbliżona do oryginału.
> > W C++ złapać wyjątek nie znając jego typu można tylko w ten sposób:
> > catch (...)
> > ale wtedy jedyne, co można zrobić z takim wyjątkiem, to rzucić go
> > dalej zanim blok catch się skończy. Nie można zapisać go w zmiennej,
> > zakończyć ten fragment kodu, a w innym miejscu odczytać go z tej
> > zmiennej i rzucić dalej.
> Są dwa wyjścia, albo tworzysz sensowną strukturę klas wyjątków dynamicznych,
> albo zamieniasz wyjątki na error code. Rozumiem, że na początku był
> {J}Object, ale tak jest w J świecie.
Ale w Javie każdy wyjątek musi być pochodną java.lang.Throwable.
W C++ co najwyżej można pójść na kompromis, że jeśli dostaniesz wyjątek nie
będący pochodną std::exception, to walisz jakiś super-wyjątek.
> W C++ nie ma jedynego stwórcy.
Gdyby twórcy od początku odrobinę pomyśleli, to tak by było od zawsze z
std::exception. Wszystkie języki - poza C++ - które mają wyjątki, mają
ograniczenie typu wyjątków. I C++ też mógł to mieć, bo ma do tego naturalną
infrastrukturę. Z jakiegoś powodu nie przystano na to. Może kiedyś zrobią tę
cechę deprecated, choć wątpię, bo zasada "nie płać..." dotyczy również
biblioteki standardowej.
> >> > - rozkład pól w strukturach (nazwy pól, typy, ich kolejność)
> >> > i metod w klasach (w przypadku C++)
> >>
> >> A po co?
> >
> > Żeby się do nich odwołać. Skąd implementacja wiązania międzyjęzykowego
> > ma wiedzieć, że jeśli program w drugim języku chce się dostać do pola
> > "st_mtime" obiektu typu "struct stat", to zaczyna się ono pod offsetem
> > 64 i ma typ time_t? W pliku *.so tej informacji nie ma.
> Nie ma, hmm ... na pewno masz rację. Ale mam wątpliwości, że ta impotencja
> jest cechą fizyczną, a raczej cechą umysłu kogoś kto to tam poukładał.
> Mam taki plik .so gdzie wystawiam stosowne API globalne. Reszta jest w
> pimplu tak poukładana, że usera tego modułu naprawdę takie detale nie
> interesują. W zasadzie mam takiego {J}Beana. Jest ustalone API obiektowe i
> to jest wołane. Headery są naprawdę minimalne, nie ma tam żadnych gołych
> pól. Z resztą {J}Beany też tak mają.
Zgadza się.
A do tego nawet nie potrzebujesz pimpla. W zupełności wystarczy, że masz
dostęp do metod i możesz je wywoływać.
> > Nie istnieje cxxdlopen. W pliku *.so tej informacji nie ma (chyba że
> > w sekcji dla debuggera, która jest opcjonalna i do której nie ma API,
> > bo program normalnie z niej nie korzysta). Ze zmanglowanych nazw można
> > od biedy odczytać tylko informację o typach argumentów, bo tylko ona
> > jest potrzebna do rozróżnienia przeciążonych funkcji. Zresztą nazwy
> > typów argumentów nie zawsze wystarczają, bo jeśli argumentem jest
> > struktura przekazywana przez wartość, to nie wiadomo, jaki jest jej
> > sizeof. W pliku *.so tej informacji nie ma, bo ona wynika z pliku
> > nagłówkowego i jest rozproszona w każdym miejscu skompilowanego kodu,
> > który alokuje tę strukturę (albo np. odwołuje się do pola innej
> > struktury występującego po tej strukturze) w formie nie nadającej się
> > do automatycznego odzyskania.
> To w jaki sposób mój .so wie jak się wpiąć do programu? Skąd mój program wie
> co ma pobierać? Co jest uruchamiane w kodzie? Rozumiem że się nie znam na
> kompilatorach i nie wiem co to linkowanie (tego nie miałem na studiach).
> Możesz mi powiedzieć, będę wdzięczny.
Masz nazwę i ta nazwa jest szukana, po nazwie znajdowana jest funkcja, funkcja
zostanie wywołana. Nie ma to nic skomplikowanego.
> > W Javie inlajnowaniem zajmuje się kompilator JIT i ten fakt nie blokuje
> > możliwości dynamicznego rozwiązywania odwołań do metod w dynamicznie
> > łączonych bibliotekach.
> Może. Nie znam się na JIT. Wiem tyle, że nawet JIT i wszystkie zalety i cuda
> zawarte w J dla mnie są pochowane.
> Kod moich algorytmów jest wolniejszy i pożera dużo więcej pamięci niż
> odpowiednik w C++. Może tam są cuda na kiju, ale dla moich prostych
> zastosowań te cuda na kiju się nie wykazują pozytywnie, zresztą dlatego
> przestałem zawodowo pisać w J.
Ale to i tak wciąż niczego nie oznacza.
Optymalizacja JIT ma tak naprawdę znaczenie dopiero w przypadku kodu SERWISU.
Serwis działa sobie dwa dni i na bieżąco skraca sobie ścieżki i się
optymalizuje. Ale do tego celu musi sobie te trochę czasu podziałać.
> > Najwyżej takie dynamicznie łączenie,
> > w odróżnieniu od kompilowania kodu razem, nie skorzysta na inlajnowaniu,
> > ale przynajmniej w ogóle jest możliwe bez ciągnięcia ze sobą połowy
> > kompilatora (frontendu).
> Tak. Dlatego lepszy od J jest Python i Tcl -- każdy język dynamiczny, bo
> wszystko w ogóle może być pisane w locie i nie ma problemu. Tak więc co
> oferuje J?
No właśnie. Dlatego nie rozumiem narzekania, że C++ jest słaby w introspekcji.
--
// _ ___ Michal "Sektor" Malecki <sektor(whirl)kis.p.lodz.pl>
\\ L_ |/ `| /^\ ,() <ethouris(O)gmail.com>
// \_ |\ \/ \_/ /\ C++ bez cholesterolu: http://www.intercon.pl/~sektor/cbx
"Java is answer for a question that has never been stated"
Technical Details
Message-ID:
<fiev8u$p7u$1@kujawiak.man.lodz.pl>Path:
mbox-mo1.pugleaf.net!news-archive.icm.edu.pl!news.rmf.pl!agh.edu.pl!news.agh.edu.pl!newsfeed.silweb.pl!news.man.lodz.pl!not-for-mailReferences:
<fghgeo$spf$1@nemesis.news.tpi.pl> <fgo4c7$mtu$1@news.vectranet.pl> <20071106073039.433f4934@Vorago> <fgqn9m$74j$2@news.vectranet.pl> <618565031.20071107113041@pkierski.net> <fgt6sh$jov$1@news.vectranet.pl> <fgtb6j$nur$1@news.vectranet.pl> <fgv6ba$k14$1@bozon2.softax.pl> <fgvkne$u3b$1@news.vectranet.pl> <fh1tjd$dtt$1@bozon2.softax.pl> <fh2lj6$qkh$1@news.vectranet.pl> <fha3as$pap$1@bozon2.softax.pl> <fhaj8u$e02$1@news.vectranet.pl> <fhf09d$ohq$1@bozon2.softax.pl> <fhfi9l$640$1@news.vectranet.pl> <fhurmu$5d9$1@bozon2.softax.pl> <fhv020$8k1$1@news.vectranet.pl> <fi1bs4$nn3$1@bozon2.softax.pl> <fi27ds$gt1$1@kujawiak.man.lodz.pl> <fi458p$fmv$1@bozon2.softax.pl> <fi9ckc$2u5$1@kujawiak.man.lodz.pl> <1195918508.27382.40.camel@qrnik> <fi9hmg$dct$1@news.vectranet.pl> <1195922067.12235.1.camel@qrnik> <fia74t$28b$1@news.vectranet.pl> <1195947820.12235.76.camel@qrnik> <fibfe9$91q$1@news.vectranet.pl> <1196000300.4731.76.camel@qrnik> <fico27$h3j$1@news.vectranet.pl>Show Full Headers
Complete Article Headers
Path: mbox-mo1.pugleaf.net!news-archive.icm.edu.pl!news.rmf.pl!agh.edu.pl!news.agh.edu.pl!newsfeed.silweb.pl!news.man.lodz.pl!not-for-mail
From: Sektor van Skijlen <ethouris@guess.if.gmail.com.is.valid.or.invalid>
Newsgroups: pl.comp.lang.c
Subject: Re: Ciemna strona C++
Date: Mon, 26 Nov 2007 17:20:30 +0000 (UTC)
Organization: LODMAN - Metropolitan Area Network in LODZ, Poland
Lines: 325
Sender: Michal Malecki <sektor@student.kis.p.lodz.pl>
Message-ID: <fiev8u$p7u$1@kujawiak.man.lodz.pl>
References: <fghgeo$spf$1@nemesis.news.tpi.pl> <fgo4c7$mtu$1@news.vectranet.pl> <20071106073039.433f4934@Vorago> <fgqn9m$74j$2@news.vectranet.pl> <618565031.20071107113041@pkierski.net> <fgt6sh$jov$1@news.vectranet.pl> <fgtb6j$nur$1@news.vectranet.pl> <fgv6ba$k14$1@bozon2.softax.pl> <fgvkne$u3b$1@news.vectranet.pl> <fh1tjd$dtt$1@bozon2.softax.pl> <fh2lj6$qkh$1@news.vectranet.pl> <fha3as$pap$1@bozon2.softax.pl> <fhaj8u$e02$1@news.vectranet.pl> <fhf09d$ohq$1@bozon2.softax.pl> <fhfi9l$640$1@news.vectranet.pl> <fhurmu$5d9$1@bozon2.softax.pl> <fhv020$8k1$1@news.vectranet.pl> <fi1bs4$nn3$1@bozon2.softax.pl> <fi27ds$gt1$1@kujawiak.man.lodz.pl> <fi458p$fmv$1@bozon2.softax.pl> <fi9ckc$2u5$1@kujawiak.man.lodz.pl> <1195918508.27382.40.camel@qrnik> <fi9hmg$dct$1@news.vectranet.pl> <1195922067.12235.1.camel@qrnik> <fia74t$28b$1@news.vectranet.pl> <1195947820.12235.76.camel@qrnik> <fibfe9$91q$1@news.vectranet.pl> <1196000300.4731.76.camel@qrnik> <fico27$h3j$1@news.vectranet.pl>
NNTP-Posting-Host: student.kis.p.lodz.pl
Mime-Version: 1.0
X-Trace: kujawiak.man.lodz.pl 1196097630 25854 212.191.89.21 (26 Nov 2007 17:20:30 GMT)
X-Complaints-To: abuse@man.lodz.pl
NNTP-Posting-Date: Mon, 26 Nov 2007 17:20:30 +0000 (UTC)
User-Agent: tin/1.8.3-20070201 ("Scotasay") (UNIX) (FreeBSD/6.2-RELEASE-p3 (sparc64))
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit