Dlaczego język programowania C nadal rządzi

Żadna technologia nie utrzymuje się przez 50 lat, chyba że wykonuje swoją pracę lepiej niż większość innych - zwłaszcza technologia komputerowa. Język programowania C jest żywy i prężnie rozwijający się od 1972 roku i nadal króluje jako jeden z podstawowych elementów budulcowych naszego świata zdefiniowanego programowo.

Ale czasami technologia się trzyma, ponieważ ludzie po prostu nie próbowali jej zastąpić. W ciągu ostatnich kilku dziesięcioleci pojawiło się dziesiątki innych języków - niektóre z nich zostały specjalnie zaprojektowane, by rzucić wyzwanie dominacji C, inne odrywają C z boku jako produkt uboczny ich popularności.

Nie jest trudno argumentować, że C wymaga wymiany. Badania nad językiem programowania i praktyki związane z rozwojem oprogramowania wskazują, że istnieją znacznie lepsze sposoby robienia rzeczy niż sposób C. Ale C nadal istnieje, mając za sobą dziesięciolecia badań i rozwoju. Niewiele innych języków może go pokonać pod względem wydajności, kompatybilności z komputerem lub wszechobecności. Mimo to warto zobaczyć, jak C wypada na tle znanych konkurencji językowych w 2018 roku.

C kontra C ++

Naturalnie C najczęściej porównuje się do C ++, języka, który - jak sama nazwa wskazuje - został stworzony jako rozszerzenie C. Różnice między C ++ i C można scharakteryzować jako rozległe lub  nadmierne , w zależności od tego, kogo zapytamy.

Mimo że w swojej składni i podejściu nadal przypomina język C, C ++ zapewnia wiele naprawdę przydatnych funkcji, które nie są dostępne natywnie w języku C: przestrzenie nazw, szablony, wyjątki, automatyczne zarządzanie pamięcią i tak dalej. Projekty wymagające najwyższej wydajności - bazy danych, systemy uczenia maszynowego - są często pisane w języku C ++ przy użyciu tych funkcji, aby wycisnąć z systemu każdy spadek wydajności.

Co więcej, C ++ nadal rozwija się znacznie bardziej agresywnie niż C. Nadchodzący C ++ 20 wnosi jeszcze więcej do tabeli, w tym moduły, procedury, bibliotekę synchronizacji i koncepcje, które ułatwiają korzystanie z szablonów. Najnowsza wersja standardu C dodaje niewiele i skupia się na zachowaniu wstecznej kompatybilności.

Rzecz w tym, że wszystkie plusy w C ++ mogą również działać jako minusy. Duże. Im więcej funkcji C ++ używasz, tym bardziej złożoność wprowadzasz i trudniej jest ujarzmić wyniki. Programiści, którzy ograniczają się do podzbioru C ++, mogą uniknąć wielu z jego najgorszych pułapek i ekscesów. Ale niektóre sklepy chcą razem chronić się przed złożonością C ++. Trzymanie się C zmusza programistów do ograniczania się do tego podzbioru. Na przykład zespół programistów jądra Linuksa unika C ++.

Wybór języka C zamiast C ++ to dla Ciebie - i dla wszystkich deweloperów, którzy zajmują się kodem po tobie - uniknięcie konieczności plątania się w ekscesy C ++ dzięki zastosowaniu narzuconego minimalizmu. Oczywiście C ++ ma bogaty zestaw funkcji wysokiego poziomu nie bez powodu. Ale jeśli minimalizm lepiej pasuje do obecnych i przyszłych projektów - i zespołów projektowych - to C ma więcej sensu.

C kontra Java

Po dziesięcioleciach Java pozostaje podstawą tworzenia oprogramowania dla przedsiębiorstw - i ogólnie podstawą rozwoju. Wiele z najważniejszych projektów oprogramowania dla przedsiębiorstw zostało napisanych w języku Java - w tym zdecydowana większość projektów Apache Software Foundation - a Java pozostaje dobrym językiem do opracowywania nowych projektów z wymaganiami klasy korporacyjnej.

Składnia Java wiele zapożycza z C i C ++. Jednak w przeciwieństwie do C, Java nie kompiluje się domyślnie do kodu natywnego. Zamiast tego środowisko wykonawcze Java, JVM, JIT (just-in-time) kompiluje kod Java do uruchomienia w środowisku docelowym. W odpowiednich okolicznościach kod JITted Java może zbliżyć się lub nawet przekroczyć wydajność C.

Filozofia „zapisz raz, uruchom gdziekolwiek” stojąca za Javą pozwala również na uruchamianie programów Java przy stosunkowo niewielkich modyfikacjach dla architektury docelowej. Z drugiej strony, chociaż C został przeportowany na bardzo wiele architektur, każdy program w C może nadal wymagać dostosowania, aby działał poprawnie, powiedzmy, w systemie Windows i Linux.

To połączenie przenośności i wysokiej wydajności, wraz z ogromnym ekosystemem bibliotek oprogramowania i struktur, sprawia, że ​​Java jest językiem i środowiskiem wykonawczym do tworzenia aplikacji korporacyjnych.

Tam, gdzie Java brakuje C, to obszar, w którym Java nigdy nie miała konkurować: biegać blisko metalu lub bezpośrednio pracować ze sprzętem. Kod C jest kompilowany do kodu maszynowego, który jest wykonywany bezpośrednio przez proces. Java jest kompilowana do kodu bajtowego, który jest kodem pośrednim, który interpreter JVM następnie konwertuje na kod maszynowy. Co więcej, chociaż automatyczne zarządzanie pamięcią w Javie jest błogosławieństwem w większości przypadków, C lepiej nadaje się do programów, które muszą optymalnie wykorzystywać ograniczone zasoby pamięci.

To powiedziawszy, istnieją obszary, w których Java może zbliżyć się do C pod względem szybkości. Silnik JVM maszyny JVM optymalizuje procedury w czasie wykonywania w oparciu o zachowanie programu, pozwalając na wiele klas optymalizacji, które nie są możliwe w przypadku skompilowanego z wyprzedzeniem C. I chociaż środowisko wykonawcze Java automatyzuje zarządzanie pamięcią, niektóre nowsze aplikacje to obejdą. Na przykład Apache Spark optymalizuje przetwarzanie w pamięci częściowo przy użyciu niestandardowego kodu zarządzania pamięcią, który omija JVM.

C kontra C # i .Net

Prawie dwie dekady po ich wprowadzeniu, C # i .Net Framework pozostają głównymi częściami świata oprogramowania dla przedsiębiorstw. Mówi się, że C # i .Net były odpowiedzią Microsoftu na Javę - system kompilatora zarządzanego kodu i uniwersalne środowisko wykonawcze - a tak wiele porównań między C i Javą jest również w przypadku C i C # / .Net.

Podobnie jak Java (i do pewnego stopnia Python), .Net zapewnia przenośność na różne platformy i rozległy ekosystem zintegrowanego oprogramowania. Są to niemałe korzyści, biorąc pod uwagę, jak bardzo zorientowany na przedsiębiorstwa rozwój ma miejsce w świecie .Net. Tworząc program w języku C # lub jakimkolwiek innym języku .Net, możesz korzystać z wielu narzędzi i bibliotek napisanych dla środowiska uruchomieniowego .Net. 

Kolejną zaletą platformy .NET podobną do języka Java jest optymalizacja JIT. Programy w językach C # i .Net mogą być kompilowane z wyprzedzeniem, zgodnie z C, ale są one głównie kompilowane dokładnie na czas przez środowisko wykonawcze .Net i optymalizowane za pomocą informacji o czasie wykonywania. Kompilacja JIT umożliwia wszelkiego rodzaju optymalizacje w miejscu dla uruchomionego programu .Net, których nie można wykonać w C.

Podobnie jak C, C # i .Net zapewniają różne mechanizmy bezpośredniego dostępu do pamięci. Sterta, stos i niezarządzana pamięć systemowa są dostępne za pośrednictwem interfejsów API i obiektów .Net. Programiści mogą używać tego unsafetrybu w .Net, aby osiągnąć jeszcze większą wydajność.

Jednak nic z tego nie jest darmowe. Zarządzanych obiektów i unsafeobiektów nie można dowolnie wymieniać, a organizowanie między nimi wiąże się ze spadkiem wydajności. Dlatego maksymalizacja wydajności aplikacji .Net oznacza ograniczenie do minimum ruchu między obiektami zarządzanymi i niezarządzanymi.

Kiedy nie możesz sobie pozwolić na zapłacenie kary za pamięć zarządzaną i niezarządzaną lub gdy środowisko uruchomieniowe .Net jest złym wyborem dla środowiska docelowego (np. Miejsce na jądro) lub może być w ogóle niedostępne, C jest tym, czego potrzebujesz potrzeba. W przeciwieństwie do C # i .Net, C domyślnie odblokowuje bezpośredni dostęp do pamięci. 

C vs. Go

Składnia Go wiele zawdzięcza C - nawiasom klamrowym jako ogranicznikom, instrukcjom zakończonym średnikami i tak dalej. Programiści biegli w języku C mogą zazwyczaj bez większych trudności przejść od razu do Go, nawet biorąc pod uwagę nowe funkcje Go, takie jak przestrzenie nazw i zarządzanie pakietami.

Czytelny kod był jednym z głównych celów projektowania w Go: ułatwienie programistom przyspieszenia pracy z dowolnym projektem w Go i szybkie opanowanie podstawy kodu. Bazy kodów C mogą być trudne do zrozumienia, ponieważ mają tendencję do przekształcania się w szczurze gniazdo makr i #ifdefsą specyficzne zarówno dla projektu, jak i dla danego zespołu. Składnia Go i wbudowane narzędzia do formatowania kodu i zarządzania projektami mają na celu powstrzymanie tego rodzaju problemów instytucjonalnych.

Go zawiera również dodatki, takie jak gorutyny i kanały, narzędzia na poziomie języka do obsługi współbieżności i przekazywania komunikatów między komponentami. C wymagałoby, aby takie rzeczy były ręcznie zwijane lub dostarczane przez zewnętrzną bibliotekę, ale Go dostarcza je od razu po wyjęciu z pudełka, co znacznie ułatwia tworzenie oprogramowania, które ich potrzebuje.

Gdzie Go różni się najbardziej od C pod maską, to zarządzanie pamięcią. Obiekty Go są domyślnie zarządzane automatycznie i zbierane jako elementy bezużyteczne. W przypadku większości zadań programistycznych jest to niezwykle wygodne. Ale oznacza to również, że każdy program wymagający deterministycznej obsługi pamięci będzie trudniejszy do napisania.

Go zawiera unsafepakiet do omijania niektórych zabezpieczeń obsługi Go, takich jak odczytywanie i zapisywanie dowolnej pamięci z Pointertypem. Ale unsafezawiera ostrzeżenie, że napisane za jego pomocą programy „mogą być nieprzenośne i nie są chronione przez wytyczne dotyczące zgodności Go 1”.

Go dobrze nadaje się do budowania programów, takich jak narzędzia wiersza poleceń i usługi sieciowe, ponieważ rzadko wymagają one tak drobiazgowych manipulacji. Ale sterowniki urządzeń niskiego poziomu, komponenty systemu operacyjnego w przestrzeni jądra i inne zadania, które wymagają ścisłej kontroli nad układem pamięci i zarządzaniem, najlepiej tworzyć w C.

C kontra Rust

W pewnym sensie Rust jest odpowiedzią na zagadki zarządzania pamięcią stworzone przez C i C ++, a także na wiele innych niedociągnięć tych języków. Rust kompiluje się do natywnego kodu maszynowego, więc pod względem wydajności jest na równi z C. Jednak domyślne bezpieczeństwo pamięci jest głównym atutem Rusta.

Składnia i reguły kompilacji Rusta pomagają programistom uniknąć typowych błędów związanych z zarządzaniem pamięcią. Jeśli program ma problem z zarządzaniem pamięcią, który przecina składnię Rusta, po prostu się nie kompiluje. Nowicjusze w tym języku, zwłaszcza z języka takiego jak C, w którym jest dużo miejsca na takie błędy, spędzają pierwszą fazę swojej edukacji na temat Rusta, ucząc się, jak uspokoić kompilator. Ale zwolennicy Rusta argumentują, że ten krótkotrwały ból ma długoterminowe korzyści: bezpieczniejszy kod, który nie poświęca szybkości.

Rdza ulepsza również C z jego narzędziami. Zarządzanie projektami i komponentami jest częścią łańcucha narzędzi dostarczanego domyślnie z Rust, tak samo jak w Go. Istnieje domyślny, zalecany sposób zarządzania pakietami, organizowania folderów projektów i obsługi wielu innych rzeczy, które w C są w najlepszym przypadku ad-hoc, a każdy projekt i zespół obsługuje je inaczej.

Jednak to, co jest reklamowane jako zaleta w Rust, może nie wydawać się jednym z nich dla programisty C. Zabezpieczeń Rusta w czasie kompilacji nie można wyłączyć, więc nawet najbardziej trywialny program Rusta musi być zgodny z ograniczeniami Rusta dotyczącymi bezpieczeństwa pamięci. C może być domyślnie mniej bezpieczny, ale w razie potrzeby jest znacznie bardziej elastyczny i wybaczający.

Inną możliwą wadą jest rozmiar języka Rust. C ma stosunkowo niewiele funkcji, nawet biorąc pod uwagę bibliotekę standardową. Zestaw funkcji Rust jest rozległy i stale się rozwija. Podobnie jak w przypadku C ++, większy zestaw funkcji Rusta oznacza większą moc, ale także większą złożoność. C to mniejszy język, ale o wiele łatwiejszy do modelowania mentalnego, więc być może lepiej nadaje się do projektów, w których Rust byłby przesadą.

C kontra Python

W dzisiejszych czasach, ilekroć mowa jest o tworzeniu oprogramowania, Python zawsze pojawia się w rozmowie. W końcu Python jest „drugim najlepszym językiem do wszystkiego” i bez wątpienia jednym z najbardziej wszechstronnych, z tysiącami bibliotek innych firm.

To, co podkreśla Python i najbardziej różni się od C, to przedkładanie szybkości programowania nad szybkość wykonywania. Program, którego złożenie w innym języku - takim jak C - może zająć godzinę, może zostać złożony w Pythonie w ciągu kilku minut. Z drugiej strony, wykonanie tego programu w C może zająć kilka sekund, ale uruchomienie w Pythonie może zająć minutę. (Dobra zasada praktyczna: programy w Pythonie działają na ogół o rząd wielkości wolniej niż ich odpowiedniki w C). Ale w przypadku wielu zadań na nowoczesnym sprzęcie Python jest wystarczająco szybki i to było kluczem do jego upowszechnienia.

Kolejną istotną różnicą jest zarządzanie pamięcią. Programy w języku Python są w pełni zarządzane pamięcią przez środowisko wykonawcze języka Python, więc programiści nie muszą martwić się o szczegółowość przydzielania i zwalniania pamięci. Ale tutaj znowu łatwość programisty odbywa się kosztem wydajności środowiska wykonawczego. Pisanie programów w C wymaga skrupulatnej uwagi na zarządzanie pamięcią, ale programy wynikowe są często złotym standardem czystej szybkości maszyny.

Jednak pod skórą Python i C mają głębokie połączenie: referencyjne środowisko uruchomieniowe Pythona jest napisane w C. Pozwala to programom Pythona na zawijanie bibliotek napisanych w C i C ++. Kod w języku C jest oparty na znacznych fragmentach ekosystemu Pythona obejmującego biblioteki innych firm, na przykład do uczenia maszynowego.

Jeśli szybkość programowania ma większe znaczenie niż szybkość wykonania i jeśli większość wydajnych części programu można wyodrębnić w samodzielne komponenty (w przeciwieństwie do rozłożenia w całym kodzie), to czysty Python lub mieszanka bibliotek Python i C. lepszy wybór niż sam C. W przeciwnym razie C nadal rządzi.