Jak język się rozszerza? [Zamknięte]

208

Uczę się C ++ i właśnie zacząłem uczyć się o niektórych możliwościach Qt do kodowania programów GUI. Zadałem sobie następujące pytanie:

W jaki sposób C ++, który wcześniej nie miał składni, która mogłaby prosić system operacyjny o okno lub sposób komunikowania się przez sieci (z interfejsami API, których do końca nie rozumiem, przyznaję) nagle zyskuje takie możliwości dzięki bibliotekom napisanym w C ++? To wszystko wydaje mi się okropnie okrągłe. Jakie instrukcje w C ++ można wymyślić w tych bibliotekach?

Zdaję sobie sprawę, że to pytanie może wydawać się trywialne doświadczonemu twórcy oprogramowania, ale badam od wielu godzin, nie znajdując żadnej bezpośredniej odpowiedzi. Doszło do tego, że nie mogę wykonać samouczka o Qt, ponieważ istnienie bibliotek jest dla mnie niezrozumiałe.

Med Larbi Sentissi
źródło
27
W jaki sposób std :: cout rysuje cokolwiek na monitorze? Czy może stoi na kompilatorze, który rozumie twój sprzęt?
doctorlove
14
Świetne pytanie. Ostatecznie trudno jest odpowiedzieć, dopóki nie przestudiujesz sprzętu.
user541686,
17
Qt nie jest rozszerzeniem języka (wymagałoby kompilatora obsługującego Qt). To tylko biblioteka dodana do twojego arsenału. Ostatecznie na najniższym poziomie wszystkie biblioteki komunikują się z systemem operacyjnym za pośrednictwem wywołań systemowych, które są niezależne od języka, ale bardzo zależą od systemu operacyjnego i architektury procesora.
DevSolar
8
afaik, C ++ ma wbudowany zestaw, który może zrobić prawie wszystko
Nazwa wyświetlana
9
@DevSolar: W rzeczywistości Qt rozszerza język o własny mechanizm szczeliny sygnałowej, odbicie i wiele innych dynamicznych funkcji. I te rzeczy wymagają kompilatora (kompilatora meta-obiektów) do skompilowania do kodu C ++.
Siyuan Ren,

Odpowiedzi:

194

Komputer jest jak cebula, ma wiele wielu warstw, od wewnętrznego rdzenia czystego sprzętu po zewnętrzną warstwę aplikacji. Każda warstwa wystawia części siebie na następną warstwę zewnętrzną, dzięki czemu warstwa zewnętrzna może wykorzystywać niektóre funkcje warstw wewnętrznych.

W przypadku np. Systemu Windows system operacyjny udostępnia tak zwany interfejs API WIN32 dla aplikacji działających w systemie Windows. Biblioteka Qt używa tego interfejsu API do dostarczania aplikacji korzystających z Qt do własnego interfejsu API. Używasz Qt, Qt używa WIN32, WIN32 używa niższych poziomów systemu operacyjnego Windows i tak dalej, dopóki nie będzie sygnałów elektrycznych w sprzęcie.

Jakiś programista, koleś
źródło
56
Uwaga: Qttutaj zapewnia abstrakcję warstwy poniżej, ponieważ w systemie Linux Qtwywołuje interfejs API systemu Linux, a nie interfejs API WIN32.
Matthieu M.,
5
Prawdopodobnie opracowałbym nieco więcej na przykładzie Qt, który po prostu wydaje się, jakby bez wysiłku rozszerzał możliwości c ++. W rzeczywistości wkładają wiele wysiłku, aby stworzyć wspólny interfejs API, (z dużym prawdopodobieństwem) wiele różnych „rdzeni cebuli”. Są to te, które zapewniają przenośność na nieprzenośnych niestandardowych backendach.
luk32
81
Komputer jest jak cebula: przecięcie go powoduje płacz, ale potem jest nieco smaczny.
alecov
3
@ChristopherPfohl Tak, musiałem go użyć, ponieważ nie mogłem zrozumieć, jak komputer będzie jak pudełko czekoladek. :)
Jakiś programista koleś
1
@Celeritas nauczyciel prawdopodobnie powiedział user32.dll, a może gdi32.dll.
user253751,
59

Masz rację, że ogólnie biblioteki nie mogą umożliwić niczego, co nie jest już możliwe.

Ale biblioteki nie muszą być napisane w C ++, aby mogły być używane przez program C ++. Nawet jeśli są napisane w C ++, mogą wewnętrznie używać innych bibliotek, które nie zostały napisane w C ++. Tak więc fakt, że C ++ nie podała żadnych sposób to zrobić nie zapobiec jej dodał, tak długo, jak istnieje jakiś sposób, aby to zrobić poza C ++.

Na dość niskim poziomie niektóre funkcje wywoływane przez C ++ (lub przez C) będą pisane w asemblerze, a asembler zawiera wymagane instrukcje do robienia wszystkiego, co nie jest możliwe (lub nie jest łatwe) w C ++, na przykład do wywołania funkcja systemu. W tym momencie to wywołanie systemowe może zrobić wszystko , co potrafi komputer, po prostu dlatego, że nic go nie powstrzymuje.


źródło
Czy masz na myśli, że biblioteki napisane w innych językach są kompilowane przy użyciu innych kompilatorów? A potem musiałby istnieć jakiś plik interfejsu łączący każde wywołanie funkcji dostarczone do C ++ przez bibliotekę ze wstępnie skompilowaną wersją biblioteki? Pozwala to kompilatorowi C ++ wiedzieć, na co tłumaczyć te wywołania?
Med Larbi Sentissi
2
@MedLarbiSentissi 1) Niekoniecznie inne kompilatory. Możliwe (i często się zdarza), że jeden kompilator jest w stanie skompilować wiele języków, w tym asembler, a nawet może kompilować C ++ z asemblerem. 2) W zależności od konkretnego systemu i kompilatora, wywołanie tych funkcji z C ++ może rzeczywiście być wykonane przy pomocy pewnego rodzaju pliku interfejsu, ale ten rodzaj pliku interfejsu może być już nagłówkiem C (lub nawet C ++) bezpośrednio używanym z C ++.
1
@MedLarbiSentissi: Wiele bibliotek systemu Windows jest skompilowanych do plików DLL, które zawierają własny interfejs, a także kod. Możesz zajrzeć do biblioteki dll i zobaczyć listę funkcji, z których można korzystać. Często zawierają również plik nagłówka C. Po utworzeniu pliku exe zawiera listę bibliotek DLL, które należy uruchomić. Gdy system operacyjny próbuje załadować plik exe, automatycznie załaduje również te biblioteki DLL przed rozpoczęciem wykonywania.
Mooing Duck
8
Ta odpowiedź wydaje się sugerować, że „magia” polega całkowicie na wywołaniu innych języków, ale tak naprawdę większość kodu tworzącego najnowocześniejsze systemy operacyjne to C (z częściami ściśle związanymi ze sprzętem lub kluczowymi dla wydajności napisanymi w asemblerze) - i zdecydowanie można zamiast tego użyć C ++. Chodzi o to, że nie ma „magii”, języki są tworzone, aby budować tak potężne abstrakcje, a kiedy będziesz mógł wchodzić w interakcje ze sprzętem, możliwości są prawie nieograniczone.
Matteo Italia
1
@hvd Myślę, że cały konflikt w tej dyskusji polega na tym, że ty (i inni) definiujesz C jako funkcje, które są dla niego określone. W rzeczywistości kompilatory dodają znacznie więcej niż podano, co sprawia, że ​​pytanie, na co C, jest dość trywialne. Dla mnie specjalną cechą języka (a więc tym, czym jest) jest meta-sposób wyrażania przepływu programu i możliwości jego struktury. Elementy, które są ustrukturyzowane, nie są dla niego ważne, ponieważ jest to po prostu lepszy kod ASM, który można dodawać kompilatorami, jak chcą
LionC
43

C i C ++ mają 2 właściwości, które pozwalają na całą tę rozszerzalność, o której mówi OP.

  1. C i C ++ mają dostęp do pamięci
  2. C i C ++ mogą wywoływać kod asemblera dla instrukcji spoza języka C lub C ++.

W jądrze lub na podstawowej platformie trybu niechronionego urządzenia peryferyjne, takie jak port szeregowy lub dysk, są odwzorowywane na mapę pamięci w taki sam sposób, jak pamięć RAM. Pamięć to seria przełączników, a przestawienie przełączników urządzeń peryferyjnych (takich jak port szeregowy lub sterownik dysku) zmusza urządzenie peryferyjne do robienia użytecznych rzeczy.

W systemie operacyjnym trybu chronionego, gdy chce się uzyskać dostęp do jądra z przestrzeni użytkownika (np. Podczas pisania do systemu plików lub narysowania piksela na ekranie), należy wykonać wywołanie systemowe. C nie ma instrukcji do wywoływania wywołań systemowych, ale C może wywoływać kod asemblera, który może wywołać prawidłowe wywołanie systemowe. To pozwala kodowi C komunikować się z jądrem.

Aby ułatwić programowanie konkretnej platformy, wywołania systemowe są pakowane w bardziej złożone funkcje, które mogą wykonywać przydatne funkcje w ramach własnego programu. Można swobodnie wywoływać wywołania systemowe bezpośrednio (za pomocą asemblera), ale prawdopodobnie łatwiej jest po prostu skorzystać z jednej z funkcji otoki, które zapewnia platforma.

Istnieje inny poziom API, który jest o wiele bardziej przydatny niż wywołanie systemowe. Weźmy na przykład Malloc. Nie tylko wywoła to system w celu uzyskania dużych bloków pamięci, ale będzie zarządzać tą pamięcią, wykonując całą księgowość na temat tego, co się dzieje.

Interfejsy API Win32 obejmują niektóre funkcje graficzne wspólnym zestawem widżetów platformy. Qt idzie o krok dalej, pakując API Win32 (lub X Windows) na wiele platform.

Zasadniczo chociaż kompilator C zamienia kod C w kod maszynowy, a ponieważ komputer jest zaprojektowany do korzystania z kodu maszynowego, należy oczekiwać, że C będzie w stanie osiągnąć udział lwów lub to, co może zrobić komputer. Wszystko, co robią biblioteki opakowań, to ciężkie podnoszenie dla ciebie, abyś nie musiał.

doron
źródło
Zastrzeżenie dotyczące # 2: C i C ++ może jedynie wywoływać funkcje zgodne z „konwencją wywoływania”, którą kompilator rozumie i oczekuje. (Kod asemblera może wykorzystywać dowolną konwencję, a nawet żadną - więc kod może nie być bezpośrednio wywoływalny). Na szczęście każdy szanujący się kompilator zapewnia wbudowany sposób korzystania ze wspólnych konwencji platformy. (Kompilatory Windows C, na przykład, pozwalają na używanie / używanie funkcji, które używają konwencji „cdecl”, „stdcall” lub „fastcall”). Ale kod asemblera musi używać konwencji znanej kompilatorowi lub C i C ++ mogą ” zadzwoń bezpośrednio.
cHao
2
Ponadto: operacje we / wy mapowane w pamięci są powszechne, ale nie cała historia. Na przykład komputery PC zwykle adresują porty szeregowe, dyski itp. Za pomocą „portów I / O” x86, co jest zupełnie innym mechanizmem. (Bufor wideo jest zwykle mapowany w pamięci, ale tryby wideo itp. Są zwykle kontrolowane przez porty I / O.)
cHao
@cHao: Oczywiście klasyczne podejście wykorzystujące INP i OUTP jest wycofywane na korzyść DMA; wydaje się, że generowanie PCI robi więcej dzięki mapowanym specjalnym rejestrom funkcji, teraz jest sposób na automatyczne mapowanie urządzeń do nie nakładających się regionów i wykrywanie ich ze sterowników, a mniej z portami I / O.
Ben Voigt
nowoczesne urządzenia peryferyjne będą używać DMA do masowego przesyłania danych, ale nadal będziesz programować kontroler DMA z pamięcią adresowalną
doron
@doron: Umm, programujesz kontroler DMA za pomocą przestrzeni adresowej I / O (nie pamięci) na komputerze, przynajmniej jeśli jesteś zdrowy. Nowoczesne procesory x86 lubią zmieniać kolejność dostępów do pamięci w celu poprawy wydajności. Z MMIO może to być katastrofalne ... musisz więc uważać, aby adresy te były nieszyfrowane i umieścić instrukcje serializacji we właściwych miejscach. OTOH, sam x86 zapewnia, że ​​odczytywanie i zapisywanie w przestrzeni we / wy odbywa się w kolejności programów. Dlatego wiele ważnych rzeczy wciąż odbywa się za pośrednictwem przestrzeni we / wy (która zwykle nie jest dostępna za pomocą wskaźnika) i prawdopodobnie zawsze będzie.
cHao
23

Języki (jak C ++ 11 ) to specyfikacje na papierze, zwykle napisane w języku angielskim. Zajrzyj do najnowszej wersji roboczej C ++ 11 (lub kup kosztowną ostateczną specyfikację od dostawcy ISO).

Zwykle używasz komputera z pewną implementacją języka (w zasadzie możesz uruchomić program C ++ bez żadnego komputera, np. Używając tłumu ludzkich niewolników interpretujących go; byłoby to nieetyczne i nieefektywne)

Ogólna implementacja C ++ działa powyżej pewnego systemu operacyjnego i komunikuje się z nim (przy użyciu kodu specyficznego dla implementacji , często w bibliotece systemowej). Zasadniczo komunikacja odbywa się za pośrednictwem wywołań systemowych . Wyszukaj na przykład w syscalls (2) listę wywołań systemowych dostępnych w jądrze systemu Linux .

Z punktu widzenia aplikacji syscall jest podstawową instrukcją maszynową, jak SYSENTERna x86-64 z pewnymi konwencjami ( ABI )

Na moim pulpicie z systemem Linux biblioteki Qt znajdują się powyżej bibliotek klienta X11 komunikujących się z serwerem X11 Xorg przez X protokołów Windows .

W systemie Linux użyj lddw swoim pliku wykonywalnym, aby zobaczyć (długą) listę zależności od bibliotek. Użyj pmapw uruchomionym procesie, aby zobaczyć, które z nich są „ładowane” w czasie wykonywania. BTW, w Linuksie Twoja aplikacja prawdopodobnie używa tylko wolnego oprogramowania, możesz przestudiować jego kod źródłowy (od Qt, do Xlib, libc, ... jądra), aby lepiej zrozumieć, co się dzieje

Basile Starynkevitch
źródło
2
Dla porównania, ANSI sprzedaje specyfikację C ++ 11 za nieco mniej oburzającą cenę 60 USD. (Kiedyś była o połowę mniejsza, ale inflacja.: P) Jest oznaczony jako INCITS / ISO / IEC 14882, ale jest to co najmniej ta sama oferta bazowa ISO. Nie jestem pewien co do errata / TRs.
cHao
19

Myślę, że koncepcja, której brakuje, to wywołania systemowe . Każdy system operacyjny zapewnia ogromną ilość zasobów i funkcji, z których można korzystać, aby wykonywać czynności związane z niskim poziomem systemu operacyjnego. Nawet jeśli wywołasz zwykłą funkcję biblioteczną, prawdopodobnie wykonuje ona wywołanie systemowe za sceną.

Wywołania systemowe są niskopoziomowym sposobem korzystania z mocy systemu operacyjnego, ale mogą być skomplikowane i uciążliwe w użyciu, dlatego często są „zawinięte” w interfejsy API, dzięki czemu nie trzeba bezpośrednio się z nimi obchodzić. Ale poniżej wszystko, co robisz, co wiąże się z zasobami związanymi z O / S, będzie korzystać z wywołań systemowych, w tym drukowania, sieci i gniazd itp.

W przypadku Windows, Microsoft Windows ma GUI faktycznie zapisane w jądrze, więc są wywołania systemowe do tworzenia okien, malowania grafiki itp. W innych systemach operacyjnych GUI może nie być częścią jądra, w którym to przypadku o ile wiem, nie byłoby żadnych wywołań systemowych dla rzeczy związanych z GUI, a ty mógłbyś pracować tylko na jeszcze niższym poziomie z dostępnymi wywołaniami graficznymi i wejściowymi niskiego poziomu.

Dave Cousineau
źródło
3
Ważną rzeczą, której brakuje, jest to, że te wywołania systemowe nie są magiczne. Są obsługiwane przez jądro, które zazwyczaj jest napisane w C (++). Ponadto wywołania systemowe nie są nawet konieczne. W podstawowym systemie operacyjnym bez ochrony pamięci okna można rysować, umieszczając piksele bezpośrednio w sprzętowym buforze ramek.
el.pescado
15

Dobre pytanie. Każdy nowy programista C lub C ++ ma to na uwadze. Zakładam standardową maszynę x86 do końca tego postu. Jeśli używasz kompilatora Microsoft C ++, otwórz notatnik i wpisz go (nazwij plik Test.c)

int main(int argc, char **argv)
{
   return 0
}

A teraz skompiluj ten plik (używając wiersza polecenia programisty) cl Test.c /FaTest.asm

Teraz otwórz Test.asm w swoim notatniku. To, co widzisz, to przetłumaczony kod - C / C ++ jest tłumaczony na asembler. Dostajesz podpowiedź?

_main   PROC
    push    ebp
    mov ebp, esp
    xor eax, eax
    pop ebp
    ret 0
_main   ENDP

Programy C / C ++ są zaprojektowane do działania na metalu. Co oznacza, że ​​mają dostęp do sprzętu niższego poziomu, co ułatwia wykorzystanie jego możliwości. Powiedzmy, że zamierzam napisać getch () biblioteki C na maszynie x86.

W zależności od asemblera napisałbym coś w ten sposób:

_getch proc 
   xor AH, AH
   int 16h
   ;AL contains the keycode (AX is already there - so just return)
ret

Uruchomię go za pomocą asemblera i wygeneruję plik .OBJ - Nazwij go getch.obj.

Następnie piszę program w języku C (nie włączam niczego)

extern char getch();

void main(int, char **)
{
  getch();
}

Teraz nazwij ten plik - GetChTest.c. Skompiluj ten plik, przekazując getch.obj. (Lub skompiluj indywidualnie do .obj i LINK GetChTest.Obj i getch.Obj razem, aby utworzyć GetChTest.exe).

Uruchom GetChTest.exe, a zobaczysz, że czeka on na klawiaturę.

Programowanie w C / C ++ to nie tylko język. Aby być dobrym programistą C / C ++, musisz dobrze rozumieć rodzaj obsługiwanego komputera. Będziesz musiał wiedzieć, jak obsługiwane jest zarządzanie pamięcią, jak są zbudowane rejestry itp. Może nie potrzebujesz wszystkich tych informacji do regularnego programowania - ale one bardzo by ci pomogły. Oprócz podstawowej wiedzy o sprzęcie, z pewnością pomaga, jeśli rozumiesz, jak działa kompilator (tj. Jak to tłumaczy) - co może umożliwić ci poprawienie kodu w razie potrzeby. To ciekawy pakiet!

Oba języki obsługują słowo kluczowe __asm, co oznacza, że ​​możesz również mieszać kod języka asemblera. Nauka języków C i C ++ sprawi, że będziesz ogólnie lepszym programistą.

Nie zawsze jest konieczne łączenie się z asemblerem. Wspomniałem o tym, ponieważ myślałem, że to pomoże ci lepiej zrozumieć. Przeważnie większość takich wywołań biblioteki korzysta z wywołań systemowych / interfejsów API dostarczanych przez system operacyjny (system operacyjny z kolei robi interakcje sprzętowe).

Krishna Santosh Sampath
źródło
10

W jaki sposób C ++ ... nagle uzyskuje takie możliwości dzięki bibliotekom napisanym w C ++?

Nie ma nic magicznego w korzystaniu z innych bibliotek. Biblioteki to proste duże pakiety funkcji, które można wywoływać.

Zastanów się, czy nie napisać takiej funkcji

void addExclamation(std::string &str)
{
    str.push_back('!');
}

Teraz, jeśli dodasz ten plik, możesz napisać addExclamation(myVeryOwnString);. Teraz możesz zapytać: „w jaki sposób C ++ nagle uzyskał możliwość dodawania wykrzykników do ciągu?” Odpowiedź jest prosta: napisałeś funkcję, aby to zrobić, a następnie ją nazwałeś.

Aby odpowiedzieć na pytanie o to, jak C ++ może uzyskać możliwości rysowania okien za pomocą bibliotek napisanych w C ++, odpowiedź jest taka sama. Ktoś inny napisał (e) funkcje, aby to zrobić, a następnie skompilował je i dał ci w formie biblioteki.

Inne pytania odpowiadają, jak działa rysunek okna, ale wydawało się, że jesteś zdezorientowany, jak działają biblioteki, więc chciałem odpowiedzieć na najbardziej podstawową część twojego pytania.

Philip
źródło
8

Kluczem jest możliwość ujawnienia przez system operacyjny interfejsu API oraz szczegółowy opis sposobu użycia tego interfejsu API.

System operacyjny oferuje zestaw interfejsów API z konwencjami wywoływania. Konwencja wywoływania określa sposób podawania parametru do interfejsu API, sposób zwracania wyników i sposób wykonania rzeczywistego wywołania.

Systemy operacyjne i kompilatory, które tworzą dla nich kod, grają dobrze razem, więc zazwyczaj nie musisz o tym myśleć, po prostu go używaj.

thst
źródło
7

Nie ma potrzeby specjalnej składni do tworzenia okien. Wystarczy, że system operacyjny zapewni interfejs API do tworzenia okien. Taki interfejs API składa się z prostych wywołań funkcji, dla których C ++ zapewnia składnię.

Ponadto C i C ++ są tak zwanymi językami programowania systemowego i mogą uzyskiwać dostęp do dowolnych wskaźników (które mogą być mapowane na niektóre urządzenia przez sprzęt). Ponadto dość łatwo jest wywoływać funkcje zdefiniowane w asemblerze, co pozwala na pełny zakres operacji zapewnianych przez procesor. Dlatego możliwe jest napisanie samego systemu operacyjnego przy użyciu C lub C ++ i niewielkiej ilości asemblera.

Należy również wspomnieć, że Qt jest złym przykładem, ponieważ wykorzystuje tak zwany meta kompilator do rozszerzenia składni C ++. Nie jest to jednak związane z jego zdolnością do wywoływania interfejsów API udostępnianych przez system operacyjny w celu rysowania lub tworzenia okien.

Joe
źródło
7

Po pierwsze, myślę, że jest to trochę nieporozumienie

Jak działa C ++, który wcześniej nie posiadał składni, która mogłaby prosić system operacyjny o okno lub sposób komunikowania się przez sieci

Nie ma składni do wykonywania operacji systemu operacyjnego. To kwestia semantyki .

nagle zdobywają takie możliwości dzięki bibliotekom napisanym w C ++

Cóż, system operacyjny jest napisany głównie w C. Możesz użyć bibliotek współdzielonych (więc dll) do wywołania kodu zewnętrznego. Ponadto kod systemu operacyjnego może rejestrować procedury systemowe w wywołaniach systemowych * lub przerwać, które można wywołać za pomocą asemblera . Te współdzielone biblioteki często po prostu wywołują dla ciebie wywołania systemowe, więc oszczędzasz sobie korzystania z wbudowanego zestawu.

Oto fajny samouczek na ten temat: http://www.win.tue.nl/~aeb/linux/lk/lk-4.html
To jest dla Linuksa, ale zasady są takie same.

Jak system operacyjny wykonuje operacje na kartach graficznych, kartach sieciowych itp.? Jest to bardzo szeroki temat, ale przede wszystkim potrzebujesz dostępu do przerwań, portów lub zapisu danych w specjalnym regionie pamięci. Ponieważ te operacje są chronione, i tak musisz wywoływać je za pośrednictwem systemu operacyjnego.

Żeglarz naddunajski
źródło
7

Próbując przedstawić nieco inne spojrzenie na inne odpowiedzi, odpowiem w ten sposób.

(Oświadczenie: Upraszczam nieco, sytuacja, którą podaję, jest czysto hipotetyczna i została napisana jako środek demonstracji koncepcji, a nie bycia w 100% wiernym życiu).

Pomyśl o tym z innej perspektywy, wyobraź sobie, że właśnie napisałeś prosty system operacyjny z podstawowymi funkcjami wątkowania, okienkowania i zarządzania pamięcią. Chcesz zaimplementować bibliotekę C ++, aby umożliwić użytkownikom programowanie w C ++ i wykonywanie takich czynności, jak tworzenie okien, rysowanie na oknach itp. Pytanie brzmi, jak to zrobić.

Po pierwsze, ponieważ C ++ kompiluje się do kodu maszynowego, musisz zdefiniować sposób użycia kodu maszynowego do interfejsu z C ++. To tutaj przychodzą funkcje, funkcje akceptują argumenty i zwracają wartości, dzięki czemu zapewniają standardowy sposób przesyłania danych między różnymi sekcjami kodu. Robią to, ustanawiając coś, co nazywa się konwencją powołania .

A konwencja wywoływania stanów, gdzie i jak argumenty powinny być umieszczane w pamięci tak, że funkcja może ich znaleźć, gdy zostanie wykonany. Kiedy funkcja zostaje wywołana, funkcja wywołująca umieszcza argumenty w pamięci, a następnie prosi CPU, aby przeskoczył do innej funkcji, gdzie robi to, co robi, zanim wróci do miejsca, z którego został wywołany. Oznacza to, że wywoływany kod może być absolutnie dowolny i nie zmienia sposobu wywoływania funkcji. W takim przypadku kod stojący za funkcją byłby odpowiedni dla systemu operacyjnego i działałby w stanie wewnętrznym systemu operacyjnego.

Wiele miesięcy później masz już uporządkowane wszystkie funkcje systemu operacyjnego. Użytkownik może wywoływać funkcje w celu tworzenia okien i rysowania na nich, może tworzyć wątki i wszelkiego rodzaju wspaniałe rzeczy. Oto problem: funkcje twojego systemu będą się różnić od funkcji Linuksa lub Windowsa. Więc decydujesz, że musisz dać użytkownikowi standardowy interfejs, aby mógł pisać przenośny kod. Tutaj pojawia się QT.

Jak prawie na pewno wiesz, QT ma mnóstwo przydatnych klas i funkcji do robienia tego, co robią systemy operacyjne, ale w sposób, który wydaje się niezależny od bazowego systemu operacyjnego. Działa to w ten sposób, że QT zapewnia klasy i funkcje, które są jednolite pod względem wyglądu dla użytkownika, ale kod stojący za funkcjami jest inny dla każdego systemu operacyjnego. Na przykład QApplication :: closeAllWindows () QT faktycznie wywoływałby specjalną funkcję zamykania okien każdego systemu operacyjnego w zależności od używanej wersji. W Windows najprawdopodobniej wywołałby CloseWindow (hwnd), podczas gdy w systemie operacyjnym korzystającym z X Window System potencjalnie wywołałby XDestroyWindow (display, window).

Jak widać, system operacyjny ma wiele warstw, z których wszystkie muszą oddziaływać poprzez interfejsy wielu odmian. Jest wiele aspektów, których nawet nie dotknąłem, ale ich wyjaśnienie zajęłoby bardzo dużo czasu. Jeśli jesteś zainteresowany wewnętrznymi działaniami systemów operacyjnych, polecam sprawdzenie wiki dla programistów systemu operacyjnego .

Należy jednak pamiętać, że powodem, dla którego wiele systemów operacyjnych decyduje się na udostępnienie interfejsów C / C ++, jest to, że kompilują się one do kodu maszynowego, pozwalają na mieszanie instrukcji asemblera z własnym kodem i zapewniają programistom dużą swobodę.

Znowu wiele się tu dzieje. Chciałbym wyjaśnić, w jaki sposób biblioteki takie jak .so i .dll nie muszą być pisane w C / C ++ i mogą być napisane w asemblerze lub w innych językach, ale wydaje mi się, że jeśli coś jeszcze dodam, równie dobrze napisz cały artykuł i tak bardzo, jak chciałbym to zrobić, nie mam witryny, w której można by go hostować.

Pharap
źródło
6

Kiedy próbujesz narysować coś na ekranie, twój kod wywołuje inny fragment kodu, który wywołuje inny kod (itp.), Aż w końcu pojawia się „wywołanie systemowe”, które jest specjalną instrukcją, którą CPU może wykonać. Instrukcje te mogą być napisane w asemblerze lub mogą być napisane w C ++, jeśli kompilator obsługuje ich „elementy wewnętrzne” (które są funkcjami, które kompilator obsługuje „specjalnie” poprzez konwersję ich na specjalny kod, który procesor może zrozumieć). Ich zadaniem jest powiedzieć systemowi operacyjnemu, aby coś zrobił.

Kiedy nastąpi wywołanie systemowe, wywoływana jest funkcja, która wywołuje inną funkcję (itp.), Aż w końcu sterownik ekranu wyświetli polecenie narysowania czegoś na ekranie. W tym momencie sterownik ekranu patrzy na konkretny region w pamięci fizycznej, który w rzeczywistości nie jest pamięcią, ale raczej zakresem adresów, do którego można zapisać tak, jakby była pamięcią. Zamiast tego zapisywanie w tym zakresie adresów powoduje, że sprzęt graficzny przechwytuje zapis w pamięci i rysuje coś na ekranie.
Zapis w tym obszarze pamięci jest czymś, co można by kodować w C ++, ponieważ po stronie oprogramowania jest to zwykły dostęp do pamięci. Po prostu sprzęt obsługuje to inaczej.
To naprawdę podstawowe wyjaśnienie, w jaki sposób może działać.

użytkownik541686
źródło
4
Afaik wywołanie systemowe nie jest tak naprawdę instrukcją procesora i nie ma nic wspólnego z wewnętrzną funkcją. Jest to raczej funkcja jądra systemu operacyjnego, które komunikuje się z urządzeniami.
MatthiasB
3
@MatthiasB: Cóż, mylisz się, ponieważ syscall(i jego kuzyn sysenter) jest rzeczywiście instrukcją procesora.
user541686,
2
To była tylko wskazówka, aby poprawić twoją odpowiedź, ponieważ nie było to dla mnie jasne. Nie uważaj tego za osobisty atak ani nic takiego.
MatthiasB
1
@MatthiasB: Nie biorę tego osobiście. Mówię, że już wiem, że odpowiedź nie jest w rzeczywistości w 100% dokładna, ale myślę, że jest to wystarczająco dobre uproszczenie, aby odpowiedzieć na PO - więc jeśli naprawdę znasz sposób na napisanie lepszej odpowiedzi, proszę albo napisz własną odpowiedz lub poświęć czas na edycję mojego. Naprawdę nie mam nic do dodania, co moim zdaniem jest tego warte, więc jeśli chcesz zobaczyć coś lepszego na tej stronie, musisz sam podjąć wysiłek.
user541686,
3
Wywołania systemowe są wykonywane przy użyciu przerwań programowych. Instrukcje takie sysentersą zoptymalizowanymi ścieżkami wywołań, ponieważ przełączanie kontekstu używane przez procedury obsługi przerwań nie było tak szybkie, jak wszyscy tego pragnęli, ale zasadniczo jest to nadal przerwanie generowane programowo, podczas gdy jest obsługiwane przez wektoryzację do procedury obsługi zainstalowanej przez jądro systemu operacyjnego. Część procesu przełączania kontekstu wykonywanego przez JEST sysenterpolega na zmianie bitów trybu w procesorze, aby ustawić pierścień 0 - pełny dostęp do wszystkich uprzywilejowanych instrukcji, rejestrów oraz obszarów pamięci i I / O.
Ben Voigt
4

Twój program C ++ korzysta z biblioteki Qt (również zakodowanej w C ++). Biblioteka Qt będzie korzystać z funkcji Windows CreateWindowEx (która została zakodowana w C wewnątrz kernel32.dll). Lub pod Linuksem może używać Xlib (również zakodowany w C), ale równie dobrze może wysyłać surowe bajty, które w protokole X oznaczają „ Proszę o utworzenie okna dla mnie ”.

Powiązane z pytaniem catch-22 jest historyczna notatka, że ​​„pierwszy kompilator C ++ został napisany w C ++”, chociaż tak naprawdę był to kompilator C z kilkoma pojęciami w C ++, wystarczająco, aby mógł skompilować pierwszą wersję, która mogłaby się następnie skompilować .

Podobnie kompilator GCC korzysta z rozszerzeń GCC: najpierw jest kompilowany do wersji, a następnie sam się rekompiluje. (Instrukcje kompilacji GCC)

Anioł
źródło
2

Jak widzę pytanie, tak naprawdę jest to pytanie kompilatora.

Spójrz na to w ten sposób, piszesz fragment kodu w asemblerze (możesz to zrobić w dowolnym języku), który tłumaczy twój nowo napisany język, który chcesz nazwać Z ++ w asemblerze, dla uproszczenia pozwala nazwać go kompilatorem (jest to kompilator) .

Teraz dajesz temu kompilatorowi kilka podstawowych funkcji, dzięki czemu możesz pisać int, string, tablice itp., W rzeczywistości dajesz mu wystarczające możliwości, abyś mógł pisać sam kompilator w Z ++. a teraz masz kompilator dla Z ++ napisany w Z ++, całkiem nieźle, prawda.

Jeszcze fajniejsze jest to, że teraz możesz dodawać umiejętności do tego kompilatora za pomocą już posiadanych umiejętności, rozszerzając w ten sposób język Z ++ o nowe funkcje, korzystając z poprzednich funkcji

Przykład: jeśli napiszesz wystarczającą ilość kodu, aby narysować piksel w dowolnym kolorze, możesz go rozwinąć za pomocą Z ++, aby narysować cokolwiek chcesz.

Thomas Andreè Wang
źródło
0

Sprzęt jest na to pozwala. Pamięć graficzną można traktować jako dużą tablicę (składającą się z każdego piksela na ekranie). Aby narysować na ekranie, możesz pisać w tej pamięci za pomocą C ++ lub dowolnego języka, który umożliwia bezpośredni dostęp do tej pamięci. Ta pamięć jest po prostu dostępna lub znajduje się na karcie graficznej.

W nowoczesnych systemach bezpośredni dostęp do pamięci graficznej wymagałby napisania sterownika z powodu różnych ograniczeń, dlatego używasz środków pośrednich. Biblioteki, które tworzą okno (tak naprawdę obraz jak każdy inny obraz), a następnie zapisują ten obraz do pamięci graficznej, którą GPU wyświetla na ekranie. Do języka nie trzeba dodawać nic oprócz możliwości pisania w określonych lokalizacjach pamięci, do czego służą wskaźniki.

Jan
źródło
Chodzi mi o to, że język nie musi się „rozszerzać” w tym sensie, że nowa wersja języka musi zostać przepisana, i że nie jest tak naprawdę okrągły, ponieważ robienie czegokolwiek interesującego programu musi współpracować ze sprzętem.
John