Po tylu latach świat nadal napędzany jest przez programowanie w języku C

Opublikowany: 2022-03-11

Wiele z istniejących obecnie projektów C zostało rozpoczętych kilkadziesiąt lat temu.

Rozwój systemu operacyjnego UNIX rozpoczął się w 1969, a jego kod został przepisany w C w 1972. Język C został faktycznie stworzony, aby przenieść kod jądra UNIX z asemblera do języka wyższego poziomu, który wykonywałby te same zadania z mniejszą liczbą wierszy kodu .

Rozwój bazy danych Oracle rozpoczął się w 1977 roku, a jej kod został przepisany z asemblera do C w 1983 roku. Stała się jedną z najpopularniejszych baz danych na świecie.

W 1985 roku został wydany Windows 1.0. Chociaż kod źródłowy systemu Windows nie jest publicznie dostępny, stwierdzono, że jego jądro jest w większości napisane w C, a niektóre części są w asemblerze. Rozwój jądra Linuksa rozpoczął się w 1991 roku i jest również napisany w języku C. W następnym roku został wydany na licencji GNU i był używany jako część systemu operacyjnego GNU. Sam system operacyjny GNU został uruchomiony przy użyciu języków programowania C i Lisp, więc wiele jego komponentów jest napisanych w C.

Ale programowanie w C nie ogranicza się do projektów, które rozpoczęły się dziesiątki lat temu, kiedy nie było tak wielu języków programowania jak dzisiaj. Wiele projektów w języku C jest nadal rozpoczętych; są ku temu dobre powody.

Jak świat jest zasilany przez C?

Pomimo rozpowszechnienia języków wyższego poziomu, C nadal wzmacnia świat. Oto niektóre z systemów używanych przez miliony i zaprogramowanych w języku C.

Microsoft Windows

Jądro Microsoftu Windows jest rozwijane głównie w C, z niektórymi częściami w języku asemblera. Od dziesięcioleci najczęściej używany system operacyjny na świecie, mający około 90 procent udziału w rynku, jest napędzany jądrem napisanym w języku C.

Linux

Linux jest również napisany głównie w C, z kilkoma częściami w asemblerze. Około 97 procent z 500 najpotężniejszych superkomputerów na świecie korzysta z jądra Linux. Jest również używany w wielu komputerach osobistych.

Prochowiec

Komputery Mac są również zasilane przez C, ponieważ jądro OS X jest napisane głównie w C. Każdy program i sterownik na Macu, podobnie jak na komputerach z systemem Windows i Linux, działa na jądrze opartym na C.

mobilny

Jądra iOS, Android i Windows Phone są również napisane w C. Są to po prostu mobilne adaptacje istniejących jąder Mac OS, Linux i Windows. Tak więc smartfony, których używasz na co dzień, działają na jądrze C.

Jądra systemu operacyjnego napisane w języku C

Bazy danych

Najpopularniejsze na świecie bazy danych, w tym Oracle Database, MySQL, MS SQL Server i PostgreSQL, są kodowane w C (pierwsze trzy z nich w rzeczywistości zarówno w C, jak i C++).

Bazy danych są używane we wszelkiego rodzaju systemach: finansowym, rządowym, medialnym, rozrywkowym, telekomunikacyjnym, zdrowotnym, edukacyjnym, detalicznym, społecznościowym, internetowym i tym podobnych.

Bazy danych obsługiwane przez C

Filmy 3D

Filmy 3D są tworzone za pomocą aplikacji, które są na ogół napisane w C i C++. Aplikacje te muszą być bardzo wydajne i szybkie, ponieważ obsługują ogromną ilość danych i wykonują wiele obliczeń na sekundę. Im są one bardziej wydajne, tym mniej czasu zajmuje artystom i animatorom wygenerowanie ujęć filmowych i tym więcej pieniędzy oszczędza firma.

Systemy wbudowane

Wyobraź sobie, że budzisz się pewnego dnia i idziesz na zakupy. Budzik, który Cię budzi, jest prawdopodobnie zaprogramowany na C. Następnie używasz kuchenki mikrofalowej lub ekspresu do kawy, aby przygotować śniadanie. Są to również systemy wbudowane i dlatego prawdopodobnie są zaprogramowane w języku C. Podczas jedzenia śniadania włączasz telewizor lub radio. Są to również systemy wbudowane, zasilane przez C. Kiedy otwierasz bramę garażową pilotem, korzystasz również z systemu wbudowanego, który najprawdopodobniej jest zaprogramowany w C.

Potem wsiadasz do samochodu. Jeśli ma następujące funkcje, zaprogramowane również w C:

  • automatyczna skrzynia
  • systemy wykrywania ciśnienia w oponach
  • czujniki (tlen, temperatura, poziom oleju itp.)
  • pamięć foteli i ustawień lusterek.
  • wyświetlacz deski rozdzielczej
  • hamulce przeciwblokujące
  • automatyczna kontrola stabilności
  • tempomat
  • kontrola klimatu
  • zamki zabezpieczające przed dziećmi
  • wejście bez klucza
  • podgrzewane siedzenia
  • sterowanie poduszką powietrzną

Docierasz do sklepu, parkujesz samochód i idziesz do automatu po napoje gazowane. Jakiego języka używali do programowania tego automatu? Prawdopodobnie C. Wtedy kupujesz coś w sklepie. Kasa również jest zaprogramowana w C. A kiedy płacisz kartą kredytową? Zgadłeś: czytnik kart kredytowych jest prawdopodobnie zaprogramowany w C.

Wszystkie te urządzenia to systemy wbudowane. Są jak małe komputery, które mają wewnątrz mikrokontroler/mikroprocesor, który uruchamia program, zwany także oprogramowaniem układowym, na urządzeniach wbudowanych. Program ten musi wykrywać naciśnięcia klawiszy i odpowiednio działać, a także wyświetlać informacje użytkownikowi. Na przykład budzik musi wchodzić w interakcje z użytkownikiem, wykrywając, jaki przycisk użytkownik naciska, a czasami, jak długo jest wciśnięty, i odpowiednio programować urządzenie, a wszystko to wyświetlając użytkownikowi odpowiednie informacje. Na przykład system przeciwblokujący w samochodzie musi być w stanie wykryć nagłe zablokowanie opon i działać w celu zwolnienia nacisku na hamulce na krótki czas, odblokowując je, a tym samym zapobiegając niekontrolowanemu poślizgowi. Wszystkie te obliczenia są wykonywane przez zaprogramowany system wbudowany.

Chociaż język programowania używany w systemach wbudowanych może się różnić w zależności od marki, najczęściej programuje się je w języku C, ze względu na cechy języka, takie jak elastyczność, wydajność, wydajność i bliskość sprzętu.

Systemy wbudowane są często pisane w języku C

Dlaczego nadal używany jest język programowania C?

Obecnie istnieje wiele języków programowania, które pozwalają programistom być bardziej produktywnym niż w przypadku C przy różnego rodzaju projektach. Istnieją języki wyższego poziomu, które zapewniają znacznie większe wbudowane biblioteki, które upraszczają pracę z JSON, XML, interfejsem użytkownika, stronami internetowymi, żądaniami klientów, połączeniami z bazami danych, manipulacją multimediami i tak dalej.

Mimo to istnieje wiele powodów, by sądzić, że programowanie w C pozostanie aktywne przez długi czas.

W językach programowania jeden rozmiar nie pasuje do wszystkich. Oto kilka powodów, dla których C jest bezkonkurencyjny i prawie obowiązkowy dla niektórych aplikacji.

Przenośność i wydajność

C jest prawie przenośnym językiem asemblera . Jest tak blisko maszyny, jak to tylko możliwe, podczas gdy jest prawie powszechnie dostępny dla istniejących architektur procesorów. Istnieje co najmniej jeden kompilator C dla prawie każdej istniejącej architektury. A w dzisiejszych czasach, ze względu na wysoce zoptymalizowane pliki binarne generowane przez nowoczesne kompilatory, nie jest łatwym zadaniem poprawienie ich wyników za pomocą ręcznie napisanego asemblera.

Taka jest jego przenośność i wydajność, że „kompilatory, biblioteki i interpretery innych języków programowania są często implementowane w C”. Interpretowane języki, takie jak Python, Ruby i PHP, mają swoje główne implementacje napisane w C. Jest nawet używany przez kompilatory dla innych języków do komunikacji z maszyną. Na przykład C jest językiem pośrednim leżącym u podstaw Eiffla i Fortha. Oznacza to, że zamiast generować kod maszynowy dla każdej obsługiwanej architektury, kompilatory dla tych języków generują tylko pośredni kod C, a kompilator C obsługuje generowanie kodu maszynowego.

C stało się także lingua franca komunikacji między programistami. Jak mówi Alex Allain, menedżer ds. inżynierii Dropbox i twórca Cprogramming.com:

C to świetny język do wyrażania wspólnych pomysłów w programowaniu w sposób, z którym większość ludzi czuje się komfortowo. Co więcej, wiele zasad używanych w C – na przykład argc i argv dla parametrów wiersza poleceń, a także konstrukcje pętli i typy zmiennych – pojawi się w wielu innych językach, których się uczysz, dzięki czemu będziesz mógł rozmawiać ludziom, nawet jeśli nie znają C w sposób wspólny dla was obojga.

Manipulacja pamięcią

Dostęp do arbitralnych adresów pamięci i arytmetyka wskaźników to ważna cecha, która sprawia, że ​​C doskonale nadaje się do programowania systemowego (systemy operacyjne i systemy wbudowane).

Na granicy sprzętu/oprogramowania systemy komputerowe i mikrokontrolery mapują swoje urządzenia peryferyjne i piny we/wy na adresy pamięci. Aplikacje systemowe muszą odczytywać i zapisywać te niestandardowe lokalizacje pamięci, aby komunikować się ze światem. Tak więc zdolność C do manipulowania dowolnymi adresami pamięci jest niezbędna do programowania systemu.

Mikrokontroler można zaprojektować na przykład w taki sposób, że bajt w pamięci o adresie 0x40008000 będzie wysyłany przez uniwersalny asynchroniczny odbiornik/nadajnik (lub UART, wspólny element sprzętowy do komunikacji z urządzeniami peryferyjnymi) za każdym razem, gdy ustawiony jest bit numer 4 adresu 0x40008001 na 1 i po ustawieniu tego bitu zostanie on automatycznie rozbrojony przez urządzenie peryferyjne.

To byłby kod funkcji C, która wysyła bajt przez ten UART:

 #define UART_BYTE *(char *)0x40008000 #define UART_SEND *(volatile char *)0x40008001 |= 0x08 void send_uart(char byte) { UART_BYTE = byte; // write byte to 0x40008000 address UART_SEND; // set bit number 4 of address 0x40008001 }

Pierwsza linia funkcji zostanie rozszerzona do:

 *(char *)0x40008000 = byte;

Ten wiersz mówi kompilatorowi, aby zinterpretował wartość 0x40008000 jako wskaźnik do char , a następnie wyłuskał (podał wartość wskazywaną przez) ten wskaźnik (z operatorem * po lewej stronie) i na koniec przypisał wartość byte do tego wyłuskanego wskaźnika. Innymi słowy: zapisz wartość byte zmiennej do adresu pamięci 0x40008000 .

Następna linia zostanie rozszerzona do:

 *(volatile char *)0x40008001 |= 0x08;

W tym wierszu wykonujemy bitową operację OR na wartości pod adresem 0x40008001 i wartości 0x08 ( 00001000 , czyli 1 w bicie numer 4), a wynik zapisujemy z powrotem pod adresem 0x40008001 . Innymi słowy: ustawiamy bit 4 bajtu, który jest pod adresem 0x40008001. Deklarujemy również, że wartość pod adresem 0x40008001 jest ulotna . To mówi kompilatorowi, że ta wartość może być modyfikowana przez procesy zewnętrzne w stosunku do naszego kodu, więc kompilator nie zrobi żadnych założeń dotyczących wartości w tym adresie po zapisaniu do niego. (W tym przypadku ten bit jest usuwany przez sprzęt UART zaraz po ustawieniu go przez oprogramowanie.) Ta informacja jest ważna dla optymalizatora kompilatora. Jeśli zrobiliśmy to na przykład wewnątrz pętli for , nie określając, że wartość jest ulotna, kompilator może założyć, że ta wartość nigdy się nie zmieni po ustawieniu i pominąć wykonywanie polecenia po pierwszej pętli.

Deterministyczne wykorzystanie zasobów

Wspólną cechą języka, na której programowanie systemowe nie może polegać, jest wyrzucanie śmieci, a nawet dynamiczna alokacja dla niektórych systemów wbudowanych. Wbudowane aplikacje są bardzo ograniczone pod względem czasu i zasobów pamięci. Są one często używane w systemach czasu rzeczywistego, w których nie można sobie pozwolić na niedeterministyczne wywołanie do garbage collectora. A jeśli nie można zastosować dynamicznej alokacji z powodu braku pamięci, bardzo ważne jest posiadanie innych mechanizmów zarządzania pamięcią, takich jak umieszczanie danych w niestandardowych adresach, na co pozwalają wskaźniki C. Języki, które w dużym stopniu zależą od dynamicznej alokacji i wyrzucania elementów bezużytecznych, nie byłyby odpowiednie dla systemów o ograniczonych zasobach.

Rozmiar kodu

C ma bardzo krótki czas pracy. A ślad pamięci na jego kod jest mniejszy niż w przypadku większości innych języków.

W porównaniu na przykład z C++, plik binarny wygenerowany w C, który trafia do urządzenia osadzonego, jest o połowę mniejszy od pliku binarnego generowanego przez podobny kod C++. Jedną z głównych przyczyn jest obsługa wyjątków.

Wyjątki są świetnym narzędziem dodanym przez C++ do C i, jeśli nie są uruchamiane i mądrze zaimplementowane, praktycznie nie powodują narzutu czasu wykonania (ale kosztem zwiększenia rozmiaru kodu).

Zobaczmy przykład w C++:

 // Class A declaration. Methods defined somewhere else; class A { public: A(); // Constructor ~A(); // Destructor (called when the object goes out of scope or is deleted) void myMethod(); // Just a method }; // Class B declaration. Methods defined somewhere else; class B { public: B(); // Constructor ~B(); // Destructor void myMethod(); // Just a method }; // Class C declaration. Methods defined somewhere else; class C { public: C(); // Constructor ~C(); // Destructor void myMethod(); // Just a method }; void myFunction() { A a; // Constructor aA() called. (Checkpoint 1) { B b; // Constructor bB() called. (Checkpoint 2) b.myMethod(); // (Checkpoint 3) } // b.~B() destructor called. (Checkpoint 4) { C c; // Constructor cC() called. (Checkpoint 5) c.myMethod(); // (Checkpoint 6) } // c.~C() destructor called. (Checkpoint 7) a.myMethod(); // (Checkpoint 8) } // a.~A() destructor called. (Checkpoint 9)

Metody klas A , B i C są zdefiniowane gdzie indziej (na przykład w innych plikach). Dlatego kompilator nie może ich przeanalizować i nie może wiedzieć, czy zgłoszą wyjątki. Musi więc przygotować się do obsługi wyjątków zgłoszonych z dowolnego z ich konstruktorów, destruktorów lub innych wywołań metod. Destruktory nie powinny rzucać (bardzo zła praktyka), ale użytkownik może mimo to rzucać lub mogą rzucać pośrednio, wywołując jakąś funkcję lub metodę (jawnie lub niejawnie), która zgłasza wyjątek.

Jeśli którekolwiek z wywołań w myFunction wyjątek, mechanizm rozwijania stosu musi być w stanie wywołać wszystkie destruktory dla obiektów, które zostały już skonstruowane. Jedna implementacja mechanizmu rozwijania stosu użyje adresu zwrotnego ostatniego wywołania z tej funkcji, aby zweryfikować „numer punktu kontrolnego” wywołania, które wyzwoliło wyjątek (jest to proste wyjaśnienie). Robi to poprzez użycie pomocniczej funkcji generowanej automatycznie (rodzaj tabeli przeglądowej), która będzie używana do rozwijania stosu w przypadku wyrzucenia wyjątku z ciała tej funkcji, co będzie podobne do tego:

 // Possible autogenerated function void autogeneratedStackUnwindingFor_myFunction(int checkpoint) { switch (checkpoint) { // case 1 and 9: do nothing; case 3: b.~B(); goto destroyA; // jumps to location of destroyA label case 6: c.~C(); // also goes to destroyA as that is the next line destroyA: // label case 2: case 4: case 5: case 7: case 8: a.~A(); } }

Jeśli wyjątek zostanie zgłoszony z punktów kontrolnych 1 i 9, żaden obiekt nie wymaga zniszczenia. Dla punktu kontrolnego 3, b i a muszą zostać zniszczone. Dla punktu kontrolnego 6, c i a muszą zostać zniszczone. We wszystkich przypadkach należy przestrzegać kolejności zniszczenia. W przypadku punktów kontrolnych 2, 4, 5, 7 i 8 tylko obiekt a musi zostać zniszczony.

Ta funkcja pomocnicza zwiększa rozmiar kodu. Jest to część dodatkowego miejsca, które C++ dodaje do C. Wiele aplikacji osadzonych nie może sobie pozwolić na tę dodatkową przestrzeń. Dlatego kompilatory C++ dla systemów wbudowanych często mają flagę wyłączającą wyjątki. Wyłączenie wyjątków w C++ nie jest bezpłatne, ponieważ Biblioteka szablonów standardowych w dużym stopniu opiera się na wyjątkach w celu informowania o błędach. Korzystanie z tego zmodyfikowanego schematu, bez wyjątków, wymaga więcej szkoleń dla programistów C++, aby wykryć możliwe problemy lub znaleźć błędy.

I mówimy o C++, języku, którego zasadą jest: „Nie płacisz za to, czego nie używasz”. Ten wzrost rozmiaru binarnego staje się gorszy w przypadku innych języków, które dodają dodatkowe obciążenie z innymi funkcjami, które są bardzo przydatne, ale nie mogą być zapewnione przez systemy wbudowane. Chociaż C nie umożliwia korzystania z tych dodatkowych funkcji, pozwala na znacznie bardziej zwarty ślad kodu niż inne języki.

Powody do nauki C

Język C nie jest trudny do nauczenia, więc wszystkie korzyści płynące z jego nauki będą dość tanie. Zobaczmy niektóre z tych korzyści.

Mieszanina języków

Jak już wspomniano, C jest lingua franca dla programistów. Wiele implementacji nowych algorytmów w książkach lub w Internecie jest najpierw (lub tylko) udostępnianych w języku C przez ich autorów. Daje to maksymalną możliwą przenośność do wdrożenia. Widziałem programistów walczących w Internecie o przepisanie algorytmu C na inne języki programowania, ponieważ nie znali bardzo podstawowych pojęć C.

Należy pamiętać, że C jest starym i rozpowszechnionym językiem, więc w sieci można znaleźć wszelkiego rodzaju algorytmy napisane w C. Dlatego najprawdopodobniej odniesiesz korzyści ze znajomości tego języka.

Zrozum maszynę (myśl w C)

Kiedy omawiamy z kolegami zachowanie pewnych fragmentów kodu lub pewnych cech innych języków, kończymy „rozmową w C:” Czy ta część przekazuje „wskaźnik” do obiektu, czy kopiuje cały obiekt? Czy może tu się dziać jakaś „obsada”? I tak dalej.

Rzadko dyskutowalibyśmy (lub myśleli) o instrukcjach asemblera, które wykonuje część kodu podczas analizy zachowania fragmentu kodu języka wysokiego poziomu. Zamiast tego, omawiając, co robi maszyna, mówimy (lub myślimy) całkiem wyraźnie w C.

Co więcej, jeśli nie możesz zatrzymać się i pomyśleć w ten sposób o tym, co robisz, możesz skończyć programując z pewnym rodzajem przesądu na temat tego, jak (magicznie) coś się robi.

Myśl jak maszyna z C

Pracuj nad wieloma ciekawymi projektami C

Wiele interesujących projektów, od dużych serwerów baz danych lub jąder systemu operacyjnego, po małe aplikacje wbudowane, które możesz nawet robić w domu dla własnej satysfakcji i zabawy, jest wykonywanych w C. Nie ma powodu, aby przestać robić rzeczy, które możesz kochać z jednego powodu że nie znasz starego i małego, ale silnego i sprawdzonego języka programowania, takiego jak C.

Pracuj nad fajnymi projektami z C

Wniosek

Iluminaci nie rządzą światem. Programiści C to robią.
Ćwierkać

Język programowania C nie ma daty ważności. Jego bliskość do sprzętu, świetna przenośność i deterministyczne wykorzystanie zasobów sprawia, że ​​jest idealny do niskopoziomowego rozwoju takich rzeczy, jak jądra systemu operacyjnego i oprogramowanie wbudowane. Jego wszechstronność, wydajność i dobra wydajność sprawiają, że jest to doskonały wybór dla oprogramowania do obróbki danych o wysokiej złożoności, takiego jak bazy danych lub animacje 3D. Fakt, że obecnie wiele języków programowania jest lepszych niż C pod względem ich zamierzonego zastosowania, nie oznacza, że ​​pokonują C we wszystkich obszarach. C nadal nie ma sobie równych, gdy wydajność jest priorytetem.

Świat działa na urządzeniach z zasilaniem C. Używamy tych urządzeń na co dzień, czy zdajemy sobie z tego sprawę, czy nie. C to przeszłość, teraźniejszość i, o ile widzimy, wciąż przyszłość dla wielu obszarów oprogramowania.

Powiązane: Jak nauczyć się języków C i C++: ostateczna lista