Home » Języki programowania – C i C++ – co wpływa na ich popularność?
Gdy powstawał ten artykuł, nie wiedzieliśmy jeszcze, że w styczniu 2023 TIOBE wybierze C++ językiem roku 2022! Mieliśmy nosa
W pierwszej części tej serii artykułów o najpopularniejszych językach programowania skupiliśmy się na języku C#, który w indeksie TIOBE z lipca 2022 zajmował piąte miejsce. Minęły 4 miesiące, a pięć pierwszych pozycji indeksu pozostało bez zmian – każdy z języków utrzymał swoje zaszczytne miejsce na podium, co nie jest rzeczą bardzo zaskakującą1.
Dzisiaj skupimy się na dwóch kolejnych pozycjach od dołu wyróżnionej przez TIOBE piątki, z wyłączeniem języka Java2. Języki C i C++ są w czołówce indeksu od 2001 roku, a omawiamy je w jednym artykule, by pokazać istotne różnice pomiędzy nimi.
Niestety, często kandydaci, z którymi spotykamy się podczas rozmów kwalifikacyjnych, łączą w swoich CV te dwa języki w jedną pozycję – C/C++. Dla mnie, jako osoby, która sprawdza techniczną wiedzę podczas spotkań, jest to sygnał, że kandydat może do końca nie znać różnic między tymi językami – zwykle podejrzewam, że chodzi jedynie o język C++ i zawsze to weryfikuję. Programista C nie zgrupowałby tego języka z jego następcą3. Istotnie różni je nie tylko sposób pracy, ale i projekty, w których je wykorzystujemy.
Język C jest najbardziej wyróżniającym się językiem ze wspomnianej piątki. Jako jedyny jest językiem niskopoziomowym, co znacząco wpływa na obszary, w których ma sensowne zastosowanie. Jest językiem zorientowanym na programowanie proceduralne, co nie oznacza oczywiście, że nie można w nim programować funkcyjnie, czy z użyciem praktyk OOP (ang. Object-Oriented Programming). Można, ale sam język programowania C nie zawiera konstrukcji gramatycznych, które by w tym pomagały.
Biblioteki takie jak standardowa C++ oraz STL (ang. Standard Template Library) dostarczają masę gotowych rozwiązań, które w C trzeba było tworzyć od zera. Nie bez powodu na rozmowach kwalifikacyjnych często padają pytania związane z STL – jej dobra znajomość pozwala szybko i efektywnie rozwiązać wiele podstawowych problemów. Adresuje ona, między innymi, problemy związane z zarządzaniem pamięcią.
Od czasu wejścia na rynek standardu C++11 coraz bardziej odchodzi się już od stosowania „czystych wskaźników” (ang. raw pointer) znanych z C, na rzecz szeregu dostępnych mechanizmów zarządzania pamięcią, takich jak inteligentne wskaźniki (ang. smart pointer). W kwestii refleksji C++ wciąż kuleje za swoimi nowszymi rywalami, ale najbardziej użyteczne mechanizmy, takie jak RTTI (ang. Run Time Type Information)11 i cechy typów, są w nim dostępne. Ma również obsługę wyjątków12.
Język C++, podobnie jak C, używany jest wszędzie tam, gdzie liczy się wydajność, ponieważ większość z mechanizmów spowalniających jego pracę jest opcjonalna13. Można zatem korzystać z wielu udogodnień, które oferuje – bez, lub z niewielkim dodatkowym narzutem. To sprawia, że jego zastosowania w dużej mierze pokrywają się z zastosowaniami języka C. W wielu nowszych projektach (również w systemach wbudowanych) dotykających najniższej warstwy sprzętu korzysta się zarówno z C jak i C++. Jest to popularny język programowania, zwłaszcza w urządzeniach z wbudowanym Linuxem.
Możliwości tego języka pozwalają skutecznie wykorzystać go w prawie każdej dziedzinie. Dzięki użyciu platform takich jak Qt z powodzeniem można w nim rozwijać aplikacje desktopowe. Współczesny C++ posiada szereg mechanizmów programowania asynchronicznego, wliczając mechanizmy tworzenia zadań wykorzystywane w EAP (ang. Event-based Asynchronous Pattern) czy mechanizmy realizujące koncepty „future” i „promise”, wykorzystane w TAP (ang. Task-based Asynchronous Pattern). Jest też powszechnie stosowany jako backend wielu serwisów internetowych. C++ to naprawdę uniwersalny język, który sprawdzi się w każdej roli, jednak nie do każdej oczywiście jest najlepszym możliwym wyborem.
Istotną bolączką pracy w projektach napisanych w języku C++ jest zarządzanie procesem kompilacji oraz dodatkowymi pakietami. Są to problemy praktycznie nieistniejące w Pythonie i marginalnie znaczące w C#. Do zarządzania kompilacją można użyć wielu zewnętrznych narzędzi – zależnie od tego, w którym roku powstał projekt, mogą to być make, autotools lub współcześnie, najczęściej CMake. Z kolei projekty, które powstały w Visual Studio, kompilowane są zwykle przez narzędzia dostarczone wraz z IDE. Zarządzanie zewnętrznymi bibliotekami wymaga użycia managera pakietów, takiego jak Conan14, który dobrze współgra z narzędziem CMake. Nie są to jednak narzędzia działające niezawodnie i dające ten sam efekt w każdym projekcie czy na każdej platformie. Praca z nimi wymaga trochę obycia i doświadczenia, zwłaszcza jeśli chcemy stworzyć własny pakiet do użycia w innych projektach, czy to własnych czy open-source. Pracując z kodem w C++, nie można zapomnieć o procesie kompilacji, a nawet trzeba poświęcić mu całkiem sporo uwagi, zwłaszcza w kluczowych momentach rozwoju projektu, takich jak rozszerzenie projektu o nowe moduły, dodawanie zależności, wdrożenie lub zmiana procesu CI, itp.
Mimo wielu udogodnień, które pomagają programistom w pracy, C++ to nadal trudny język. Jego konstrukcje gramatyczne zdają się często nienaturalne i wymagają znajomości skomplikowanych mechanizmów, które działają w tle, by w pełni wykorzystać jego moc15. Niezrozumiałe błędy kompilacji, zajmujące dziesiątki stron tekstu, to w pracy z szablonami w C++ normalność. Przykład poniżej należy do jednego z najprostszych. W złożonych projektach szukanie właściwego komunikatu błędu zajmuje sporo czasu i nie zawsze IDE jest w stanie z tym pomóc.
Jednak błędy kompilacji to najprostsze z możliwych, bo wychodzą na powierzchnię od razu. Dużo trudniejsze błędy to te, które na przykład wykluczają RVO (ang. Return-Value Optimization), ignorują lub nieprawidłowo wykorzystują semantyki przenoszenia danych (ang. move semantics), czy jeden z wielu zaawansowanych mechanizmów optymalizacji dostępnych w C++. Potrzeba dużo doświadczenia, by rozpoznać, że dany mechanizm nie działa poprawnie i zorientować się dlaczego nie działa. Co więcej, mnogość ukrytych mechanizmów, które w różnych sytuacjach zachodzą „w tle” bez informowania o tym programisty, znacząco utrudnia efektywne wykorzystanie tego języka początkującym. To język programowania, w którym mam ponad dziesięć lat doświadczenia, a i tak niejednokrotnie frustrują mnie związane z nim problemy. Bezapelacyjnie, C++ ma największy próg wejścia ze wszystkich współcześnie stosowanych na rynku języków programowania i wymaga wieloletniego doświadczenia, by umiejętnie się nim posługiwać.
Sprawy nie ułatwia fakt, że każda nowa edycja16 wprowadza szereg mechanizmów, które wzbogacają język, ale często zmieniają podejście do niektórych konceptów i wymagają przyzwyczajenia. Z tego powodu wiele projektów decyduje się zatrzymać na określonej wersji standardu C++ i nie aktualizować go do nowszej17 – to jest również powód, dla którego istotne jest wyróżnienie z którą wersją języka dany programista miał jedynie styczność, a z którą spędził więcej czasu.
Języki C i C++, mimo ogromu swoich wad, nie muszą się bać o to, że w nadchodzących czasach stracą swoją popularność – zbyt dużo kodu w nich powstało i wciąż wymaga utrzymania, ale też cały czas powstają produkty, do wytworzenia których nadają się najlepiej. Niemniej C i C++ to rzadko wybory początkujących programistów, głównie dlatego, że istnieją interesujące oferty pracy w konkurencyjnych językach, których dużo łatwiej się nauczyć i efektywnie pracować w krótszym czasie. A jednak, popularność tych języków jest niezmienna, a zapotrzebowanie na specjalistów C i C++ nieustające. Dla tych, którzy nie boją się wyzwań związanych z nauką tych języków, pracy nie zabraknie.
Omówiliśmy już języki programowania C#, C++ oraz C. Serię zakończymy językiem Python, który niewiele ma wspólnego z powyższą trójką, a którego popularność w bardzo krótkim czasie pchnęła go na pierwsze miejsce indeksu. W 2018 roku zajmował jedynie 3% rynku, aktualnie jest to około 17% – co czyni go tak popularnym wyborem wśród programistów? Dowiecie się z trzeciej części serii.
Porozmawiajmy o Twoim projekcie! Nasi inżynierowie zaproponują Ci najlepsze rozwiązania techniczne, dzięki którym wydobędziesz potencjał swojego pomysłu. Obejrzyj projekty, które zrealizowaliśmy z klientami, którzy potrzebowali oprogramowania na zamówienie.
Tomasz Przedziński, rocznik 86, ukończył studia magisterskie z Informatyki Stosowanej na Uniwersytecie Jagiellońskim w Krakowie i obronił doktorat z inżynierii oprogramowania na AGH. Specjalizuje się w C++, ale mocno polubił się ze środowiskami embedded. Nie ukrywa sympatii do algorytmiki i optymalizacji rozwiązań. W pracy zawodowej stawia na jakość, budując zaawansowane środowiska testowe lub rozbudowując istniejące.
1 - Pierwsze cztery pozycje, w kolejności Python, C, Java i C++, zajmują razem 55% w indeksie, zostawiając konkurencję daleko w tyle, a ich miejsca w czołówce zdają się niezagrożone. Od lipca ich pozycje wzrosły łącznie prawie o 7%. Z kolei piąty na liście C#, który był głównym tematem pierwszej części tej serii, utrzymał swoją piątą pozycję, mimo straty udziałów w tym okresie. Zresztą, o czym wspominałem w poprzednim tekście, temu językowi programowania nie są obce skoki popularności.
2 - Dawniej, do prostszych silników gier używało się języka C, ale ze względu na złożoność współczesnych silników obecnie nie praktykuje się takich rozwiązań. W języku Python można pisać gry, ale ze względu na niską wydajność tego silnika są to głównie niewymagające sprzętowo produkcje 2D. Dobrym przykładem jest silnik RenPy, który jest popularnym silnikiem do tworzenia gier typu Visual Novel.
3 - Jak wspominaliśmy, język Java jest pominięty w tej serii tylko dlatego, że jej autor nie ma praktycznego doświadczenia w jego używaniu i nie jest kompetentny mówić o jego zaletach i wadach.
4 - Jedną z największych bolączek C jest manipulacja wskaźnikami do tablic czy struktur danych. Przekazywane wskaźniki mogą dotyczyć obszaru pamięci dowolnego rozmiaru i typu. W złożonych projektach, gdzie funkcje przyjmują generyczne wskaźniki, programista sam musi stworzyć mechanizm informujący z jakim obszarem pamięci mamy w danej chwili do czynienia lub ufać, że dane funkcje zawsze operują tylko na wyznaczonych typach danych.
5 - Na przykład prawie każdy projekt dotyczący systemów wbudowanych prędzej czy później będzie potrzebował bufora cyklicznego. Na rynku są setki bibliotek, które oferują taką strukturę danych, ale ja osobiście nie spotkałem się nawet z jednym projektem, który wykorzystałby taką bibliotekę. Zawsze pisana jest ona na nowo. Oczywiście mówimy o bardzo prostym, ale powszechnym przykładzie. Takich przykładów jest znacznie więcej.
6 - Wynika to głównie z tego, że zespoły wolą zmienić istniejącą implementację lub napisać ją na nowo, zamiast wypracować wspólnie wersję, której mogłyby użyć dwa lub więcej projektów. Czasem jest to wynik potrzeby optymalizacji, a czasem próby przedwczesnej i nieuzasadnionej optymalizacji.
7 - Przykładowo, gdy na jaw wyszła potężna dziura w bibliotece OpenSSL, nazwana Heartblead, szacowało się, że około 17% serwerów posiadających certyfikaty nadane przez CA (Certification Authority) było podatnych na atak, który pozwalał m.in. poznać prywatne klucze serwera, podsłuchać całe połączenie z serwerem czy wykonać atak typu man-in-the-middle. Jak grzyby po deszczu zaczęły pojawiać się strony, aplikacje i dodatki do przeglądarek analizujące czy serwery, z którymi się łączymy, są podatne na tą dziurę. W grę wchodziło bezpieczeństwo każdego użytkownika Internetu. Od tamtego czasu wykryto szereg nowych dziur w bibliotece OpenSSL o najwyższej możliwej ocenie zagrożenia, wynoszącej 10. Najnowsza taka dziura pochodzi z 1. lipca tego roku.
8 - Opublikowana w marcu 2022 roku aktualizacja MacOS do wersji 12.3 spowodowała, że ogromna ilość urządzeń ze zmienioną płytą główną przestała się uruchamiać. Obejściem tego problemu była pracochłonna procedura przywracająca działanie urządzeń.
9 - Rust siódmy rok z rzędu został uznany przez użytkowników StackOverflow jako „most loved”, czyli język, w którym największa ilość ankietowanych, bo aż 87%, chce nadal pracować. Niedawno Linus Torvalds, główny autor jądra Linuxa, oświadczył, że najdalej w 2023 roku pierwsze moduły napisane w Rust powinny znaleźć się w jądrze Linux. Z kolei Google rozpoczął przepisywanie fragmentów systemu operacyjnego Android do Rust. W październiku Rust ponownie wskoczył do top-20 języków w indeksie TIOBE. Jego popularność wciąż rośnie, co ukazuje, że programiści pracujący na najniższej warstwie oprogramowania widzą potrzebę i doceniają istnienie języka, który narzuca szereg bardzo sztywnych zasad, ale daje w zamian bezpieczeństwo i znacznie większą kontrolę nad działaniem programu.
10 - W C++ nadal są makra, choć większość zastosowań tych makr można zastąpić szablonami i nowymi składniami wprowadzonymi w C++20. Nadal można w C++ pisać proceduralnie, choć nie wykorzystamy wtedy większości możliwości tego języka. Można w C++ zarządzać pamięcią tak jak w języku C, choć istnieje szereg nowych mechanizmów znacznie ułatwiających tę pracę. Można też zrobić co się chce ze wskaźnikami do obszarów pamięci, choć znów, C++ dodaje masę mechanizmów zabezpieczających przed podstawowymi błędami wynikającymi ze złego użycia wskaźników. Technicznie, kod w C z bardzo niewielkimi wyjątkami da się skompilować kompilatorem kodu C++. Ale, oczywiście, nie odwrotnie.
11 - Co ciekawe, jest głęboka świadomość kosztu w postaci obniżonej wydajności, jaki mechanizm RTTI wprowadza w projekcie, dlatego też RTTI można wyłączyć w wielu kompilatorach C++, uniemożliwiając jego użycie w projekcie.
12 - Podobnie jak z RTTI, narzut związany z rzucaniem wyjątków jest na tyle duży, że często użycie wyjątków w C++ uznawane jest za złą praktykę. Kompilatory zwykle również pozwalają zablokować ich użycie. Moim zdaniem „zła karma” dotycząca wyjątków dotyczy nie tyle kosztu ich użycia, co braku doświadczenia programistów w ich używaniu oraz kosztu nieprawidłowego ich stosowania. Nie bez powodu te mechanizmy są kluczowe we wszystkich innych wysokopoziomowych językach programowania.
13 - Pomijając wspomniane wcześniej mechanizmy RTTI czy obsługi wyjątków, które można wyłączyć, nic nie zmusza programisty C++ do używania dziedziczenia i polimorfizmu. C++ posiada również możliwość dziedziczenia po wielu klasach, co w przypadku, gdy klasy bazowe nie są czysto abstrakcyjne, jest bardzo wolnym mechanizmem. Nikt jednak nie wymaga by z tego mechanizmu korzystać i jest to nawet powszechnie uważane za złą praktykę.
14 - Paradoksalnie instalowany managerem pakietów Pythona.
15 - Dla przykładu, jakkolwiek wielu programistów rozumie na czym polega unikalny dla C++ koncept SFINAE (ang. Substitution Failure Is Not An Error), to na wagę złota jest programista, który jest w stanie poprawnie wymienić wszystkie etapy rozwiązywania przeciążonych nazw (ang. Overload resolution), nie mówiąc już o poprawnym zastosowaniu tej wiedzy w praktyce.
16 - Obecnie wiodąca wersja to C++20. Prace nad C++23, który zgodnie z notacją miałby być opublikowany w 2023 roku, wciąż trwają.
17 - W środowiskach systemów wbudowanych najpopularniejszy jest standard C++11, choć powoli wchodzi w nie C++14. W branży automotive powszechnie stosowany jest C++14. Jest to również obecnie najszerzej używana wersja w projektach na rynku. C++17 powoli wkracza w istniejące projekty. Im nowsze, tym więcej ich elementów jest napisanych w C++17. Natomiast C++20 osobiście spotykam jedynie w ofertach pracy na nowe stanowiska. Nie zauważam jeszcze jego powszechniejszego użycia w istniejących projektach.
Najnowsze wpisy na blogu
Wypełnij formularz
Po wypełnieniu poniższego formularza skontaktujemy się z Tobą, żeby umówić rozmowę w dogodnym dla Ciebie terminie.
Sąd Rejonowy Gdańsk-Północ w Gdańsku, VII Wydział Gospodarczy Krajowego Rejestru Sądowego, Kapitał zakładowy: 455 647,60 zł PLN
We use cookies for the website to function properly. By clicking “Accept”, you consent to the use of cookies for analytical and marketing purposes. You can adjust or withdraw your consent at any time. Więcej w Polityce cookies