După toți acești ani, lumea este încă alimentată de programarea C
Publicat: 2022-03-11Multe dintre proiectele C care există astăzi au fost începute cu zeci de ani în urmă.
Dezvoltarea sistemului de operare UNIX a început în 1969, iar codul său a fost rescris în C în 1972. Limbajul C a fost de fapt creat pentru a muta codul nucleului UNIX de la asamblare la un limbaj de nivel superior, care ar face aceleași sarcini cu mai puține linii de cod. .
Dezvoltarea bazei de date Oracle a început în 1977, iar codul său a fost rescris din asamblare în C în 1983. A devenit una dintre cele mai populare baze de date din lume.
În 1985 a fost lansat Windows 1.0. Deși codul sursă Windows nu este disponibil public, s-a afirmat că nucleul său este scris în mare parte în C, cu unele părți în asamblare. Dezvoltarea nucleului Linux a început în 1991 și este scris și în C. Anul următor, a fost lansat sub licență GNU și a fost folosit ca parte a sistemului de operare GNU. Sistemul de operare GNU însuși a fost început folosind limbaje de programare C și Lisp, așa că multe dintre componentele sale sunt scrise în C.
Dar programarea C nu se limitează la proiecte care au început cu zeci de ani în urmă, când nu existau atât de multe limbaje de programare ca astăzi. Multe proiecte C sunt încă demarate astăzi; există câteva motive întemeiate pentru asta.
Cum este lumea alimentată de C?
În ciuda prevalenței limbilor de nivel superior, C continuă să dea putere lumii. Următoarele sunt câteva dintre sistemele care sunt folosite de milioane și sunt programate în limbajul C.
Microsoft Windows
Nucleul Microsoft Windows este dezvoltat în mare parte în C, cu unele părți în limbaj de asamblare. De zeci de ani, cel mai folosit sistem de operare din lume, cu aproximativ 90% din cota de piață, a fost alimentat de un nucleu scris în C.
Linux
Linux este, de asemenea, scris mai ales în C, cu unele părți în asamblare. Aproximativ 97% dintre cele mai puternice 500 de supercalculatoare din lume rulează nucleul Linux. Este, de asemenea, folosit în multe computere personale.
Mac
Calculatoarele Mac sunt, de asemenea, alimentate de C, deoarece nucleul OS X este scris în cea mai mare parte în C. Fiecare program și driver dintr-un Mac, ca și în computerele Windows și Linux, rulează pe un nucleu C-powered.
Mobil
Nucleele iOS, Android și Windows Phone sunt, de asemenea, scrise în C. Sunt doar adaptări mobile ale nucleelor Mac OS, Linux și Windows existente. Deci, smartphone-urile pe care le folosiți în fiecare zi rulează pe un nucleu C.
Baze de date
Cele mai populare baze de date din lume, inclusiv Oracle Database, MySQL, MS SQL Server și PostgreSQL, sunt codificate în C (primele trei dintre ele, de fapt, ambele în C și C++).
Bazele de date sunt utilizate în toate tipurile de sisteme: financiare, guvernamentale, media, divertisment, telecomunicații, sănătate, educație, retail, rețele sociale, web și altele asemenea.
Filme 3D
Filmele 3D sunt create cu aplicații care sunt în general scrise în C și C++. Acele aplicații trebuie să fie foarte eficiente și rapide, deoarece gestionează o cantitate imensă de date și fac multe calcule pe secundă. Cu cât sunt mai eficienți, cu atât le ia mai puțin timp artiștilor și animatorilor pentru a genera filmele și cu atât compania economisește mai mulți bani.
Sisteme integrate
Imaginează-ți că te trezești într-o zi și mergi la cumpărături. Ceasul cu alarmă care te trezește este probabil programat în C. Apoi folosești cuptorul cu microunde sau aparatul de cafea pentru a-ți pregăti micul dejun. Sunt, de asemenea, sisteme încorporate și, prin urmare, sunt probabil programate în C. Porniți televizorul sau radioul în timp ce mâncați micul dejun. Acestea sunt, de asemenea, sisteme încorporate, alimentate de C. Când deschideți ușa garajului cu telecomanda, utilizați și un sistem încorporat care este cel mai probabil programat în C.
Apoi te urci în mașina ta. Dacă are următoarele caracteristici, programate și în C:
- transmisie automată
- sisteme de detectare a presiunii în anvelope
- senzori (oxigen, temperatură, nivel ulei etc.)
- memorie pentru setarile scaunelor si oglinzilor.
- afișajul tabloului de bord
- frane anti-blocare
- control automat al stabilității
- control de croazieră
- Control climatic
- încuietori pentru copii
- intrare fără cheie
- scaune incalzite
- control airbag
Ajungi la magazin, parchezi mașina și mergi la un automat pentru a obține un suc. Ce limbaj au folosit pentru a programa acest automat? Probabil C. Atunci cumperi ceva de la magazin. Casa de marcat este programata si in C. Si cand platiti cu cardul de credit? Ai ghicit: cititorul de carduri de credit este, din nou, probabil programat în C.
Toate aceste dispozitive sunt sisteme încorporate. Sunt ca computerele mici care au în interior un microcontroler/microprocesor care rulează un program, numit și firmware, pe dispozitive încorporate. Programul respectiv trebuie să detecteze apăsările de taste și să acționeze în consecință și, de asemenea, să afișeze informații utilizatorului. De exemplu, ceasul deșteptător trebuie să interacționeze cu utilizatorul, detectând ce buton apasă utilizatorul și, uneori, cât timp este apăsat și să programeze dispozitivul în consecință, totul în timp ce afișează utilizatorului informațiile relevante. Sistemul de frânare antiblocare al mașinii, de exemplu, trebuie să fie capabil să detecteze blocarea bruscă a anvelopelor și să acționeze pentru a elibera presiunea asupra frânelor pentru o perioadă scurtă de timp, deblocându-le și prevenind astfel derapajele necontrolate. Toate aceste calcule sunt efectuate de un sistem încorporat programat.
Deși limbajul de programare utilizat pe sistemele încorporate poate varia de la o marcă la alta, acestea sunt cel mai frecvent programate în limbajul C, datorită caracteristicilor limbajului de flexibilitate, eficiență, performanță și apropiere de hardware.
De ce este încă folosit limbajul de programare C?
Există multe limbaje de programare, astăzi, care permit dezvoltatorilor să fie mai productivi decât cu C pentru diferite tipuri de proiecte. Există limbaje de nivel superior care oferă biblioteci integrate mult mai mari care simplifică lucrul cu JSON, XML, UI, pagini web, solicitări ale clienților, conexiuni la baze de date, manipulare media și așa mai departe.
Dar, în ciuda acestui fapt, există o mulțime de motive pentru a crede că programarea C va rămâne activă mult timp.
În limbajele de programare, o singură dimensiune nu se potrivește tuturor. Iată câteva motive pentru care C este imbatabil și aproape obligatoriu pentru anumite aplicații.
Portabilitate și eficiență
C este aproape un limbaj de asamblare portabil . Este cât mai aproape posibil de mașină, în timp ce este aproape universal disponibil pentru arhitecturile de procesoare existente. Există cel puțin un compilator C pentru aproape fiecare arhitectură existentă. Și în zilele noastre, din cauza binarelor foarte optimizate generate de compilatoarele moderne, nu este o sarcină ușoară să le îmbunătățim rezultatul cu asamblarea scrisă manual.
Așa este portabilitatea și eficiența sa, încât „compilatoarele, bibliotecile și interpreții altor limbaje de programare sunt adesea implementate în C”. Limbajele interpretate precum Python, Ruby și PHP au implementările lor principale scrise în C. Este folosit chiar de compilatori pentru alte limbi pentru a comunica cu mașina. De exemplu, C este limbajul intermediar care stă la baza Eiffel și Forth. Aceasta înseamnă că, în loc să genereze cod de mașină pentru fiecare arhitectură care urmează să fie suportată, compilatoarele pentru acele limbi doar generează cod C intermediar, iar compilatorul C se ocupă de generarea codului de mașină.
C a devenit, de asemenea, o lingua franca pentru comunicarea între dezvoltatori. După cum spune Alex Allain, manager de inginerie Dropbox și creatorul Cprogramming.com:
C este un limbaj excelent pentru exprimarea ideilor comune în programare într-un mod în care majoritatea oamenilor se simt confortabil. Mai mult decât atât, multe dintre principiile folosite în C – de exemplu,
argc
șiargv
pentru parametrii liniei de comandă, precum și constructele bucle și tipurile de variabile – vor apărea în multe alte limbi pe care le înveți, astfel încât să poți vorbi oamenilor chiar dacă nu cunosc C într-un mod care este comun pentru amândoi.
Manipularea memoriei
Accesul arbitrar la adresa de memorie și aritmetica pointerului este o caracteristică importantă care face ca C să se potrivească perfect pentru programarea sistemului (sisteme de operare și sisteme încorporate).
La limita hardware/software, sistemele computerizate și microcontrolerele își mapează perifericele și pinii I/O în adrese de memorie. Aplicațiile de sistem trebuie să citească și să scrie în acele locații de memorie personalizate pentru a comunica cu lumea. Deci, capacitatea lui C de a manipula adrese de memorie arbitrare este imperativă pentru programarea sistemului.
Un microcontroler ar putea fi proiectat, de exemplu, astfel încât octetul din adresa de memorie 0x40008000 să fie trimis de receptorul/transmițătorul asincron universal (sau UART, o componentă hardware comună pentru comunicarea cu periferice) de fiecare dată când bitul numărul 4 al adresei 0x40008001 este setat la 1 și că după ce setați acel bit, acesta va fi dezactivat automat de periferic.
Acesta ar fi codul pentru o funcție C care trimite un octet prin acel 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 }
Prima linie a funcției va fi extinsă la:
*(char *)0x40008000 = byte;
Această linie îi spune compilatorului să interpreteze valoarea 0x40008000
ca un pointer către un char
, apoi să derefere (dați valoarea indicată de) acel pointer (cu operatorul din stânga *
) și în final să atribuie o valoare de byte
acelui pointer dereferit. Cu alte cuvinte: scrieți valoarea byte
variabil la adresa de memorie 0x40008000
.

Următoarea linie va fi extinsă la:
*(volatile char *)0x40008001 |= 0x08;
În această linie, efectuăm o operație SAU pe biți asupra valorii de la adresa 0x40008001
și a valorii 0x08
( 00001000
în binar, adică un 1 în bitul numărul 4) și salvăm rezultatul înapoi la adresa 0x40008001
. Cu alte cuvinte: setăm bitul 4 al octetului care se află la adresa 0x40008001. De asemenea, declarăm că valoarea la adresa 0x40008001
este volatilă . Aceasta îi spune compilatorului că această valoare poate fi modificată de procese externe codului nostru, astfel încât compilatorul nu va face nicio presupunere cu privire la valoarea din acea adresă după ce i-a scris. (În acest caz, acest bit este dezactivat de hardware-ul UART imediat după ce îl setăm de software.) Aceste informații sunt importante pentru optimizatorul compilatorului. Dacă am făcut acest lucru în interiorul unei bucle for
, de exemplu, fără a specifica faptul că valoarea este volatilă, compilatorul ar putea presupune că această valoare nu se schimbă niciodată după ce a fost setată și să omite executarea comenzii după prima buclă.
Utilizarea deterministă a resurselor
O caracteristică comună a limbajului pe care programarea sistemului nu se poate baza este colectarea gunoiului sau chiar doar alocarea dinamică pentru unele sisteme încorporate. Aplicațiile încorporate sunt foarte limitate în timp și resurse de memorie. Ele sunt adesea folosite pentru sisteme în timp real, unde nu se poate permite un apel nedeterminist către colectorul de gunoi. Și dacă alocarea dinamică nu poate fi utilizată din cauza lipsei de memorie, este foarte important să existe și alte mecanisme de gestionare a memoriei, cum ar fi plasarea datelor în adrese personalizate, așa cum permit pointerii C. Limbile care depind în mare măsură de alocarea dinamică și colectarea gunoiului nu ar fi potrivite pentru sistemele cu resurse limitate.
Dimensiunea codului
C are o durată de rulare foarte mică. Și amprenta de memorie pentru codul său este mai mică decât pentru majoritatea celorlalte limbi.
În comparație cu C++, de exemplu, un binar generat de C care merge la un dispozitiv încorporat are aproximativ jumătate din dimensiunea unui binar generat de cod C++ similar. Una dintre principalele cauze pentru aceasta este suportul pentru excepții.
Excepțiile sunt un instrument grozav adăugat de C++ peste C și, dacă nu sunt declanșate și implementate inteligent, practic nu au timp de execuție (dar cu prețul creșterii dimensiunii codului).
Să vedem un exemplu în 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)
Metodele claselor A
, B
și C
sunt definite în altă parte (de exemplu, în alte fișiere). Prin urmare, compilatorul nu le poate analiza și nu poate ști dacă vor arunca excepții. Deci trebuie să se pregătească să gestioneze excepțiile aruncate de la oricare dintre constructorii, destructorii sau alte apeluri de metodă. Destructorii nu ar trebui să arunce (foarte proastă practică), dar utilizatorul ar putea arunca oricum, sau ar putea arunca indirect apelând vreo funcție sau metodă (explicit sau implicit) care aruncă o excepție.
Dacă oricare dintre apelurile din myFunction
aruncă o excepție, mecanismul de derulare a stivei trebuie să poată apela toți destructorii pentru obiectele care au fost deja construite. O implementare pentru mecanismul de derulare a stivei va folosi adresa de retur a ultimului apel din această funcție pentru a verifica „numărul punctului de control” al apelului care a declanșat excepția (aceasta este explicația simplă). Face acest lucru utilizând o funcție auxiliară autogenerată (un fel de tabel de căutare) care va fi folosită pentru derularea stivei în cazul în care se aruncă o excepție din corpul acelei funcții, care va fi similară cu aceasta:
// 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(); } }
Dacă excepția este aruncată de la punctele de control 1 și 9, niciun obiect nu are nevoie de distrugere. Pentru punctul de control 3, b
și a
trebuie distruse. Pentru punctul de control 6, c
și a
trebuie distruse. În toate cazurile, ordinul de distrugere trebuie respectat. Pentru punctele de control 2, 4, 5, 7 și 8, numai obiectul a
trebuie distrus.
Această funcție auxiliară adaugă dimensiune codului. Aceasta face parte din suprasarcina de spațiu pe care C++ o adaugă la C. Multe aplicații încorporate nu își pot permite acest spațiu suplimentar. Prin urmare, compilatoarele C++ pentru sistemele încorporate au adesea un indicator pentru a dezactiva excepțiile. Dezactivarea excepțiilor în C++ nu este gratuită, deoarece Biblioteca de șabloane standard se bazează în mare măsură pe excepții pentru a informa erorile. Utilizarea acestei scheme modificate, fără excepții, necesită mai multă pregătire pentru dezvoltatorii C++ pentru a detecta posibile probleme sau a găsi erori.
Și, vorbim despre C++, un limbaj al cărui principiu este: „Nu plătești pentru ceea ce nu folosești”. Această creștere a dimensiunii binarului se înrăutățește pentru alte limbi care adaugă supraîncărcare suplimentară cu alte caracteristici care sunt foarte utile, dar nu pot fi permise de sistemele încorporate. Deși C nu vă oferă utilizarea acestor caracteristici suplimentare, permite o amprentă de cod mult mai compactă decât celelalte limbi.
Motive pentru a învăța C
C nu este o limbă greu de învățat, așa că toate beneficiile învățării acesteia vor fi destul de ieftine. Să vedem câteva dintre aceste beneficii.
Lingua Franca
După cum am menționat deja, C este o lingua franca pentru dezvoltatori. Multe implementări ale algoritmilor noi în cărți sau pe internet sunt mai întâi (sau numai) puse la dispoziție în C de către autorii lor. Acest lucru oferă portabilitate maximă posibilă pentru implementare. Am văzut programatori care se luptă pe internet să rescrie un algoritm C în alte limbaje de programare, deoarece el sau ea nu cunoșteau conceptele de bază despre C.
Fiți conștienți de faptul că C este un limbaj vechi și răspândit, așa că puteți găsi tot felul de algoritmi scrisi în C pe web. Prin urmare, foarte probabil veți beneficia de cunoașterea acestei limbi.
Înțelegeți mașina (Gândiți în C)
Când discutăm despre comportamentul anumitor porțiuni de cod sau anumite caracteristici ale altor limbaje, cu colegii, ajungem să „vorbim în C:” Această porțiune transmite un „pointer” la obiect sau copiază întregul obiect? S-ar putea întâmpla vreo „distribuție” aici? Și așa mai departe.
Rareori am discuta (sau ne-am gândi) despre instrucțiunile de asamblare pe care o porțiune de cod le execută atunci când analizăm comportamentul unei porțiuni de cod dintr-un limbaj de nivel înalt. În schimb, când discutăm despre ceea ce face mașina, vorbim (sau gândim) destul de clar în C.
Mai mult, dacă nu poți să te oprești și să te gândești așa la ceea ce faci, s-ar putea să ajungi să programezi cu un fel de superstiție despre modul în care (magic) se fac lucrurile.
Lucrați la multe proiecte C interesante
Multe proiecte interesante, de la servere mari de baze de date sau nuclee de sisteme de operare, la aplicații mici încorporate pe care le poți face chiar acasă pentru satisfacția și distracția ta personală, sunt realizate în C. Nu există niciun motiv să încetezi să faci lucruri pe care le-ar putea plăcea din singurul motiv. că nu cunoașteți un limbaj de programare vechi și mic, dar puternic și dovedit în timp, precum C.
Concluzie
Limbajul de programare C nu pare să aibă o dată de expirare. Apropierea de hardware, portabilitatea excelentă și utilizarea deterministă a resurselor îl fac ideal pentru dezvoltarea la nivel scăzut pentru lucruri precum nucleele sistemului de operare și software-ul încorporat. Versatilitatea, eficiența și performanțele sale bune îl fac o alegere excelentă pentru software-ul de manipulare a datelor de mare complexitate, cum ar fi bazele de date sau animația 3D. Faptul că multe limbaje de programare din ziua de azi sunt mai bune decât C pentru utilizarea lor, nu înseamnă că ele depășesc C în toate domeniile. C este încă de neîntrecut atunci când performanța este prioritatea.
Lumea rulează pe dispozitive C-powered. Folosim aceste dispozitive în fiecare zi, indiferent dacă ne dăm seama sau nu. C este trecutul, prezentul și, din câte putem vedea, încă viitorul pentru multe domenii ale software-ului.