Dlaczego ludzie używają C, jeśli jest to tak niebezpieczne?

132

Rozważam naukę C.

Ale dlaczego ludzie używają C (lub C ++), jeśli można go używać „niebezpiecznie”?

Przez niebezpieczne rozumiem wskaźniki i inne podobne rzeczy.

Jak pytanie o przepełnienie stosu Dlaczego funkcja gets jest tak niebezpieczna, że ​​nie należy jej używać? . Dlaczego programiści nie używają tylko Java, Python lub innego skompilowanego języka, takiego jak Visual Basic?

Tristan
źródło
152
Dlaczego szefowie kuchni używają noży, skoro można ich używać „niebezpiecznie”?
oerkelens,
78
Z dużą mocą przychodzi duża odpowiedzialność.
Pieter B,
10
Joe Blow, pontyfikat dużo?
Matthew James Briggs,
4
Ponieważ „Back In The Day”, gdy C stał się językiem wyboru, oczekiwaliśmy, że będziemy w stanie poradzić sobie z takimi rzeczami, ponieważ musieliśmy. Języki interpretowane lub bajtowe były zbyt wolne, ponieważ procesory tego dnia działały o wiele wolniej. (Dzisiaj mogę kupić komputer stacjonarny niskiej klasy z wielordzeniowym procesorem 2+ GHz i 4 GB pamięci od Dell za 279 USD. Nie masz pomysłu, jak absolutnie niesamowite wydaje się to facetowi takiemu jak ja, dla którego komputer 4 MHz z 640 kilobajtów pamięci była błogość ...). Spójrz prawdzie w oczy - prawo Moore'a wygrało. Gra. Koniec!
Bob Jarvis,
6
@Bob Jarvis: Gra się nie skończyła. Jeśli uważasz, że Twój komputer 2 + GHz, 4 GB - lub w tym przypadku klaster kilkuset komputerów 4 GHz z najnowszymi procesorami graficznymi CUDA lub czymkolwiek - jest wystarczająco szybki, po prostu nie pracujesz nad wystarczająco trudnymi problemami :-)
jamesqf

Odpowiedzi:

246
  1. C wyprzedza wiele innych języków, o których myślisz. Wiele z tego, co wiemy o tym, jak uczynić programowanie „bezpieczniejszym”, pochodzi z doświadczenia z językami takimi jak C.

  2. Wiele bezpieczniejszych języków, które pojawiły się od czasu C, polega na większym środowisku wykonawczym, bardziej skomplikowanym zestawie funkcji i / lub maszynie wirtualnej do osiągnięcia swoich celów. W rezultacie C pozostało czymś w rodzaju „najniższego wspólnego mianownika” wśród wszystkich popularnych / popularnych języków.

    • C jest znacznie łatwiejszym językiem do implementacji, ponieważ jest stosunkowo mały i ma większe szanse na odpowiednią wydajność nawet w najsłabszym środowisku, więc wiele systemów wbudowanych, które muszą opracować własne kompilatory i inne narzędzia, jest w stanie zapewnić funkcjonalny kompilator dla C.

    • Ponieważ C jest tak mały i tak prosty, inne języki programowania mają tendencję do komunikowania się ze sobą za pomocą API typu C. Jest to prawdopodobnie główny powód, dla którego C nigdy tak naprawdę nie umrze, nawet jeśli większość z nas kiedykolwiek wejdzie z nim w interakcję poprzez opakowania.

  3. Wiele „bezpieczniejszych” języków, które starają się ulepszyć w C i C ++, nie próbuje być „językami systemowymi”, które dają prawie całkowitą kontrolę nad wykorzystaniem pamięci i zachowaniem się programu w czasie wykonywania. Chociaż prawdą jest, że coraz więcej aplikacji w dzisiejszych czasach po prostu nie potrzebuje takiego poziomu kontroli, zawsze będzie kilka drobnych przypadków, w których jest to konieczne (szczególnie w maszynach wirtualnych i przeglądarkach, które implementują te wszystkie ładne, bezpieczne języki dla reszta z nas).

    Obecnie istnieje kilka języków programowania systemów (Rust, Nim, D, ...), które są bezpieczniejsze niż C lub C ++. Mają korzyści z perspektywy czasu i zdają sobie sprawę, że w większości przypadków taka dokładna kontrola nie jest potrzebna, dlatego oferują ogólnie bezpieczny interfejs z kilkoma niebezpiecznymi zaczepami / trybami, które można przełączać, gdy jest to naprawdę konieczne.

  4. Nawet w obrębie C nauczyliśmy się wielu zasad i wytycznych, które drastycznie zmniejszają liczbę podstępnych błędów, które pojawiają się w praktyce. Zasadniczo niemożliwe jest wprowadzenie w życie tych reguł z mocą wsteczną, ponieważ spowodowałoby to uszkodzenie zbyt wiele istniejącego kodu, ale powszechne jest używanie ostrzeżeń kompilatora, linters i innych narzędzi do analizy statycznej w celu wykrycia tego rodzaju problemów, którym można łatwo zapobiec. Podzbiór programów C, które przekazują te narzędzia w jaskrawych kolorach, jest już znacznie bezpieczniejszy niż „tylko C”, a każdy kompetentny programista C w tych dniach będzie z nich korzystać.


Ponadto, nigdy nie dokona ukrywane konkurs Java jako zabawne jak ukrywane konkursie C .

Ixrec
źródło
7
„najniższy wspólny mianownik” brzmi obraźliwie. Powiedziałbym, że w przeciwieństwie do wielu znacznie cięższych języków, C nie wymusza tony dodatkowego bagażu, którego nie potrzebujesz, i zapewnia błyskawiczną bazę do wdrażania rzeczy, których potrzebujesz. Czy o to ci chodziło?
underscore_d
9
„Naprawdę nie możesz mieć jednego bez drugiego.”: W rzeczywistości wiele nowych języków (Rust, Nim, D, ...) próbuje. Zasadniczo sprowadza się to do dopasowania „bezpiecznego” podzbioru języka z kilkoma „niebezpiecznymi” prymitywami, gdy ten poziom kontroli jest absolutnie konieczny. Jednak wszystkie oparte są na wiedzy zgromadzonej w C i C ++, więc można powiedzieć, że w czasie, gdy C i C ++ były rozwijane, nie można mieć jednego bez drugiego, a obecnie istnieje język, który próbuje się rozdzielić ich „niebezpieczne” fragmenty, ale jeszcze ich nie dogonili.
Matthieu M.,
6
1) nie jest prawidłowym punktem! C wziął funkcje z Algolu 68, który jest w tym sensie „bezpiecznym” językiem; więc autorzy wiedzieli o takich językach. Pozostałe punkty są świetne.
reinierpost
4
@MatthieuM. Możesz także przyjrzeć się Microsoft Research. W ciągu ostatniej dekady lub dwóch wyprodukowali kilka zarządzanych systemów operacyjnych, w tym takie, które są szybsze (zupełnie przypadkowo - głównym celem większości tych systemów badawczych jest bezpieczeństwo, a nie szybkość) niż równoważny niezarządzany system operacyjny dla niektórych rzeczywistych obciążeń roboczych . Przyspieszenie wynika głównie z ograniczeń bezpieczeństwa - statyczne i dynamiczne sprawdzanie wykonywalności umożliwia optymalizacje, które nie są dostępne w niezarządzanym kodzie. W sumie jest na co popatrzeć, a źródła są uwzględnione;)
Luaan
3
@Luaan: Midori wygląda tak niesamowicie; Zawsze czekam na artykuły na blogu Joe.
Matthieu M.,
41

Po pierwsze, C jest językiem programowania systemów. Na przykład, jeśli napiszesz maszynę wirtualną Java lub interpreter Pythona, będziesz potrzebować systemowego języka programowania, aby ją napisać.

Po drugie, C zapewnia wydajność, której nie zapewniają języki takie jak Java i Python. Zazwyczaj wysokowydajne obliczenia w Javie i Pythonie wykorzystują biblioteki napisane w języku o wysokiej wydajności, takim jak C, do ciężkiego podnoszenia.

Po trzecie, C ma znacznie mniejszą powierzchnię niż języki takie jak Java i Python. Dzięki temu można go używać w systemach wbudowanych, które mogą nie mieć zasobów niezbędnych do obsługi dużych środowisk wykonawczych i wymagań dotyczących pamięci w językach takich jak Java i Python.


„Systemowy język programowania” to język odpowiedni do budowania systemów o sile przemysłowej; w obecnej formie Java i Python nie są językami programowania systemowego. „Dokładnie to, co sprawia, że ​​systemowy język programowania” nie wchodzi w zakres tego pytania, ale systemowy język programowania musi zapewniać wsparcie w pracy z platformą bazową.

Z drugiej strony (w odpowiedzi na komentarze) systemowy język programowania nie musi być hostem własnym. Ten problem pojawił się, ponieważ oryginalny zadawane pytanie „dlaczego ludzie używają C”, pierwszy komentarz pytanie „dlaczego trzeba język jak C”, gdy masz pypy, i zauważył, że pypy ma w rzeczywistości użyć C tak, to był pierwotnie istotny dla pytania, ale niestety (i myląco) „samodzielne hostingowanie” w rzeczywistości nie ma znaczenia dla tej odpowiedzi. Przepraszam, że o tym wspomniałem.

Podsumowując: Java i Python nie nadają się do programowania systemów nie dlatego, że ich podstawowe implementacje są interpretowane, ani dlatego, że natywnie skompilowane implementacje nie są hostowane, ale dlatego, że nie zapewniają niezbędnej obsługi do pracy z platformą bazową.

nadchodząca burza
źródło
19
„jeśli napiszesz maszynę wirtualną Java lub interpreter Pythona, będziesz potrzebować systemowego języka programowania, aby je napisać.” Co? Jak wytłumaczysz PyPy? Dlaczego potrzebujesz języka takiego jak C, aby napisać kompilator, tłumacz lub maszynę wirtualną?
Vincent Savard
10
Wierzę, że twierdziłeś, że potrzebujesz systemowego języka programowania do napisania wirtualnej maszyny Java lub interpretera Pythona, kiedy powiedziałeś „jeśli napiszesz wirtualną maszynę Java lub interpreter Pythona, będziesz potrzebował systemowego języka programowania do pisania”. Jeśli PyPy cię nie satysfakcjonuje, możesz także szukać dowolnego interpretera lub kompilatora napisanego w języku Haskell. Lub naprawdę, po prostu dodaj referencję potwierdzającą twoje roszczenie.
Vincent Savard
16
Nawet jeśli żółwie są do końca, coś takiego jak PyPy nie mogłoby istnieć bez interpretera Pythona napisanego w innym języku.
Blrfl,
18
@Birfl Ale to tak naprawdę niewiele mówi. C nie mógłby zostać napisany bez kompilatora asemblera i nie można pisać asemblera bez sprzętu, który go implementuje.
ogrodnik
11
Jeśli PyPy nie jest „systemowym językiem programowania”, ponieważ gdzieś zaangażowany jest kompilator C, to C nie jest też językiem programowania systemowego, ponieważ gdzieś używany jest asembler. W rzeczywistości, czy obecnie nie jest popularne tłumaczenie C na jakiś inny język? np. LLVM
30

Przykro mi, że dodałem jeszcze jedną odpowiedź, ale nie sądzę, aby którakolwiek z istniejących odpowiedzi bezpośrednio dotyczyła pierwszego zdania, stwierdzając:

„Rozważam naukę języka C”

Dlaczego? Czy chcesz robić rzeczy, do których zwykle używa się obecnie C (np. Sterowniki urządzeń, maszyny wirtualne, silniki gier, biblioteki multimediów, systemy osadzone, jądra systemu operacyjnego)?

Jeśli tak, to tak, na pewno naucz się C lub C ++ w zależności od tego, kim jesteś zainteresowany. Czy chcesz się tego nauczyć, aby lepiej zrozumieć, co robi Twój język wysokiego poziomu?

Następnie wspomnij o obawach dotyczących bezpieczeństwa. Nie musisz dogłębnie rozumieć bezpiecznego C, aby zrobić to drugie, w taki sam sposób, jak przykład kodu w języku wyższego poziomu może dać ci sedno bez gotowości do produkcji.

Napisz kod C, aby uzyskać sedno. Następnie odłóż go z powrotem na półkę. Nie przejmuj się zbytnio bezpieczeństwem, chyba że chcesz napisać produkcyjny kod C.

Jared Smith
źródło
2
Świetna robota odpowiadająca na prawdziwe pytanie! Świetnym sposobem na docenienie C / C ++ i „bezpieczniejszych” języków dla wszystkich, którymi naprawdę są, jest napisanie czegoś takiego jak prosty silnik bazy danych. Dajesz dobre wyczucie każdego z podejść, które próbujesz, i widzisz, dokąd cię to zaprowadzi. Zobaczysz, co wydaje się naturalne w każdym z nich, i zobaczysz, gdzie naturalne podejście zawodzi (np. Bardzo łatwo jest „serializować” surowe dane w C - po prostu zapisuj dane !; ale wynik nie jest przenośny, więc może mieć ograniczone zastosowanie). Zrozumienie bezpieczeństwa jest trudne, ponieważ większość problemów może być trudna do rozwiązania.
Luaan
1
@Luaan, dokładnie, jak skopiować ciąg znaków za pomocą wskaźników, daje pomysł, nauczenie się, jak robić to bezpiecznie, to kolejny poziom, a zależnie od celów może być niepotrzebny.
Jared Smith
2
To nie było tak naprawdę pytanie. Ale pomogło to w rozwiązaniu problemu. Postanowiłem to zrobić. Jestem gotów dowiedzieć się więcej o wewnętrznym działaniu wszystkiego, na czym się opiera. I po prostu lubię programować. W ten sposób mam nadzieję lepiej zrozumieć wewnętrzne działanie komputerów. To jest po prostu dla zabawy.
Tristan
10
Nie zgadzam się z ostatnim zdaniem. Dowiedz się, jak zrobić to dobrze, zanim rozwiniesz złe nawyki.
glglgl,
2
@glglgl IDK, jeśli czytam (lub piszę) fragment kodu JavaScript w sieci, robię to ze świadomością, że nie jest on gotowy do produkcji: nie będzie obsługiwał wyjątków, może być O (n ^ 2) itp. Brak z tego jest konieczne, aby uzyskać punkt. Wszystko to jest niezbędne do kodu produkcyjnego. Dlaczego to jest inne? Potrafię napisać naiwne C dla mojego własnego wyszkolenia, jednocześnie rozumiejąc intelektualnie, że jeśli chciałbym je tam przedstawić, musiałbym wykonać o wiele więcej pracy.
Jared Smith,
14

To jest OGROMNE pytanie z mnóstwem odpowiedzi, ale krótka wersja jest taka, że ​​każdy język programowania specjalizuje się w różnych sytuacjach. Na przykład JavaScript dla stron internetowych, C dla rzeczy niskiego poziomu, C # dla czegokolwiek w systemie Windows itp. Pomaga wiedzieć, co chcesz robić, kiedy znasz programowanie i decydujesz, który język programowania wybrać.

Aby odnieść się do ostatniego punktu, dlaczego C / C ++ w Javie / Python, często sprowadza się do szybkości. Tworzę gry, a Java / C # ostatnio osiągają prędkości wystarczające do uruchomienia gier. W końcu, jeśli chcesz, aby Twoja gra działała z szybkością 60 klatek na sekundę i chcesz, aby gra dużo (renderowanie jest szczególnie kosztowne), musisz uruchomić kod tak szybko, jak to możliwe. Python / Java / C # / Wiele innych działa na „interpreterach”, dodatkowej warstwie oprogramowania, która obsługuje wszystkie żmudne rzeczy, których nie obsługuje C / C ++, takie jak zarządzanie pamięcią i wyrzucanie elementów bezużytecznych. To dodatkowe obciążenie spowalnia działanie, więc prawie każda duża gra, którą widzisz, została wykonana (w każdym razie przez ostatnie 10 lat) w C lub C ++. Istnieją wyjątki: silnik gry Unity używa C # *, a Minecraft używa Java, ale są one wyjątkiem, a nie regułą. Ogólnie,

* Nawet Unity to nie wszystko C #, ogromne części to C ++, a po prostu używasz C # do kodu gry.

EDYCJA Aby odpowiedzieć na niektóre komentarze, które pojawiły się po opublikowaniu tego: Być może zbytnio upraszczałem zbyt wiele, po prostu dawałem ogólny obraz. W przypadku programowania odpowiedź nigdy nie jest prosta. Istnieją interpretery dla C, JavaScript może działać poza przeglądarką, a C # może działać na prawie wszystkim dzięki Mono. Różne języki programowania są wyspecjalizowane dla różnych domen, ale jakiś programista prawdopodobnie wymyślił, jak uruchomić dowolny język w dowolnym kontekście. Ponieważ OP wydawał się nie znać dużo programowania (założenie z mojej strony, przepraszam, jeśli się mylę), starałem się zachować prostą odpowiedź.

Jeśli chodzi o komentarze o tym, że C # jest prawie tak szybki jak C ++, kluczowym słowem jest prawie. Kiedy byłem na studiach, zwiedziliśmy wiele firm z branży gier, a mój nauczyciel (który zachęcał nas do przejścia od C # do C ++ przez cały rok) pytał programistów z każdej firmy, w której poszliśmy, dlaczego C ++ zamiast C # i każda z nich powiedział C # jest zbyt wolny. Zasadniczo działa szybko, ale moduł wyrzucania elementów bezużytecznych może zaszkodzić wydajności, ponieważ nie można kontrolować, kiedy działa, i ma prawo cię zignorować, jeśli nie chce uruchamiać się zgodnie z zaleceniami. Jeśli potrzebujesz czegoś, co ma być wysoką wydajnością, nie chcesz czegoś tak nieprzewidywalnego.

Aby odpowiedzieć na mój komentarz „tylko osiągające prędkości”, tak, znaczna część wzrostu prędkości C # pochodzi z lepszego sprzętu, ale wraz z poprawą frameworka .NET i kompilatora C # nastąpiły pewne przyspieszenia.

W zależności od komentarza „gry są pisane w tym samym języku, co silnik”. Niektóre są, ale wiele z nich jest napisanych w języku hybrydowym. Unreal potrafi używać UnrealScript i C ++, Unity robi C # Javascript i Boo, wiele innych silników napisanych w C lub C ++ używa Pythona lub Lua jako języków skryptowych. Nie ma tam prostej odpowiedzi.

I tylko dlatego, że zmusił mnie do przeczytania „kogo to obchodzi, jeśli gra działa z prędkością 200 lub 120 klatek na sekundę”, jeśli gra działa szybciej niż 60 klatek na sekundę, prawdopodobnie marnujesz czas procesora, ponieważ przeciętny monitor nawet tego nie odświeża szybki. Niektóre wyższe i nowsze mają, ale nie jest to standard (jeszcze ...).

Jeśli chodzi o uwagę dotyczącą „ignorowania dziesięcioleci techniki”, wciąż mam 20 lat, więc kiedy ekstrapoluję wstecz, w większości powtarzam to, co powiedzieli mi starsi i bardziej doświadczeni programiści. Oczywiście będzie to kwestionowane na takiej stronie, ale warto to rozważyć.

Cody
źródło
4
C # dla dowolnego systemu Windows ” - Och, to taki błąd. A ty nawet podajesz przykład. Jedność. AFAIK Nie napisano, że zapewnia C # API, ponieważ język jest przyjemny w adaptacji. Jest naprawdę dobrze zaprojektowany. I bardziej podoba mi się c ++, ale należy przyznać uznanie tam, gdzie jest to należne. Może połączyłeś C # z .NET? Często spotykają się razem.
luk32
2
„Nawet Unity to nie wszystko C #, ogromne części to C ++” I? Gry w Unity często intensywnie używają C # i istnieją już od dłuższego czasu. Sugerowanie, że C # „niedawno osiąga prędkości” albo wymaga większego kontekstu, albo grozi, że będzie ślepy na tę dekadę technologii.
NPSF3000,
2
Niemal każda duża gra była napisana w języku używanego silnika: ilość pracy, która wymagałaby powielenia, była tak duża, że ​​nie warto było brać pod uwagę żadnej innej uwagi technicznej. Renderowanie jest rzeczywiście drogie, ale obecnie wszystko to jest napisane w shaderach, a język pętli logicznej jest nieistotny.
Peter Taylor,
2
C # zawsze był kompilowany w JIT (w przeciwieństwie do Javy, gdzie twój komentarz jest poprawny) i był całkiem zdolny do bardzo podobnych prędkości wykonywania do C ++ od samego początku, jeśli wiedziałeś, co robisz. To rok 2003 - nie jest to coś, co rozważałbym ostatnio. Nieprzetworzona prędkość nie jest głównym problemem w grach (szczególnie z programowalnymi modułami cieniującymi na GPU), są też inne rzeczy, które sprawiły, że języki takie jak C # były mniej lub bardziej popularne. Dwa główne problemy to interfejsy API (które są silnie zorientowane na C, a interfejs może być drogi) i GC (głównie ze względu na opóźnienia, a nie surową przepustowość).
Luaan
1
@gbjbaanb Nie chodzi tylko o to, że procesory są szybsze - dużą rzeczą jest to, że C ++ i C mieli dziesięciolecia na udoskonalenie swoich kompilatorów i środowiska uruchomieniowego, podczas gdy Java zaczęła zasadniczo od zera (głównie jako platforma wieloplatformowa). Wraz z poprawą maszyny wirtualnej (np. Przejście z interpretera na kompilator JIT, poprawiono GC ...), poprawiła się również wydajność aplikacji Java. Dużą przewagą C / C ++ wciąż jest podejście „miejmy nadzieję, że nic nie zepsuje” - unikanie wielu kontroli, które są uważane za „niepotrzebne”. Ale wciąż jest to ogromna pamięć - w rzeczywistości poprawa wykorzystania procesora często oznaczała gorszą wydajność pamięci :)
Luaan
13

To zabawne, że twierdzisz, że C nie jest bezpieczniejszy, ponieważ „ma wskaźniki”. Jest odwrotnie: Java i C # mają praktycznie tylko wskaźniki (dla typów innych niż rodzime). Najczęstszym błędem w Javie jest prawdopodobnie wyjątek Null Pointer (por. Https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare ). Drugim najczęściej występującym błędem jest prawdopodobnie przechowywanie ukrytych odniesień do nieużywanych obiektów (np. Zamknięte dialogi nie są usuwane), których w związku z tym nie można zwolnić, co prowadzi do długotrwałych programów z ciągle rosnącym stopniem pamięci.

Istnieją dwa podstawowe mechanizmy, które sprawiają, że C # i Java są bezpieczniejsze i bezpieczniejsze na dwa różne sposoby:

  • Czyszczenie pamięci zmniejsza prawdopodobieństwo, że program spróbuje uzyskać dostęp do odrzuconych obiektów. To sprawia, że ​​mniej prawdopodobne jest nieoczekiwane zakończenie działania programu. W przeciwieństwie do C, Java i C # domyślnie przydzielają dynamicznie dane nienatywne. To sprawia, że ​​logika programu jest w rzeczywistości bardziej złożona, ale wbudowane wyrzucanie elementów bezużytecznych - kosztem - przejmuje trudną część.

Najnowsze inteligentne wskaźniki C ++ ułatwiają programistom to zadanie.

  • Java i C # kompilują się do kodu pośredniego, który jest interpretowany / wykonywany przez skomplikowany czas działania. Dodaje to poziom bezpieczeństwa, ponieważ środowisko wykonawcze może wykryć nielegalne działania programu. Nawet jeśli program jest kodowany niepewnie (co jest możliwe w obu językach), odpowiedni czas działania teoretycznie zapobiega „włamaniu się” do systemu.
    Czas działania nie chroni przed np. Próbami przekroczenia bufora, ale teoretycznie nie pozwala na wykorzystanie takich programów. Natomiast w C i C ++ programista musi bezpiecznie kodować, aby zapobiec exploitom. Zwykle nie osiąga się tego od razu, ale wymaga przeglądu i iteracji.

Warto jednak zauważyć, że skomplikowany czas działania stanowi również zagrożenie dla bezpieczeństwa. Wydaje mi się, że Oracle aktualizuje JVM co kilka tygodni z powodu nowo wykrytych problemów z bezpieczeństwem. Oczywiście znacznie trudniej jest zweryfikować JVM niż pojedynczy program.

Bezpieczeństwo skomplikowanego czasu pracy jest zatem dwuznaczne i do pewnego stopnia mylące: Twój średni program C można, dzięki przeglądom i iteracjom, zapewnić względnie bezpiecznemu bezpieczeństwu. Twój przeciętny program Java jest tak bezpieczny, jak JVM; to znaczy nie do końca. Nigdy.

Artykuł na gets()ten temat zawiera linki do historycznych decyzji bibliotecznych, które zostałyby dzisiaj podjęte inaczej, a nie podstawowy język.

Peter A. Schneider
źródło
3
Myślę, że celem oryginalnego autora jest to, że w C możesz podjąć niesprawdzone działanie na tych wskaźnikach. W Javie od razu dostajesz miły wyjątek - w C możesz nie zdawać sobie sprawy, że czytasz nieprawidłową lokalizację, dopóki stan aplikacji nie zostanie uszkodzony.
Sam Dufel,
1
Również stackoverflow.com/questions/57483/... - bardziej trafne byłoby stwierdzenie, że Java ma „praktycznie tylko referencje”.
Sam Dufel,
4
Widziałem wiele interesujących błędów wynikających z prób uniknięcia wyraźnych wskazówek. Zwykle podstawową kwestią jest pomieszanie kopii z odniesieniem. W C, jeśli przekażę ci wskaźnik do czegoś, wiesz, że jego modyfikacja wpłynie na oryginał. Niektóre próby uniknięcia wskaźników zaciemniają to rozróżnienie, prowadząc do labiryntu głębokich kopii, płytkich kopii i ogólnego zamieszania.
Arlie Stephens
Twierdzenie, że JVM Oracle jest do bani (z punktu widzenia krwotocznych nadających się do wykorzystania luk w zabezpieczeniach), a zatem środowiska wykonawcze zarządzanych języków ogólnie wprowadzają więcej problemów związanych z bezpieczeństwem niż używanie języka zarządzanego, których unika, jest jak powiedzenie, że Adobe Flash jest przerażający jako źródło niepewności i program, który czy odtwarzanie wideo i animacji ze strony internetowej z natury musi być absurdalnie niebezpieczne. Nie wszystkie środowiska wykonawcze Java są prawie tak złe, jak obrzydliwość JVM firmy Oracle / Sun z lat 90. XX wieku i nie wszystkie zarządzane języki to Java. (Cóż, oczywiście.)
głównie poinformowano
@halfinformed Well; Mówiłem, że program jest tak bezpieczny, jak jego środowisko uruchomieniowe, i że „Twój średni” (czytaj: small-ish) program można z trudem uczynić bezpieczniejszym niż jakikolwiek duży środowisko wykonawcze, takie jak interpreter kodu bajtowego. To stwierdzenie wydaje się niezaprzeczalne. To, czy konkretny program autonomiczny, czy określony środowisko wykonawcze jest mniej lub bardziej bezpieczne od drugiego, zależy od ich złożoności oraz projektu, kodowania i jakości konserwacji. Na przykład nie powiedziałbym, że sendmail jest bezpieczniejszy niż Java VM Oracle; ale qmail może być.
Peter A. Schneider,
10

Ponieważ „bezpieczeństwo” kosztuje prędkość, „bezpieczniejsze” języki działają wolniej.

Pytasz, dlaczego używasz „niebezpiecznego” języka, takiego jak C lub C ++, każesz komuś napisać ci sterownik wideo lub podobny w Pythonie lub Javie itp. I zobaczysz, co myślisz o „bezpieczeństwie” :)

Poważnie, musisz być tak blisko podstawowej pamięci maszyny, aby móc manipulować pikselami, rejestrami itp. ... Java lub Python nie mogą tego zrobić z żadną szybkością godną wydajności ... C i C ++ zarówno pozwalają to zrobić za pomocą wskaźników i tym podobnych ...

Wintermut3
źródło
20
Zasadniczo nie jest prawdą, że bezpieczeństwo kosztuje prędkość . Najbezpieczniejsze dostępne języki wykonują większość kontroli podczas kompilacji. O'Caml, Ada, Haskell, Rust nie podążają za C pod względem średniej prędkości działania. To, co zwykle robią, to znaczne obciążenie programowe, wydajność pamięci, opóźnienie i oczywiście czas kompilacji. I tak, mają problemy z materiałami zbliżonymi do metalu. Ale to nie jest tak naprawdę problem prędkości.
leftaroundabout
6
Ponadto C nie robi tego, co myślisz. C to abstrakcyjna maszyna . Nie daje ci bezpośredniego dostępu do niczego - to dobra rzecz. Nie możesz nawet patrzeć na nowoczesny asembler, aby zobaczyć, jak wiele C ukrywa się przed tobą - nowoczesne asemblery (np. TASM) byłyby uważane za język wysokiego poziomu, gdy C został opracowany. Byłbym bardzo szczęśliwy, gdyby ktoś napisał sterowniki w „bezpiecznym” języku, dziękuję - to całkiem pomogłoby uniknąć wielu takich BSOD i zawiesza się, nie wspominając o lukach w zabezpieczeniach :) A co najważniejsze, języki systemowe są znacznie bezpieczniejsze niż C.
Luaan
3
@Wintermute Naprawdę chcesz sprawdzić Rust przed komentowaniem, w jaki sposób funkcje bezpieczeństwa muszą kosztować szybkość. Cholera, układ typu C za niski poziom faktycznie hamuje wiele bardzo użytecznych optymalizacje że kompilatory mogłoby zrobić (szczególnie gdy rozważa że prawie nie duży projekt c udaje się uniknąć, aby nie naruszać ścisłego aliasing gdzieś ).
Voo
7
@Wintermute Tak, mit, że nie można uczynić C / C ++ bezpieczniejszym bez wprowadzenia narzutu wydajności, jest bardzo trwały i dlatego podchodzę do tego dość poważnie (istnieją pewne obszary, w których jest to z pewnością prawda [sprawdzanie granic]). Dlaczego więc Rust nie jest bardziej rozpowszechniony? Historia i złożoność. Rdza jest wciąż stosunkowo nowa i wiele z największych systemów napisanych w C istniało, zanim Rust został wynaleziony - nie będziesz przepisywać miliona LOC w nowym języku, nawet jeśli byłoby to znacznie bezpieczniejsze. Również każdy programista i ich pies zna C, Rust? Powodzenia w znalezieniu wystarczającej liczby osób.
Voo
3
@Voo Dogs robi C? ... nic dziwnego, że widziałem tyle złego kodu ... j / k Punkt poświęcony Rustowi (właśnie go pobrałem i zainstalowałem, więc możesz mieć kolejną konwersję do dodania) BTW w odniesieniu do Rust, robiąc to dobrze ... Mógłbym zrobić to samo ulepszenie dla „D” :)
Wintermut3
9

Oprócz wszystkich powyższych, istnieje również jeden dość powszechny przypadek użycia, który używa C jako wspólnej biblioteki dla innych języków.

Zasadniczo prawie wszystkie języki mają interfejs API do C.

Prosty przykład, spróbuj utworzyć wspólną aplikację dla Linux / IOS / Android / Windows. Oprócz wszystkich dostępnych narzędzi, skończyliśmy na zrobieniu biblioteki podstawowej w C, a następnie zmianie GUI dla każdego środowiska, czyli:

  • IOS: ObjectiveC może używać bibliotek C. natywnie
  • Android: Java + JNI
  • Linux / Windows / MacOS: Z GTK / .Net możesz korzystać z bibliotek natywnych. Jeśli używasz Python, Perl, Ruby, każdy z nich ma natywne interfejsy API. (Java ponownie z JNI).

Moje dwa centy,

Nito
źródło
Jednym z powodów, dla których uwielbiam używać, powiedzmy, PHP, jest to, że prawie wszystkie jego biblioteki są rzeczywiście napisane w C - na szczęście tak, inaczej PHP byłoby nieznośnie powolne :) PHP świetnie pisze niechlujny kod bez obaw robienie czegokolwiek „niebezpiecznego” (i dlatego zwykle piszę o wiele więcej kodu PHP niż cokolwiek innego - lubię niechlujny kod!: D), ale miło jest wiedzieć, że pod wieloma tymi wywołaniami funkcji są dobre stare Biblioteki C, aby zwiększyć wydajność ;-) Natomiast pisanie niechlujnego kodu w C jest wielkim nie-nie ...
Gwyneth Llewelyn
7

Podstawową trudnością związaną z C jest to, że nazwa jest używana do opisania wielu dialektów o identycznej składni, ale bardzo różnej semantyce. Niektóre dialekty są znacznie bezpieczniejsze niż inne.

W języku C, jak pierwotnie zaprojektował Dennis Ritchie, instrukcje C byłyby generalnie odwzorowane na instrukcje maszynowe w przewidywalny sposób. Ponieważ C mógł działać na procesorach, które zachowywały się inaczej, gdy wystąpiły takie rzeczy, jak podpisane przepełnienie arytmetyczne, programista, który nie wiedział, jak zachowa się maszyna w przypadku przepełnienia arytmetycznego, nie wiedziałby, jaki kod C działający na tym komputerze zachowałby się również, ale jeśli wiadomo, że komputer zachowuje się w określony sposób (np. owijanie komplementu dwóch cichych), wówczas implementacje na tej maszynie zwykle działałyby podobnie. Jednym z powodów, dla których C zyskał reputację szybkiego, było to, że w przypadkach, gdy programiści wiedzieli, że naturalne zachowanie platformy w scenariuszach skrajnych będzie odpowiadało ich potrzebom, nie było potrzeby, aby programista lub kompilator pisał kod w celu wygenerowania takich scenariuszy. .

Niestety twórcy kompilatorów przyjęli pogląd, że skoro standard nie nakłada żadnych wymagań na to, co implementacje muszą zrobić w takich przypadkach (luźność, która miała umożliwić implementacje sprzętowe, które mogą nie zachowywać się przewidywalnie), kompilatory powinny swobodnie generować kod, który neguje prawa czasu i przyczynowości.

Zastanów się nad czymś takim:

int hey(int x)
{
   printf("%d", x);
   return x*10000;
}
void wow(int x)
{
  if (x < 1000000)
    printf("QUACK!");
  hey(x);    
}

Hipernowoczesna (ale modna) teoria kompilatora sugerowałaby, że kompilator powinien wypisać „QUACK!” bezwarunkowo, ponieważ w każdym przypadku, gdy warunek był fałszywy, program w końcu wywołałby niezdefiniowane zachowanie wykonując wielokrotność, której wynik i tak zostanie zignorowany. Ponieważ Standard pozwoliłby kompilatorowi robić wszystko, co mu się podoba w takim przypadku, pozwala on kompilatorowi na wyjście „QUACK!”.

Podczas gdy C był bezpieczniejszy niż język asemblera, w hipernowoczesnych kompilatorach jest odwrotnie. W języku asemblera przepełnienie liczb całkowitych może spowodować, że obliczenia przyniosą bezsensowny wynik, ale na większości platform będzie to zakres jego efektów. Jeśli wyniki i tak zostaną zignorowane, przepełnienie nie będzie miało znaczenia. Jednak w hipernowoczesnym C nawet to, co normalnie byłoby „łagodnymi” formami niezdefiniowanego zachowania (takie jak przepełnienie liczby całkowitej w obliczeniach, które ostatecznie zostaje zignorowane) może spowodować wykonanie dowolnego programu.

supercat
źródło
1
nawet w hipernowoczesnym kompilatorze C nie sprawdza tablic. gdyby tak się stało, byłoby to niezgodne z definicją języka. używam tego faktu czasami do tworzenia tablic z dodatkowym wskaźnikiem na środku tablicy, aby mieć ujemne wskaźniki.
robert bristow-johnson,
1
Chciałbym zobaczyć dowody, że twój przykład produkuje „QUACK!” bezwarunkowo. x z pewnością może być większy niż 1000000 w punkcie porównania, a późniejsza ocena, która spowodowałaby przepełnienie, nie zapobiega temu. Co więcej, jeśli masz włączone wstawianie, które pozwala na usunięcie przepełnionego mnożenia, twój argument dotyczący ukrytych ograniczeń zakresu nie ma zastosowania.
Graham,
2
@ robertbristow-johnson: W rzeczywistości Standard dość wyraźnie mówi, że biorąc pod uwagę, na przykład int arr[5][[5], próba dostępu arr[0][5]da nieokreślone zachowanie. Taka reguła umożliwia kompilatorowi, któremu podano coś w rodzaju arr[1][0]=3; arr[0][i]=6; arr[1][0]++;wnioskowania, które arr[1][0]będzie równe 4, bez względu na wartość i.
supercat
2
@ robertbristow-johnson: Nawet jeśli kompilator przydziela tablice wewnątrz struktury sekwencyjnie bez przerw, nie gwarantuje to, że indeksowanie jednej z tablic będzie miało wpływ na inną. Zobacz godbolt.org/g/Avt3KW, aby zobaczyć, jak gcc będzie traktować taki kod.
supercat
1
@ robertbristow-johnson: Skomentowałem zgromadzenie, aby wyjaśnić, co robi. Kompilator widzi, że kod przechowuje 1 w s-> arr2 [0], a następnie inkrementuje s-> arr2 [0], więc gcc łączy te dwie operacje, po prostu przechowując wartość 2, bez uwzględnienia możliwości zapisu na s-> arr1 [i] może wpływać na wartość s-> arr1 [0] (ponieważ, zgodnie ze standardem, nie może).
supercat
5

Przyczyny historyczne. Często nie piszę zupełnie nowego kodu, głównie utrzymuję i rozszerzam stare rzeczy, które działają od dziesięcioleci. Cieszę się, że to C, a nie Fortran.

Mogę się zirytować, gdy jakiś uczeń mówi: „Ale dlaczego, u licha, robisz to okropne X, skoro możesz robić Y?”. Cóż, X to praca, którą mam i bardzo ładnie płaci rachunki. Robiłem Y przy okazji i było fajnie, ale X jest tym, co większość z nas robi.

RedSonja
źródło
5

Co jest „niebezpieczne”?

Twierdzenie, że C jest „niebezpieczne”, jest częstym tematem wojen językowych o płomienie (najczęściej w porównaniu z Javą). Jednak dowody na to twierdzenie są niejasne.

C to język z określonym zestawem funkcji. Niektóre z tych funkcji mogą dopuszczać pewne rodzaje błędów, które nie są dozwolone przez inne typy języków (ryzyko zarządzania pamięcią C jest zwykle podkreślone). Nie jest to jednak to samo, co argument, że C jest bardziej niebezpieczny niż inne języki ogółem . Nie znam nikogo, kto dostarczy przekonujące dowody na ten temat.

Również „niebezpieczne” zależy od kontekstu: co próbujesz zrobić i jakie rodzaje obaw się martwisz?

W wielu kontekstach uważałbym C za „niebezpieczny” niż język wysokiego poziomu, ponieważ wymaga to większej ręcznej implementacji podstawowej funkcjonalności, zwiększając ryzyko błędów. Na przykład wykonanie podstawowego przetwarzania tekstu lub opracowanie strony internetowej w C byłoby zwykle głupie, ponieważ inne języki mają funkcje, które znacznie to ułatwiają.

Jednak C i C ++ są szeroko stosowane w systemach o znaczeniu krytycznym, ponieważ mniejszy język z bardziej bezpośrednią kontrolą hardward jest w tym kontekście uważany za „bezpieczniejszy”. Z bardzo dobrej odpowiedzi Przepełnienie stosu :

Chociaż C i C ++ nie zostały specjalnie zaprojektowane dla tego typu aplikacji, są one szeroko stosowane w oprogramowaniu wbudowanym i krytycznym dla bezpieczeństwa z kilku powodów. Główne właściwości notatki to kontrola nad zarządzaniem pamięcią (co pozwala na przykład uniknąć konieczności wyrzucania elementów bezużytecznych), proste, dobrze debugowane podstawowe biblioteki wykonawcze i dojrzała obsługa narzędzi. Wiele używanych obecnie wbudowanych łańcuchów narzędzi programistycznych opracowano po raz pierwszy w latach 80. i 90. XX wieku, kiedy była to obecna technologia i wywodziła się z dominującej wówczas kultury uniksowej, więc narzędzia te pozostają popularne w tego rodzaju pracy.

Chociaż kod zarządzania pamięcią ręczną musi być dokładnie sprawdzony, aby uniknąć błędów, pozwala on na kontrolę czasu odpowiedzi aplikacji niedostępną w przypadku języków zależnych od odśmiecania. Podstawowe biblioteki wykonawcze języków C i C ++ są stosunkowo proste, dojrzałe i dobrze zrozumiane, dlatego należą do najbardziej stabilnych dostępnych platform.

Społeczność
źródło
2
Powiedziałbym, że hipernowoczesne C jest również bardziej niebezpieczne niż język asemblerowy lub autentyczne dialekty niskiego poziomu C, które zachowują się tak, jakby konsekwentnie tłumaczyły operacje C na operacje kodu maszynowego bez względu na przypadki krawędzi, w których naturalne operacje kodu maszynowego mają określone zachowanie, ale Norma C nie nakłada żadnych wymagań. Hipernowoczesne podejście, w którym przepełnienie liczb całkowitych może negować reguły czasu i przyczynowości, wydaje się znacznie mniej podatne na generowanie bezpiecznego kodu.
supercat
5

Aby dodać do istniejących odpowiedzi, dobrze jest powiedzieć, że wybierzesz Python lub PHP dla swojego projektu, ze względu na ich względne bezpieczeństwo. Ale ktoś musi wdrożyć te języki, a kiedy to zrobi, prawdopodobnie zrobi to w C. (Lub, no cóż, coś podobnego).

Dlatego ludzie używają C - do tworzenia mniej niebezpiecznych narzędzi, z których chcesz korzystać.

Lekkość Wyścigi na orbicie
źródło
2

Pozwól, że przeredaguję twoje pytanie:

Rozważam naukę [narzędzia].

Ale dlaczego ludzie używają [narzędzia] (lub [powiązanego narzędzia]), jeśli [można] używać ich „niebezpiecznie”?

Niebezpiecznie można użyć dowolnego interesującego narzędzia, w tym języków programowania. Państwo dowiedzieć się więcej, dzięki czemu można zrobić więcej (i tak, że mniejsze niebezpieczeństwo jest tworzony podczas korzystania z narzędzia). W szczególności uczysz się tego narzędzia, dzięki czemu możesz robić rzeczy, do których narzędzie jest dobre (i być może rozpoznajesz, kiedy to narzędzie jest najlepszym narzędziem spośród narzędzi, które znasz).

Na przykład, jeśli chcesz umieścić cylindryczny otwór o średnicy 6 mm i głębokości 5 cm w bloku drewna, wiertło jest znacznie lepszym narzędziem niż parser LALR. Jeśli wiesz, jakie są te dwa narzędzia, wiesz, które z nich jest odpowiednie. Jeśli już wiesz, jak korzystać z wiertła, voila !, dziura.

C to tylko kolejne narzędzie. Jest lepszy do niektórych zadań niż do innych. Inne odpowiedzi tutaj rozwiązują ten problem. Jeśli nauczysz się trochę C, zrozumiesz, kiedy jest to właściwe narzędzie, a kiedy nie.

Eric Towers
źródło
Ta odpowiedź jest powodem, dla którego pytania są odrzucane jako „przede wszystkim oparte na opiniach”. Nie mów, że C ma swoje zalety, powiedz, jakie są!
reinierpost
1

Rozważam naukę C.

Nie ma konkretnego powodu, aby nie uczyć się C, ale sugerowałbym C ++. Oferuje wiele z tego, co robi C (ponieważ C ++ jest super zestawem C), z dużą ilością „dodatków”. Nauka języka C przed C ++ jest niepotrzebna - są to właściwie oddzielne języki.

Innymi słowy, gdyby C był zestawem narzędzi do obróbki drewna, prawdopodobnie byłby to:

  • młot
  • paznokcie
  • Piła ręczna
  • wiertło ręczne
  • szlifierka blokowa
  • dłuto (może)

Za pomocą tych narzędzi możesz zbudować wszystko - ale wszystko, co miłe, wymaga dużo czasu i umiejętności.

C ++ to kolekcja elektronarzędzi w lokalnym sklepie z narzędziami.

Jeśli zaczniesz korzystać z podstawowych funkcji językowych, C ++ ma stosunkowo niewielką dodatkową krzywą uczenia się.

Ale dlaczego ludzie używają C (lub C ++), jeśli można go używać „niebezpiecznie”?

Ponieważ niektórzy ludzie nie chcą mebli od IKEA. =)

Poważnie, chociaż w wielu językach, które są „wyższe” niż C lub C ++, mogą występować elementy, które czynią je (potencjalnie) „łatwiejszymi” w niektórych aspektach, nie zawsze jest to dobra rzecz. Jeśli nie podoba ci się sposób, w jaki coś się robi lub funkcja nie jest zapewniona, prawdopodobnie niewiele możesz z tym zrobić. Z drugiej strony C i C ++ zapewniają wystarczająco dużo funkcji języka „niskiego poziomu” (w tym wskaźników), aby można było uzyskać dostęp do wielu rzeczy bezpośrednio (szczególnie sprzętowo lub pod względem systemu operacyjnego) lub zbudować je samodzielnie, co może nie być możliwe w innych języki jak zaimplementowane.

Mówiąc dokładniej, C ma następujący zestaw funkcji, które sprawiają, że jest pożądany dla wielu programistów:

  • Szybkość - Ze względu na względną prostotę i optymalizacje kompilatora na przestrzeni lat jest natywnie bardzo szybki. Ponadto wiele osób wymyśliło wiele skrótów do konkretnych celów podczas korzystania z języka, co sprawia, że ​​jest on potencjalnie jeszcze szybszy.
  • Rozmiar - Z podobnych powodów, jak wymienione dla szybkości, programy C mogą być bardzo małe (zarówno pod względem rozmiaru wykonywalnego, jak i wykorzystania pamięci), co jest pożądane w środowiskach o ograniczonej pamięci (tj. Osadzonych lub mobilnych).
  • Kompatybilność - C istnieje już od dawna i każdy ma do tego narzędzia i biblioteki. Sam język również nie jest wybredny - oczekuje, że procesor wykona instrukcje, a pamięć zapisze rzeczy i tyle.

    Ponadto istnieje coś takiego jak Application Binary Interface (ABI) . Krótko mówiąc, jest to sposób komunikowania się programów na poziomie kodu maszynowego, który może mieć zalety w stosunku do interfejsu programowania aplikacji (API) . Podczas gdy inne języki, takie jak C ++, mogą mieć ABI, zazwyczaj są one mniej jednolite (uzgodnione) niż C, więc C stanowi dobry język podstawowy, gdy z jakiegoś powodu chcesz użyć ABI do komunikacji z innym programem.

Dlaczego programiści nie używają tylko Java, Python lub innego skompilowanego języka, takiego jak Visual Basic?

Wydajność (i czasami schematy zarządzania pamięcią, których nie można wdrożyć bez względnie bezpośredniego dostępu do pamięci).

Bezpośredni dostęp do pamięci za pomocą wskaźników wprowadza wiele schludnych (zwykle szybkich) sztuczek, kiedy możesz umieścić swoje brudne łapy na małych i zerach w twoich komórkach pamięci bezpośrednio i nie musisz czekać, aż ten wredny nauczyciel wręcza zabawki po prostu w czasie zabawy, a następnie ponownie je zgarnij.

Krótko mówiąc, dodawanie rzeczy potencjalnie powoduje opóźnienie lub w inny sposób wprowadza niepożądaną złożoność.

Jeśli chodzi o języki skryptowe i podobne, musisz ciężko pracować, aby języki wymagające programów pomocniczych działały tak skutecznie, jak natywnie C (lub dowolny skompilowany język). Dodanie tłumacza w locie z natury wprowadza możliwość zmniejszenia prędkości wykonywania i zwiększenia zużycia pamięci, ponieważ dodajesz inny program do miksu. Wydajność programów zależy w równym stopniu od wydajności tego dodatkowego programu, jak również od tego, jak dobrze (źle =)) napisałeś oryginalny kod programu. Nie wspominając o tym, że twój program jest często całkowicie zależny od drugiego programu, aby nawet go uruchomić. Ten drugi program z jakiegoś powodu nie istnieje w danym systemie? Kod nie idź.

W rzeczywistości wprowadzenie czegoś „dodatkowego” potencjalnie spowalnia lub komplikuje kod. W językach „bez przerażających wskaźników” zawsze czekasz na inne fragmenty kodu, które wyczyszczą się za tobą lub w inny sposób wymyślisz „bezpieczne” sposoby wykonywania różnych czynności - ponieważ Twój program nadal wykonuje te same operacje dostępu do pamięci, jakie można wykonać za pomocą wskaźniki Po prostu nie jesteś tym, który się tym zajmuje (więc nie możesz tego pieprzyć, geniusz = P).

Przez niebezpieczne rozumiem wskaźniki i inne podobne rzeczy. [...] Jak pytanie o przepełnienie stosu Dlaczego funkcja gets jest tak niebezpieczna, że ​​nie należy jej używać?

Zgodnie z przyjętą odpowiedzią:

„Pozostał oficjalną częścią języka aż do standardu ISO C z 1999 r., Ale został oficjalnie usunięty przez standard z 2011 r. Większość implementacji języka C nadal go obsługuje, ale przynajmniej gcc wydaje ostrzeżenie dla każdego kodu, który go używa”.

Myśl, że ponieważ coś można zrobić w języku, musi być zrobione, jest głupie. Języki mają wady, które można naprawić. Ze względu na kompatybilność ze starszym kodem ta konstrukcja może być nadal używana. Ale nic (prawdopodobnie) nie zmusza programisty do użycia get (), a tak naprawdę to polecenie zostało zasadniczo zastąpione bezpieczniejszymi alternatywami.

Co więcej, problem z get () nie jest sam w sobie problemem ze wskaźnikiem . Jest to problem z poleceniem, które niekoniecznie wie, jak bezpiecznie korzystać z pamięci. W sensie abstrakcyjnym są to wszystkie problemy ze wskaźnikami - czytanie i pisanie rzeczy, których nie powinieneś. To nie jest problem ze wskaźnikami; jest to problem z implementacją wskaźnika.

Aby wyjaśnić, wskaźniki nie są niebezpieczne, dopóki przypadkowo nie uzyskasz dostępu do miejsca w pamięci, do którego nie miałeś zamiaru. I nawet wtedy nie gwarantuje to, że komputer się stopi lub wybuchnie. W większości przypadków twój program po prostu przestanie działać (poprawnie).

To powiedziawszy, ponieważ ponieważ wskaźniki zapewniają dostęp do lokalizacji w pamięci oraz ponieważ dane i kod wykonywalny istnieją razem w pamięci, istnieje realne niebezpieczeństwo przypadkowego uszkodzenia, aby można było właściwie zarządzać pamięcią.

Do tego momentu, ponieważ prawdziwie bezpośrednie operacje dostępu do pamięci często przynoszą ogólnie mniej korzyści, niż mogły to być lata temu, nawet języki nieskradzione, takie jak C ++, wprowadziły takie elementy, jak inteligentne wskaźniki, które pomagają wypełnić lukę między wydajnością pamięci a bezpieczeństwem.

Podsumowując, nie ma powodu, aby obawiać się wskaźnika, o ile jest on bezpiecznie używany. Wystarczy skorzystać z podpowiedzi z wersji Steve'a „The Crocodile Hunter” Irwina z South Park - nie chowaj kciuka w otworach krokodyli .

Anaksunaman
źródło
2
Nie zgadzam się z sugestią uczenia się C ++ zamiast C. Pisanie dobrego C ++ jest trudniejsze niż pisanie dobrego C, a czytanie C ++ jest znacznie trudniejsze niż czytanie C. Więc krzywa uczenia się C ++ jest znacznie bardziej stroma. „C ++ to super zestaw C” To mniej więcej przypomina powiedzenie, że buty to superset kapci. Mają różne zalety i zastosowanie, a każda z nich ma funkcje, których nie ma druga.
martinkunev
„Pisanie dobrego C ++ jest trudniejsze niż pisanie dobrego C” - Oczywiście. =) „Odczytywanie C ++ jest znacznie trudniejsze niż czytanie C” - Wszelkie zaawansowane programowanie jest prawdopodobnie nieodróżnialne od magii ;-) Moje dwa centy to to, że jest to o wiele bardziej zależne od programisty niż od języka, chociaż C ++ nie robi nic, by sobie pomóc w tej kategorii. „Zatem krzywa uczenia się w C ++ jest znacznie bardziej stroma”. - Na dłuższą metę tak. W krótkim okresie mniej tak (moja opinia). Anegdotycznie większość podstawowych kursów językowych w C i C ++ prawdopodobnie obejmuje w przybliżeniu te same ogólne typy materiałów, z wyjątkiem zajęć z C ++.
Anaksunaman
2
„Mają różne zalety i zastosowanie, a każda z nich ma funkcje, których nie ma druga”. - Jak wspomniano „Nie ma konkretnego powodu, aby nie uczyć się języka C [.]” C jest dobrym językiem i trzymam się tego. Jeśli pasuje OP lub komukolwiek innemu, w pełni popieram naukę. =)
Anaksunaman,
Uczenie się C uczy, jak działa maszyna (nie do końca, ale wciąż o krok bliżej metalu). To bardzo dobry powód, aby się tego nauczyć.
Agent_L,
1

Jak zawsze język programowania jest jedynie konsekwencją rozwiązywania problemów. Powinieneś nauczyć się nie tylko języka C, ale wielu różnych języków (i innych sposobów programowania komputera, czy to narzędzi GUI, czy interpreterów poleceń), aby mieć przyzwoity zestaw narzędzi do rozwiązywania problemów.

Czasami okaże się, że problem dobrze pasuje do czegoś, co jest zawarte w domyślnych bibliotekach Java, w takim przypadku możesz wybrać Javę, aby to wykorzystać. W innych przypadkach może być konieczne zrobienie czegoś w systemie Windows, co jest dużo prostsze w środowisku uruchomieniowym .NET, więc możesz użyć C # lub VB. Może istnieć narzędzie graficzne lub skrypt poleceń, który rozwiązuje twój problem, wtedy możesz ich użyć. Być może musisz napisać aplikację GUI na wielu platformach, Java może być opcją, biorąc pod uwagę biblioteki zawarte w JDK, ale z drugiej strony jedna platforma docelowa może nie mieć JRE, więc może zamiast tego wybierzesz C i SDL (lub podobne).

C ma ważną pozycję w tym zestawie narzędzi, ponieważ jest ogólny, mały i szybki, a także kompiluje się do kodu maszynowego. Obsługiwany jest również na każdej platformie pod słońcem (jednak nie bez ponownej kompilacji).

Podsumowując, powinieneś nauczyć się jak największej liczby narzędzi, języków i paradygmatów.

Proszę oderwać się od sposobu myślenia: „Jestem programistą X” (X = C, C ++, Java itp.)

Wystarczy użyć „Jestem programistą”.

Programista rozwiązuje problemy i projektuje algorytmy, instruując maszyny, aby wykonały obciążenie pracą. Koniec opowieści. Nie ma to znaczenia dla języka. Najważniejszą umiejętnością jest rozwiązywanie problemów i logiczne rozkładanie problemów strukturalnych, umiejętność / wybór języka ZAWSZE jest drugorzędny i / lub jest konsekwencją charakteru problemu.

Interesującą ścieżką, jeśli interesuje Cię C, jest rozszerzenie zestawu umiejętności o Go. Go jest naprawdę ulepszonym C, z odśmiecaniem i interfejsami, a także ładnym wbudowanym modelem / kanałami wątków, które również przynoszą wiele korzyści C (takich jak arytmetyka wskaźników i kompilacja do kodu maszynowego).

Richard Tyregrim
źródło
0

To zależy od tego, co zamierzasz z tym zrobić. C został zaprojektowany jako zamiennik języka asemblera i jest językiem wysokiego poziomu, który jest najbliższy językowi maszynowemu. W związku z tym ma niskie koszty ogólne i wydajność i nadaje się do programowania systemów i innych zadań, które wymagają niewielkiej powierzchni i zbliżania się do podstawowego sprzętu.

Jonathan Rosenne
źródło
0

Kiedy pracujesz na poziomie bitów i bajtów, pamięci jako surowego jednorodnego zbioru danych, co często byłoby wymagane do skutecznego wdrożenia najbardziej wydajnych alokatorów i struktur danych, nie ma bezpieczeństwa. Bezpieczeństwo to przede wszystkim silna koncepcja związana z typem danych, a alokator pamięci nie działa z typami danych. Działa z bitami i bajtami, aby połączyć je z tymi samymi bitami i bajtami potencjalnie reprezentującymi jeden typ danych w danym momencie i innym później.

W tym przypadku nie ma znaczenia, czy użyjesz C ++. Nadal będziesz krążył static_castspo całym kodzie, aby rzucać ze void*wskaźników, nadal pracując z bitami i bajtami i po prostu radząc sobie z większymi problemami związanymi z przestrzeganiem systemu typów w tym kontekście niż C, który ma znacznie prostszy system typów, w którym jesteś wolny do memcpybitów i bajtów, nie martwiąc się o buldożerowanie nad systemem typów.

W rzeczywistości często trudniej jest pracować w C ++, ogólnie bezpieczniejszym języku, w tak niskim kontekście bitów i bajtów bez pisania nawet bardziej niebezpiecznego kodu niż w C, ponieważ możesz buldożować się nad systemem typów C ++ i robić rzeczy takie jak nadpisywanie vptrs i nie wywoływanie konstruktorów kopiowania i destruktorów w odpowiednim czasie. Jeśli poświęcisz odpowiedni czas, aby uszanować te typy i zastosujesz nowe i ręcznie przywołujesz lekarzy, i tak dalej, będziesz narażony na świat obsługi wyjątków w kontekście zbyt niskim, aby RAII był praktyczny, i osiągnięcie wyjątku - bezpieczeństwo w kontekście niskiego poziomu jest bardzo trudne (musisz udawać, że prawie każda funkcja może rzucić i złapać wszystkie możliwości i wycofać wszelkie efekty uboczne jako niepodzielna transakcja, jakby nic się nie wydarzyło). Kod C często „

Niemożliwe byłoby wdrożenie takich alokatorów w językach, które nie pozwolą ci stać się „niebezpiecznym” tutaj; będziesz musiał polegać na wszystkich udostępnianych przez siebie alokatorach (najprawdopodobniej zaimplementowanych w C lub C ++) i mieć nadzieję, że będzie wystarczająco dobry dla twoich celów. I prawie zawsze istnieją bardziej wydajne, ale mniej ogólne alokatory i struktury danych odpowiednie do konkretnych celów, ale o wiele bardziej wąskie, ponieważ są one specjalnie dostosowane do twoich celów.

Większość ludzi nie potrzebuje takich jak C lub C ++, ponieważ mogą po prostu wywołać kod pierwotnie zaimplementowany w C lub C ++ lub ewentualnie nawet zaimplementowany już dla nich zestaw. Wielu może odnieść korzyści z innowacji na wysokim poziomie, takich jak łączenie ze sobą programu graficznego, który po prostu wykorzystuje biblioteki istniejących funkcji przetwarzania obrazu już zaimplementowanych w C, gdzie nie wprowadzają tak dużych innowacji na najniższym poziomie zapętlania poszczególnych pikseli, ale może oferując bardzo przyjazny interfejs użytkownika i przepływ pracy, jakiego nigdy wcześniej nie widziano. W takim przypadku, jeśli celem oprogramowania jest po prostu wywoływanie wywołań wysokiego poziomu w bibliotekach niskiego poziomu ( „przetwarzaj dla mnie cały obraz, a nie dla każdego piksela, zrób coś” ), może to być prawdopodobnie przedwczesna optymalizacja nawet próby rozpoczęcia pisania takiej aplikacji w C.

Ale jeśli robisz coś nowego na niskim poziomie, gdzie pomaga on uzyskać dostęp do danych w sposób niskopoziomowy, jak zupełnie nowy filtr obrazu, którego nigdy wcześniej nie widziałem, który jest wystarczająco szybki, aby pracować na wideo HD w czasie rzeczywistym, zazwyczaj musisz uzyskać trochę niebezpieczne.

Łatwo jest uznać to za coś oczywistego. Pamiętam post na Facebooku, w którym ktoś wskazał, jak możliwe jest stworzenie gry wideo w języku Python z sugestią, że języki niskiego poziomu stają się przestarzałe, a na pewno była to gra przyzwoicie wyglądająca. Ale Python robił wywołania wysokiego poziomu do bibliotek zaimplementowanych w C, aby wykonać całą ciężką pracę. Nie możesz tworzyć Unreal Engine 4, po prostu wykonując wywołania wysokiego poziomu w istniejących bibliotekach. Unreal Engine 4 jestBiblioteka. Robił wiele rzeczy, które nigdy nie istniały w innych bibliotekach i silnikach, od oświetlenia po nawet system węzłów, i jak mógł kompilować i uruchamiać kod w locie. Jeśli chcesz wprowadzać innowacje na niskim poziomie silnika / rdzenia / jądra, musisz uzyskać niski poziom. Gdyby wszyscy twórcy gier zmienili się na bezpieczne języki wysokiego poziomu, nie byłoby Unreal Engine 5, 6 lub 7. Prawdopodobnie ludzie nadal używaliby Unreal Engine 4 dekady później, ponieważ nie można wprowadzać innowacji na poziomie wymaganym do przyjścia dzięki silnikowi nowej generacji, wykonując połączenia na wysokim poziomie w starym.


źródło