C (programozási nyelv)

Az oldal jelenlegi verzióját még nem ellenőrizték tapasztalt közreműködők, és jelentősen eltérhet a 2022. augusztus 4-én felülvizsgált verziótól ; az ellenőrzések 3 szerkesztést igényelnek .
C
Nyelvóra eljárási
A végrehajtás típusa összeállított
Megjelent 1972
Szerző Dennis Ritchie
Fejlesztő Bell Labs , Dennis Ritchie [1] , US National Standards Institute , ISO és Ken Thompson
Fájlkiterjesztés _ .c— kódfájlokhoz, .h— fejlécfájlokhoz
Kiadás ISO/IEC 9899:2018 (2018. július 5. )
Típusrendszer statikus gyenge
Főbb megvalósítások GCC , Clang , TCC , Turbo C , Watcom , Oracle Solaris Studio C, Pelles C
Dialektusok "K&R" C ( 1978 )
ANSI C ( 1989 )
C99 ( 1999 )
C11 ( 2011 )
Befolyásolva BCPL , B
befolyásolta C++ , Objective-C , C# , Java , Nim
OS Microsoft Windows és Unix-szerű operációs rendszer
 Médiafájlok a Wikimedia Commons oldalon
ISO/IEC 9899
Információtechnológia — Programozási nyelvek — C
Kiadó Nemzetközi Szabványügyi Szervezet (ISO)
Weboldal www.iso.org
Bizottság (fejlesztő) ISO/IEC JTC 1/SC 22
bizottság honlapja Programozási nyelvek, környezetük és rendszerszoftver interfészek
ISS (ICS) 35.060
Aktuális kiadás ISO/IEC 9899:2018
Korábbi kiadások ISO/IEC 9899:1990/COR2:1996
ISO/IEC 9899:1999/COR3:2007
ISO/IEC 9899:2011/COR1:2012

A C (a latin C betűből , angol nyelv ) egy általános célú lefordított, statikusan tipizált programozási nyelv , amelyet 1969-1973-ban a Bell Labs alkalmazottja, Dennis Ritchie fejlesztett ki a Bee nyelv továbbfejlesztéseként . Eredetileg a UNIX operációs rendszer megvalósítására fejlesztették ki , de azóta sok más platformra is portolták . Tervezésénél fogva a nyelv szorosan illeszkedik a tipikus gépi utasításokhoz , és olyan projektekben is használható, amelyek az assembly nyelven natívak voltak , beleértve az operációs rendszereket és a különféle alkalmazásszoftvereket különféle eszközökhöz, a szuperszámítógépektől a beágyazott rendszerekig . A C programozási nyelv jelentős hatással volt a szoftveripar fejlődésére, szintaxisa olyan programozási nyelvek alapjává vált, mint a C++ , C# , Java és Objective-C .

Történelem

A C programozási nyelvet 1969 és 1973 között fejlesztette ki a Bell Labs , és 1973 -ra az eredetileg PDP-11 /20 assemblerben írt UNIX kernel nagy részét átírták erre a nyelvre. A nyelv elnevezése a régi „ Bi[a] nyelv logikai folytatása lett , melynek számos jellemzőjét vették alapul.

A nyelv fejlődésével először ANSI C néven szabványosították, majd ezt a szabványt az ISO nemzetközi szabványügyi bizottság ISO C néven, más néven C90 néven vette át. A C99 szabvány új funkciókkal bővítette a nyelvet, mint például változó hosszúságú tömbök és soron belüli függvények. A C11 szabványban pedig a streamek megvalósítása és az atomtípusok támogatása került a nyelvbe. Azóta azonban a nyelv lassan fejlődött, és csak a C11 szabvány hibajavításai kerültek be a C18 szabványba.

Általános információk

A C nyelvet rendszerprogramozási nyelvnek tervezték, amelyhez egymenetes fordító is létrehozható . A standard könyvtár is kicsi. Ezen tényezők következtében a fordítók viszonylag könnyen fejleszthetők [2] . Ezért ez a nyelv számos platformon elérhető. Ráadásul alacsony szintű jellege ellenére a nyelv a hordozhatóságra összpontosít. A nyelvi szabványnak megfelelő programok különböző számítógép-architektúrákhoz fordíthatók.

A nyelv célja az volt, hogy egyszerűbbé tegye a nagy programok minimális hibával történő írását az assemblerhez képest, a procedurális programozás elveit követve , de elkerülve mindazt, ami a magas szintű nyelvekre jellemző többletköltséget jelentene.

A C főbb jellemzői:

Ugyanakkor C-ből hiányzik:

A hiányzó funkciók egy része beépített eszközökkel szimulálható (például a korutinok szimulálhatók a setjmpéslongjmp függvényekkel ), néhány pedig harmadik féltől származó könyvtárak segítségével adható hozzá (például a többfeladatos és hálózati funkciók támogatásához használhatja a könyvtárak pthreads , sockets és hasonlók; vannak olyan könyvtárak, amelyek támogatják az automatikus szemétgyűjtést [3] ), egyes fordítóprogramok egy részét nyelvi kiterjesztésként implementálják (például beágyazott függvények a GCC -ben ). Létezik egy kissé körülményes, de meglehetősen működő technika, amely lehetővé teszi az OOP mechanizmusok megvalósítását C-ben [4] , amely a C-beli mutatók tényleges polimorfizmusán és a függvényekre mutató mutatók támogatásán alapul ezen a nyelven. Az ezen a modellen alapuló OOP-mechanizmusok a GLib könyvtárban vannak implementálva, és aktívan használatosak a GTK+ keretrendszerben . A GLib egy alaposztályt biztosít GObject, amely egyetlen osztályból örököl [5] és több interfészt [6] valósít meg .

A nyelv bevezetésekor kedvező fogadtatásra talált, mert lehetővé tette az új platformokhoz való fordítóprogramok gyors létrehozását , és lehetővé tette a programozók számára, hogy meglehetősen pontosak legyenek programjaik végrehajtásában. Az alacsony szintű nyelvekhez való közelsége miatt a C programok hatékonyabban futottak, mint sok más magas szintű nyelven írottak, és csak a kézzel optimalizált assembly nyelvi kód futhatott még gyorsabban, mert teljes irányítást adott a gép felett. A fordítóprogramok fejlődése és a processzorok bonyolítása a mai napig oda vezetett, hogy a kézzel írt assembly kódnak (talán nagyon rövid programok kivételével) gyakorlatilag nincs előnye a fordító által generált kóddal szemben, míg a C továbbra is az egyik legjobb. hatékony magas szintű nyelvek.

Szintaxis és szemantika

Tokenek

Nyelvi ábécé

A nyelv a latin ábécé összes karakterét használja , számokat és néhány speciális karaktert [7] .

Az ábécé összetétele [7]
Latin ábécé karakterei

A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, , , W_ X_ Y_ Z
a_ b_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _cdefghijklmnopqrstuvwxyz

Számok 0, 1, 2, 3, 4, 5, 6, 7, 8,9
Különleges szimbólumok , (vessző) , ;, . (pont) , +, -, *, ^, (és & ) , =, ~ (tilde) , !, /, <, >, (, ), {, }, [, ], |, %, (aposztróf)? , (idézőjelek) , (kettőspont) , ( aláhúzás ) ) , ,' " : _ \#

A tokenek érvényes karakterekből -  előre definiált konstansokból , azonosítókból és műveleti jelekből - jönnek létre . A lexémák viszont a kifejezések részét képezik ; az utasítások és az operátorok pedig kifejezésekből épülnek fel .

Amikor egy programot C nyelvre fordítunk, a programkódból kivonják a maximális hosszúságú, érvényes karaktereket tartalmazó lexémákat. Ha egy program érvénytelen karaktert tartalmaz, akkor a lexikális elemző (vagy fordító) hibát generál, és a program fordítása lehetetlen lesz.

A szimbólum #nem lehet semmilyen token része, és a előfeldolgozóban használatos .

Azonosítók

Az érvényes azonosító  olyan szó, amely tartalmazhat latin karaktereket, számokat és aláhúzásjeleket [8] . Azonosítókat az operátorok, konstansok, változók, típusok és függvények kapják.

A kulcsszóazonosítók és a beépített azonosítók nem használhatók programobjektum azonosítóként. Vannak fenntartott azonosítók is, amelyeknél a fordító nem ad hibát, de amelyek a jövőben kulcsszavakká válhatnak, ami összeférhetetlenséghez vezet.

Csak egy beépített azonosító van - __func__, amely egy konstans karakterláncként van definiálva, amely implicit módon deklarált minden függvényben, és tartalmazza a nevét [8] .

Literális állandók

A C-ben speciálisan formázott literálokat konstansoknak nevezzük. A szó szerinti állandók lehetnek egészek, valós számok, karakterek [9] és karakterláncok [10] .

Az egész számok alapértelmezés szerint decimálisan vannak megadva . Ha egy előtag van megadva 0x, akkor hexadecimális formában van megadva . A 0 előtag azt jelzi, hogy a szám oktálisan értendő . Az utótag megadja az állandó típus minimális méretét, és azt is meghatározza, hogy a szám előjeles vagy előjel nélküli. A végső típust a lehető legkisebbnek vesszük, amelyben az adott konstans ábrázolható [11] .

Az adattípusok egész konstansokhoz való hozzárendelésének sorrendje értékük szerint [11]
Utótag Tizedesjegyhez Oktális és hexadecimális számokhoz
Nem int

long

long long

int

unsigned int

long

unsigned long

long long

unsigned long long

uvagyU unsigned int

unsigned long

unsigned long long

unsigned int

unsigned long

unsigned long long

lvagyL long

long long

long

unsigned long

long long

unsigned long long

uvagy Uegyütt lvagyL unsigned long

unsigned long long

unsigned long

unsigned long long

llvagyLL long long long long

unsigned long long

uvagy Uegyütt llvagyLL unsigned long long unsigned long long
Példák valós szám írására 1.5
Decimális

formátum

Kitevővel Hexadecimális

formátum

1.5 1.5e+0 0x1.8p+0
15e-1 0x3.0p-1
0.15e+1 0x0.cp+1

A valós számállandók alapértelmezés szerint típusúak double. Utótag megadásakor fa típus hozzá van rendelve a konstanshoz , a vagy  - floatmegadásakor pedig . Egy konstans akkor tekinthető valósnak, ha tartalmaz egy pontjelet vagy egy betűt, vagy ha hexadecimális jelölés van előtaggal . A decimális jelölés tartalmazhat kitevőt a vagy betűk után . Hexadecimális jelölés esetén a kitevő a betűk után van megadva, vagy kötelező, ami megkülönbözteti a valódi hexadecimális állandókat az egész számoktól. Hexadecimálisban a kitevő 2 hatványa [12] . lLlong doublepP0xeEpP

A karakterkonstansok idézőjelek közé ( ') vannak zárva, és az előtag megadja a karakterkonstans adattípusát és azt a kódolást, amelyben a karakter megjelenik. C-ben az előtag nélküli karakterkonstans int[13] típusú , ellentétben a C++ -val , ahol a karakterkonstans char.

Karakterállandó előtagok [13]
Előtag Adattípus Kódolás
Nem int ASCII
u char16_t 16 bites többbájtos karakterlánc kódolás
U char32_t 32 bites többbájtos karakterlánc kódolás
L wchar_t Széles karakterlánc kódolás

A karakterlánc-literálok dupla idézőjelek közé vannak zárva, és előtagként szerepelhet a karakterlánc adattípusa és kódolása. A karakterlánc-literálok sima tömbök. A többbyte-os kódolásokban, például az UTF-8- ban azonban egy karakter egynél több tömbelemet is elfoglalhat. Valójában a string literálok const [14] , de a C++-tól eltérően adattípusaik nem tartalmazzák a módosítót const.

Karakterlánc-konstans előtagok [15]
Előtag Adattípus Kódolás
Nem char * ASCII vagy többbyte-os kódolás
u8 char * UTF-8
u char16_t * 16 bites többbájtos kódolás
U char32_t * 32 bites többbájtos kódolás
L wchar_t * Széles karakterlánc kódolás

Több egymást követő, szóközzel vagy újsorral elválasztott karakterlánc-állandót egyetlen karakterláncba vonnak össze fordításkor, amelyet gyakran használnak egy karakterlánc kódjának stíluszására úgy, hogy a karakterlánc-konstans részeit elválasztják a különböző sorokban az olvashatóság javítása érdekében [16] .

Elnevezett konstansok Az állandók beállítási módszereinek összehasonlítása [17]
Makró #define BUFFER_SIZE 1024
Névtelen
felsorolás
enum { BUFFER_SIZE = 1024 };
Változó
, mint
állandó
const int puffer_méret = 1024 ; extern const int puffer_size ;

A C nyelvben a konstansok meghatározásához a preprocesszor direktívával deklarált makródefiníciókat szokás használni [17] : #define

#define állandó név [ érték ]

Az így bevezetett konstans a konstans beállításától kezdve a programkód végéig, vagy amíg az adott konstans hatását a direktíva meg nem szünteti, hatókörében lesz érvényben #undef:

#undef állandó név

Mint minden makró esetében, egy elnevezett konstans esetén az állandó értéke automatikusan behelyettesítésre kerül a programkódban, ahol az állandó nevét használják. Emiatt egész vagy valós számok makrón belüli deklarálásakor szükség lehet az adattípus explicit megadására a megfelelő literális utótag használatával, ellenkező esetben a szám alapértelmezés szerint típust fog adni integész szám esetén, vagy típust egy típus double esetén. igazi.

Egész számok esetén van egy másik módszer is a névvel ellátott konstansok létrehozására – operátor-enum-okon keresztül enum[17] . Ez a módszer azonban csak a típusnál kisebb vagy azzal egyenlő típusokra alkalmas , és a szabványos könyvtárban nem használják [18] . int

A minősítővel is lehet állandókat változóként létrehozni const, de a másik két módszertől eltérően az ilyen állandók memóriát fogyasztanak, rá lehet mutatni, és fordítási időben nem használhatók [17] :

  • a bitmezők méretének megadásához,
  • egy tömb méretének beállítása (kivéve a változó hosszúságú tömböket),
  • egy felsorolási elem értékének beállításához,
  • mint az operátor értéke case.
Kulcsszavak

A kulcsszavak  olyan azonosítók, amelyeket egy adott feladat elvégzésére terveztek a fordítási szakaszban, vagy tippeket és utasításokat adnak a fordítónak.

A C nyelv kulcsszavai [19]
Kulcsszavak Célja Alapértelmezett
sizeof Egy objektum méretének lekérése fordítási időben C89
typedef Alternatív név megadása egy típushoz
auto,register A fordítói tippek a változók tárolási helyére
extern Megmondja a fordítónak, hogy az aktuális fájlon kívül keressen egy objektumot
static Statikus objektum deklarálása
void Nincs értékjelző; mutatókban tetszőleges adatot jelent
char... short_ int_long Egész számtípusok és méretmódosítóik
signed,unsigned Egész típusú módosítók, amelyek előjelesként vagy előjel nélküliként határozzák meg őket
float,double Valódi adattípusok
const Egy adattípus-módosító, amely közli a fordítóval, hogy az adott típusú változók csak olvashatók
volatile A fordító utasítása egy változó értékének kívülről történő megváltoztatására
struct Adattípus, mezőkészlettel rendelkező struktúraként megadva
enum Egy adattípus, amely egész értékek halmazának egyikét tárolja
union Olyan adattípus, amely különböző adattípusok reprezentációiban képes adatokat tárolni
do. for_while Loop kijelentések
if,else Feltételes operátor
switch. case_default Operátor kiválasztása egész szám paraméter alapján
break,continue Loop Break nyilatkozatok
goto Feltétel nélküli ugrás operátor
return Visszatérés egy függvényből
inline Inline függvény deklaráció C99 [20]
restrict Olyan mutató deklarálása, amely egy olyan memóriablokkra hivatkozik, amelyre más mutató nem hivatkozik
_Bool[b] logikai adattípus
_Complex[c] ,_Imaginary [d] Komplex számszámításokhoz használt típusok
_Atomic Típusmódosító, ami atomossá teszi C11
_Alignas[e] Explicit módon megadja a bájt igazítását egy adattípushoz
_Alignof[f] Igazítás lekérése egy adott adattípushoz fordítási időben
_Generic Egy értékkészlet kiválasztása fordítási időben, a vezérelt adattípus alapján
_Noreturn[g] Jelzi a fordítónak, hogy a függvény nem fejeződhet be normálisan (azaz return)
_Static_assert[h] A fordításkor ellenőrizendő állítások megadása
_Thread_local[én] Egy szál-lokális változó deklarálása
Fenntartott azonosítók

A kulcsszavakon kívül a nyelvi szabvány lefoglalt azonosítókat határoz meg, amelyek használata a szabvány jövőbeni verzióival való összeférhetetlenséghez vezethet. A kulcsszó kivételével minden olyan szó, amely aláhúzással ( ) kezdődik, _amelyet nagybetű ( A- Z) vagy egy másik aláhúzás [21] követ, le van foglalva . A C99 és C11 szabványokban ezen azonosítók egy részét az új nyelvi kulcsszavakhoz használták.

A fájl hatókörében az aláhúzással ( _) [21] kezdődő nevek használata fenntartva , azaz megengedett típusok, konstansok és változók elnevezése egy utasításblokkon belül, például függvényeken belül, aláhúzással.

Fenntartott azonosítók a szabványos könyvtár összes makrója és a linkelési szakaszban hivatkozott nevek is [21] .

A lefoglalt azonosítók használatát a programokban a szabvány nem definiált viselkedésként határozza meg . Bármely szabványos makró törlésének kísérlete a viadalon #undefszintén meghatározatlan viselkedést eredményez [21] .

Megjegyzések

A C program szövege tartalmazhat olyan töredékeket, amelyek nem részei a programkódnak – megjegyzések . A megjegyzéseket speciális módon jelöljük a program szövegében, és az összeállítás során kihagyjuk.

Kezdetben a C89/* szabványban olyan soron belüli megjegyzések álltak rendelkezésre, amelyeket a karaktersorozatok és a karakterek közé lehetett helyezni */. Ebben az esetben nem lehet egyik megjegyzést a másikba beágyazni, mivel az első találkozási sorozat */befejezi a megjegyzést, és a jelölést közvetlenül követő szöveget */a fordító a program forráskódjaként fogja fel.

A következő szabvány, a C99 a megjegyzések megjelölésének egy újabb módját vezette be: megjegyzésnek egy karaktersorozattal //kezdődő és egy sor végére végződő szöveget tekintünk [20] .

A megjegyzéseket gyakran használják a forráskód öndokumentálására, bonyolult részek magyarázatára, bizonyos fájlok céljának leírására, valamint bizonyos függvények, makrók, adattípusok és változók használatának és működésének szabályainak leírására. Vannak olyan utófeldolgozók, amelyek a speciálisan formázott megjegyzéseket dokumentumokká alakíthatják. Az ilyen C nyelvű utófeldolgozók közül a Doxygen dokumentációs rendszer működhet .

Operátorok

A kifejezésekben használt operátorok olyan műveletek, amelyeket operandusokon hajtanak végre, és amelyek egy számított értéket adnak vissza - a művelet eredményét. Az operandus lehet konstans, változó, kifejezés vagy függvényhívás. Az operátor lehet speciális karakter, speciális karakterek halmaza vagy speciális szó. Az operátorokat az érintett operandusok száma különbözteti meg, nevezetesen megkülönböztetnek unáris, bináris és hármas operátorokat.

Unáris operátorok

Az unáris operátorok egyetlen argumentumon hajtanak végre műveletet, és a következő műveleti formátummal rendelkeznek:

[ operátor ] [ operandus ]

A postfix növelési és csökkentési műveletek fordított formátumúak:

[ operandus ] [ operátor ] Unary C operátorok [22]
+ egységes plusz ~ A visszatérési kód felvétele & Cím felvétele ++ Előtag vagy utótag növekménye sizeof A memóriában lévő objektum által elfoglalt bájtok számának lekérése; műveletként és operátorként is használható
- egységes mínusz ! logikai tagadás * Mutató dereferálása -- Előtag vagy utótag csökkentése _Alignof Igazítás lekérése egy adott adattípushoz

A növelő és csökkentő operátorok, ellentétben a többi unáris operátorral, megváltoztatják operandusuk értékét. Az előtag operátor először módosítja az értéket, majd visszaadja azt. A Postfix először visszaadja az értéket, és csak azután módosítja azt.

Bináris operátorok

A bináris operátorok két argumentum között helyezkednek el, és műveletet hajtanak végre rajtuk:

[ operandus ] [ operátor ] [ operandus ] Alapvető bináris operátorok [23]
+ Kiegészítés % A hadosztály fennmaradó részét elvéve << Bitenkénti bal eltolás > Több == Egyenlő
- Kivonás & Bitenként ÉS >> Bit eltolás jobbra < Kevésbé != Nem egyenlő
* Szorzás | Bitenkénti VAGY && logikus ÉS >= Nagyobb vagy egyenlő
/ Osztály ^ Bitenkénti XOR || Logikus VAGY <= Kisebb vagy egyenlő

Ezenkívül a C bináris operátorai közé tartoznak a bal oldali hozzárendelési operátorok is, amelyek a bal és a jobb oldali argumentumokra hajtanak végre műveletet, és az eredményt a bal oldali argumentumba helyezik.

Balra hozzárendelt bináris operátorok [24]
= A jobb oldali argumentum értékének hozzárendelése a balhoz %= A bal oldali operandus jobbdal való osztásának maradéka ^= A jobb oldali operandus bitenkénti XOR-ja a bal operandushoz
+= Hozzáadás a jobb oldal bal operandusához /= A bal oldali operandus felosztása a jobb oldalival <<= A bal oldali operandus bitenkénti eltolása balra a jobb oldali operandus által megadott bitek számával
-= Kivonás a jobb bal oldali operandusából &= Bitenként ÉS a jobb oldali operandus balra >>= A bal oldali operandus bitenkénti eltolása jobbra a jobb oldali operandus által meghatározott bitszámmal
*= A bal oldali operandus szorzása a jobb oldalival |= A jobb oldali operandus bitenkénti VAGY balra
Ternáris operátorok

C-ben csak egy háromtagú operátor van, a rövidített feltételes operátor, amelynek a következő alakja van:

[ feltétel ] ?[ kifejezés1 ] :[ kifejezés2 ]

A gyorsított feltételes operátornak három operandusa van:

  • [ feltétel ] - egy logikai feltétel, amely ellenőrzi az igazságot,
  • [ kifejezés1 ] - kifejezés, amelynek értéke a művelet eredményeként kerül visszaadásra, ha a feltétel igaz;
  • A [ kifejezés2 ] az a kifejezés, amelynek értéke hamis feltétel esetén a művelet eredményeként kerül visszaadásra.

Az operátor ebben az esetben a jelek ?és a kombinációja :.

Kifejezések

A kifejezés konstansokon, változókon és függvényeken végzett műveletek rendezett halmaza. A kifejezések operandusokból és operátorokból álló műveleteket tartalmaznak . A műveletek végrehajtásának sorrendje a rögzítési formától és a műveletek prioritásától függ. Minden kifejezésnek van értéke  - a kifejezésben szereplő összes művelet végrehajtásának eredménye. Egy kifejezés kiértékelése során a műveletek függvényében a változók értéke változhat, illetve függvények is végrehajthatók, ha a kifejezésben a hívásaik szerepelnek.

A kifejezések között megkülönböztethető a balra megengedett kifejezések osztálya  - olyan kifejezések, amelyek a hozzárendelési jeltől balra jelenhetnek meg.

A műveletek végrehajtásának prioritása

A műveletek prioritását a szabvány határozza meg, és meghatározza a műveletek végrehajtásának sorrendjét. A műveletek C-ben az alábbi [25] [26] sorrendi táblázat szerint történnek .

Prioritás zsetonok Művelet Osztály Az asszociativitás
egy a[index] Hivatkozás index szerint postfix balról jobbra →
f(érvek) Funkcióhívás
. Field Access
-> Mezőhozzáférés mutató segítségével
++ -- Pozitív és negatív növekedés
(típusnév ) {inicializáló} Összetett literál (C99)
(típusnév ) {inicializáló,}
2 ++ -- Pozitív és negatív előtag növekmény egységes ← jobbról balra
sizeof A méret megszerzése
_Alignof[f] Igazítás lekérése ( C11 )
~ Bitenként NEM
! Logikus NEM
- + Jeljelzés (mínusz vagy plusz)
& Cím megszerzése
* Mutatóhivatkozás (hivatkozás visszavonása)
(típusnév) Típusöntés
3 * / % Szorzás, osztás és maradék bináris balról jobbra →
négy + - Összeadás és kivonás
5 << >> Váltás balra és jobbra
6 < > <= >= Összehasonlító műveletek
7 == != Az egyenlőség vagy egyenlőtlenség ellenőrzése
nyolc & Bitenként ÉS
9 ^ Bitenkénti XOR
tíz | Bitenkénti VAGY
tizenegy && logikus ÉS
12 || Logikus VAGY
13 ? : Állapot hármas ← jobbról balra
tizennégy = Érték hozzárendelés bináris
+= -= *= /= %= <<= >>= &= ^= |= Műveletek a bal oldali érték megváltoztatására
tizenöt , Szekvenciális számítás balról jobbra →

A kezelői prioritások C-ben nem mindig igazolják magukat, és néha intuitív módon nehezen megjósolható eredményekhez vezetnek. Például, mivel az unáris operátorok jobbról balra irányú asszociativitással rendelkeznek, a kifejezés kiértékelése a mutatónövekedést *p++eredményezi, amelyet egy hivatkozás ( *(p++)) követ, nem pedig a mutató növekedését ( (*p)++). Ezért a nehezen érthető helyzetekben javasolt a kifejezéseket kifejezetten zárójelek használatával csoportosítani [26] .

A C nyelv másik fontos jellemzője, hogy a függvényhívásnak átadott argumentumértékek kiértékelése nem szekvenciális [27] , vagyis az argumentumokat vesszővel elválasztó nem felel meg a szekvenciális kiértékelésnek a precedenciatáblázatból. A következő példában egy másik függvény argumentumaként megadott függvényhívások tetszőleges sorrendben lehetnek:

int x ; x = számítás ( get_arg1 (), get_arg2 ()); // először hívja meg a get_arg2() függvényt

A kifejezés kiértékelése során fellépő mellékhatások esetén sem hagyatkozhat a műveletek elsőbbségére , mivel ez definiálatlan viselkedéshez vezet [27] .

Sorozatpontok és mellékhatások

A nyelvi szabvány C függeléke meghatározza a szekvenciapontok halmazát , amelyek garantáltan nem okoznak folyamatos mellékhatásokat a számításokból. Azaz a szekvenciapont a számítások olyan szakasza, amely a kifejezések kiértékelését szétválasztja egymás között úgy, hogy a szekvenciapont előtt történt számítások, beleértve a mellékhatásokat is, már befejeződtek, a szekvenciapont után pedig még nem kezdődtek el [28 ] . Mellékhatás lehet egy változó értékének változása egy kifejezés kiértékelése során. A számításba bevont érték megváltoztatása, valamint annak mellékhatása, hogy ugyanazt az értéket a következő sorozatpontra változtatja, meghatározatlan viselkedéshez vezet. Ugyanez történik, ha a számításban ugyanannak az értéknek két vagy több oldalsó változása van [27] .

A szabvány által meghatározott szekvenciapontok [27]
Útpont Esemény előtt Esemény után
Funkcióhívás Mutató kiszámítása egy függvényre és argumentumaira Funkcióhívás
Logikai ÉS operátorok ( &&), VAGY ( ||) és szekvenciális számítások ( ,) Az első operandus kiszámítása A második operandus számítása
Gyorsírási feltétel operátor ( ?:) A feltételként szolgáló operandus számítása A 2. vagy 3. operandus kiszámítása
Két teljes kifejezés között (nem beágyazott) Egy teljes kifejezés A következő teljes kifejezés
Elkészült a teljes leíró
Közvetlenül a könyvtári funkcióból való visszatérés előtt
Minden egyes formázott I/O specifikátorhoz társított átalakítás után
Közvetlenül az összehasonlító függvény minden egyes hívása előtt és után, valamint az összehasonlító függvény hívása és az összehasonlító függvénynek átadott argumentumokkal végzett mozgások között

A teljes kifejezések a következők : [27] :

  • egy inicializáló, amely nem része egy összetett literálnak;
  • izolált kifejezés;
  • feltételes utasítás ( if) vagy kiválasztási utasítás ( switch) feltételeként megadott kifejezés;
  • ciklusfeltételként megadott kifejezés whileelő- vagy utófeltétellel;
  • minden ciklusparaméter for, ha van ilyen;
  • operátor kifejezés return, ha van megadva.

A következő példában a változó háromszor megváltozik a sorozatpontok között, ami meghatározatlan eredményt eredményez:

int i = 1 ; // A leíró az első sorozatpont, a teljes kifejezés a második i += ++ i + 1 ; // Teljes kifejezés - harmadik sorozatpont printf ( "%d \n " , i ); // 4-et vagy 5-öt is kiadhat

További egyszerű példák az elkerülendő, meghatározatlan viselkedésre:

i = i ++ + 1 ; // meghatározatlan viselkedés i = ++ i + 1 ; // szintén meghatározatlan viselkedés printf ( "%d, %d \n " , -- i , ++ i ); // meghatározatlan viselkedés printf ( "%d, %d \n " , ++ i , ++ i ); // szintén meghatározatlan viselkedés printf ( "%d, %d \n " , i = 0 , i = 1 ); // meghatározatlan viselkedés printf ( "%d, %d \n " , i = 0 , i = 0 ); // szintén meghatározatlan viselkedés a [ i ] = i ++ ; // meghatározatlan viselkedés a [ i ++ ] = i ; // szintén meghatározatlan viselkedés

Ellenőrző utasítások

A vezérlő utasítások műveletek végrehajtására és a programvégrehajtás folyamatának szabályozására szolgálnak. Több egymást követő utasítás állítássorozatot alkot .

Üres nyilatkozat

A legegyszerűbb nyelvi konstrukció egy üres kifejezés, amelyet üres utasításnak neveznek [29] :

;

Az üres utasítás nem tesz semmit, és bárhol elhelyezhető a programban. Gyakran használják a hiányzó törzsű hurkokban [30] .

Útmutató

Az utasítás egyfajta elemi művelet:

( kifejezés );

Ennek az operátornak az a feladata, hogy végrehajtsa az operátor törzsében megadott kifejezést.

Több egymást követő utasítás egy utasítássorozatot alkot .

Utasítás blokk

Az utasítások a következő formájú speciális blokkokba csoportosíthatók:

{

( utasítások sorozata )

},

Az állítások blokkját, amelyet néha összetett utasításnak is neveznek, egy bal oldali kapcsos zárójel ( {) az elején és egy jobb oldali kapcsos zárójel ( }) a végén határolja.

A függvényekben egy utasításblokk a függvény törzsét jelöli, és a függvénydefiníció része. Az összetett utasítás ciklus-, feltétel- és választási utasításokban is használható.

Feltételes állítások

Két feltételes operátor van a nyelvben, amelyek programelágazást valósítanak meg:

  • ifegyetlen feltételtesztet tartalmazó nyilatkozat ,
  • és egy switchtöbb ellenőrizendő feltételt tartalmazó nyilatkozat.

Az operátor legegyszerűbb formájaif

if(( feltétel ) )( operátor ) ( következő kijelentés )

Az operátor ifa következőképpen működik:

  • ha a zárójelben lévő feltétel igaz, akkor az első utasítás, majd az azt követő utasítás végrehajtásra kerül if.
  • ha a zárójelben megadott feltétel nem teljesül, akkor az utasítás után megadott utasítás azonnal végrehajtódik if.

Különösen a következő kód, ha a megadott feltétel teljesül, nem hajt végre semmilyen műveletet, mivel valójában egy üres utasítás kerül végrehajtásra:

if(( állapot )) ;

Az operátor összetettebb formája ifa kulcsszót tartalmazza else:

if(( feltétel ) )( operátor ) else( alternatív operátor ) ( következő kijelentés )

Itt, ha a zárójelben megadott feltétel nem teljesül, akkor a kulcsszó után megadott utasítás végrehajtásra kerül else.

Annak ellenére, hogy a szabvány lehetővé teszi az utasítások egy sorban ifvagy elseegyetlen sorban történő megadását, ez rossz stílusnak minősül, és csökkenti a kód olvashatóságát. Javasoljuk, hogy mindig kapcsos kapcsos zárójeleket használva adjon meg egy utasításblokkot [31] .

Cikkvégrehajtási utasítások

A ciklus egy kódrészlet, amely tartalmazza

  • hurokvégrehajtási feltétel – állandóan ellenőrzött feltétel;
  • a ciklustörzs pedig egy egyszerű vagy összetett utasítás, amelynek végrehajtása a ciklus állapotától függ.

Ennek megfelelően kétféle ciklus létezik:

  • előfeltételű hurok , ahol először a ciklusvégrehajtási feltételt ellenőrizzük, és ha a feltétel teljesül, akkor a ciklustörzs végrehajtásra kerül;
  • egy utófeltételes hurok , ahol a huroktörzs végrehajtása után a ciklusfolytatási feltételt ellenőrizzük.

A feltételes ciklus garantálja, hogy a ciklus törzse legalább egyszer végrehajtásra kerül.

A C nyelv két ciklusváltozatot biztosít előfeltétellel: whileés for.

while(állapot) [ huroktest ] for( inicializálási blokkfeltétel ;utasítás ;[) loop body ],

A ciklust forparametrikusnak is nevezik, ez ekvivalens a következő utasításblokkkal:

[ inicializálási blokk ] while(állapot) { [ huroktest ] [ operátor ] }

Normál helyzetben az inicializálási blokk tartalmazza egy változó kezdeti értékének beállítását, amelyet hurokváltozónak nevezünk, és az utasítás, amely közvetlenül azután fut le, hogy a ciklustörzs megváltoztatja a használt változó értékét, a feltétel tartalmazza a használt ciklusváltozó értékének összehasonlítása valamilyen előre meghatározott értékkel, és amint az összehasonlítás leáll, a ciklus megszakad, és a ciklus utasítást közvetlenül követő programkód végrehajtása megkezdődik.

A ciklus do-whileesetén a feltétel a ciklus törzse után van megadva:

do[ huroktest ] while( állapot)

A ciklusfeltétel egy logikai kifejezés. Az implicit típusú öntvény azonban lehetővé teszi számtani kifejezések ciklusfeltételként történő használatát. Ez lehetővé teszi az úgynevezett "végtelen hurok" megszervezését:

while(1);

Ugyanezt megtehetjük az operátorral is for:

for(;;);

A gyakorlatban az ilyen végtelen hurkokat általában a break, gotovagy a -val együtt használják return, amelyek különböző módon szakítják meg a hurkot.

A feltételes utasításokhoz hasonlóan, ha egysoros törzset kapcsos kapcsos zárójeles utasításblokkba nem zárunk, az rossz stílusnak minősül, ami csökkenti a kód olvashatóságát [31] .

Feltétel nélküli ugrás operátorok

A feltétel nélküli elágazási operátorok lehetővé teszik, hogy megszakítsa a számítások bármely blokkjának végrehajtását, és az aktuális függvényen belül egy másik helyre lépjen a programban. A feltétel nélküli ugrás operátorokat általában feltételes operátorokkal együtt használják.

goto[ címke ],

A címke egy olyan azonosító, amely átadja az irányítást a programban a megadott címkével megjelölt operátornak:

[ címke ] :[ operátor ]

Ha a megadott címke nem szerepel a programban, vagy ha több utasítás van ugyanazzal a címkével, a fordító hibát jelez.

A vezérlés átadása csak azon a funkción belül lehetséges, ahol az átmeneti operátort használjuk, ezért az operátor gotohasználatával nem lehet átvinni a vezérlést egy másik funkcióra.

Más ugrási utasítások ciklusokhoz kapcsolódnak, és lehetővé teszik a ciklustörzs végrehajtásának megszakítását:

  • az utasítás breakazonnal megszakítja a ciklustörzs végrehajtását, és a vezérlés átkerül a ciklust közvetlenül követő utasításra;
  • az operátor continuemegszakítja a ciklus aktuális iterációjának végrehajtását, és kísérletet kezdeményez a következőre.

Az utasítás breakmegszakíthatja az utasítás működését is , így a ciklusban futó switchutasításon belül az utasítás nem tudja megszakítani a ciklust. A ciklus törzsében megadva megszakítja a legközelebbi beágyazott ciklus munkáját. switchbreak

Az operátor continuecsak a do, whileés operátorokon belül használható for. A hurkok whileés do-whileaz operátor continueesetén a ciklusfeltétel tesztelését, hurok esetén pedig a ciklus for 3. paraméterében megadott operátor végrehajtását okozza, a ciklus folytatásának feltételének ellenőrzése előtt.

Függvényvisszaadási utasítás

Az operátor returnmegszakítja annak a funkciónak a végrehajtását, amelyben használják. Ha a függvénynek nem kell értéket adnia, akkor visszatérési érték nélküli hívást használunk:

return;

Ha a függvénynek értéket kell visszaadnia, akkor a visszatérési érték az operátor után jelenik meg:

return[ érték ];

Ha a függvénytörzsben a return utasítás után van más utasítás, akkor ezek az utasítások soha nem fognak végrehajtódni, ilyenkor a fordító figyelmeztetést adhat ki. Az operátor után azonban returnfel lehet tüntetni a funkció alternatív, például tévedésből történő leállítására vonatkozó utasításokat, és az ezekre az operátorokra való áttérés az operátor segítségével gototetszőleges feltételek mellett végrehajtható .

Változók

Egy változó deklarálásakor megadjuk a típusát és a nevét, valamint a kezdőértéket is megadhatjuk:

[leíró] [név];

vagy

[descriptor] [name] =[initializer] ;,

ahol

  • [descriptor] - változó típusa és a típust megelőző opcionális módosítók;
  • [név] – változó neve;
  • [inicializáló] – a létrehozásakor hozzárendelt változó kezdeti értéke.

Ha a változóhoz nincs hozzárendelve kezdeti érték, akkor globális változó esetén az értéke nullákkal lesz kitöltve, lokális változó esetén pedig a kezdeti érték definiálatlan lesz.

A változóleíróban a változót globálisként jelölheti meg, de csak egy fájl vagy függvény hatókörére korlátozódik a kulcsszó használatával static. Ha egy változót a kulcsszó nélkül deklarálunk globálisnak static, akkor más fájlokból is elérhető, ahol ezt a változót inicializáló nélkül, de a kulcsszóval kell deklarálni extern. Az ilyen változók címét a kapcsolat időpontjában határozzák meg .

Funkciók

A függvény egy független programkód, amely újra felhasználható egy programban. A függvények argumentumokat vehetnek fel, és értékeket adhatnak vissza. A függvényeknek mellékhatásai is lehetnek végrehajtásuk során: globális változók megváltoztatása, fájlokkal való munka, interakció az operációs rendszerrel vagy hardverrel [28] .

Egy függvény C-ben való meghatározásához deklarálnia kell:

  • jelentse a funkció nevét (azonosítóját),
  • bemeneti paraméterek listája (argumentumok)
  • és adja meg a visszatérési típust.

Szükséges továbbá egy függvénydefiníció megadása, amely a függvény viselkedését megvalósító utasítások blokkját tartalmazza.

Egy adott függvény nem deklarálása hiba, ha a függvényt a definíción kívül használják, ami a megvalósítástól függően üzeneteket vagy figyelmeztetéseket eredményez.

Egy függvény meghívásához elegendő a nevét megadni a zárójelben megadott paraméterekkel. Ilyenkor a híváspont címe kerül a verembe, létrejönnek és inicializálódnak a függvényparaméterekért felelős változók, és a vezérlés átkerül a hívott függvényt megvalósító kódra. A függvény végrehajtása után a függvényhívás során lefoglalt memória felszabadul, a hívási ponthoz való visszatérés, és ha a függvényhívás valamilyen kifejezés része, a függvényen belül számított érték átkerül a visszatérési pontba.

Ha a függvény után nincs megadva zárójel, akkor a fordító ezt úgy értelmezi, hogy megkapja a függvény címét. Egy függvény címe beírható egy pointerbe, majd a függvényt egy rámutató mutató segítségével hívhatjuk meg, amit például a plugin rendszerekben aktívan használnak [32] .

A kulcsszó inlinesegítségével megjelölheti azokat a függvényeket, amelyek hívását a lehető leggyorsabban szeretné végrehajtani. A fordító az ilyen függvények kódját közvetlenül a hívásuk pontján helyettesítheti [33] . Ez egyrészt növeli a végrehajtható kód mennyiségét, másrészt viszont a végrehajtás idejét takarítja meg, mivel az időigényes függvényhívási műveletet nem használjuk. A számítógépek architektúrája miatt azonban a beágyazó funkciók felgyorsíthatják vagy lelassíthatják az alkalmazás egészét. Azonban sok esetben az inline függvények a legelőnyösebb helyettesítő makrók [34] .

Funkció deklaráció

A függvénydeklaráció a következő formátumú:

[leíró] [név] ([lista] );,

ahol

  • [descriptor] – a függvény által visszaadott érték típusleírója;
  • [név] - függvény neve (a függvény egyedi azonosítója);
  • [lista] - a függvény (formális) paramétereinek listája vagy voidezek hiányában [35] .

A függvénydeklaráció előjele a „ ;” szimbólum, tehát a függvénydeklaráció egy utasítás.

A legegyszerűbb esetben a [declarator] egy adott típusú visszatérési érték jelzését tartalmazza. Az olyan függvény, amely nem adhat vissza semmilyen értéket, típusúnak van deklarálva void.

Ha szükséges, a leíró kulcsszavakkal meghatározott módosítókat tartalmazhat:

  • externazt jelzi, hogy a függvénydefiníció egy másik modulban van ;
  • staticegy statikus függvényt határoz meg, amely csak az aktuális modulban használható.

A függvényparaméterek listája határozza meg a függvény aláírását.

A C nem teszi lehetővé több, azonos nevű függvény deklarálását, a függvénytúlterhelés nem támogatott [36] .

Függvénydefiníció

A függvénydefiníció a következő formátumú:

[leíró] [név] ([lista] )[test]

Ahol [deklarátor], [név] és [lista] megegyezik a deklarációban szereplővel, a [test] pedig egy összetett utasítás, amely a függvény konkrét megvalósítását képviseli. A fordító az azonos nevű függvények definícióit aláírásukkal különbözteti meg, és így (aláírással) kapcsolat jön létre a definíció és a megfelelő deklaráció között.

A függvény törzse így néz ki:

{ [kijelentéssorozat] return([visszatérési érték]); }

A függvényből való visszatérés a operátor segítségével történik , amely a függvény által visszaadott adattípustól függően vagy megadja a visszatérési értéket, vagy nem adja meg. Ritka esetekben előfordulhat, hogy egy függvény fejlécfájlból származó makró használatával nem hoz vissza eredményt , ebben az esetben nincs szükség utasításra. Például az önmagukban feltétel nélkül meghívó függvények jelölhetők így [33] . returnnoreturnstdnoreturn.hreturnabort()

Függvényhívás

A függvényhívás a következő műveleteket hajtja végre:

  • a hívópont mentése a verembe;
  • automatikus memóriafoglalás a függvény formális paramétereinek megfelelő változókhoz;
  • a változók inicializálása a függvénynek átadott változók értékeivel (a függvény tényleges paraméterei) a függvény meghívásakor, valamint azon változók inicializálása, amelyekre a függvény deklarációjában az alapértelmezett értékek vannak megadva, de amelyekre a hívás során nem adták meg a hozzájuk tartozó tényleges paramétereket;
  • az irányítás átadása a funkció törzsének.

Az implementációtól függően a fordító vagy szigorúan gondoskodik arról, hogy az aktuális paraméter típusa megegyezzen a formális paraméter típusával, vagy ha lehetséges, implicit típuskonverziót hajt végre, ami nyilvánvalóan mellékhatásokhoz vezet.

Ha egy változót adunk át a függvénynek, akkor a függvény meghívásakor másolat jön létre róla ( memória lefoglalásra kerül a veremben , és az értéket másoljuk). Például, ha egy struktúrát egy függvénynek adunk át, akkor a teljes struktúra másolásra kerül. Ha egy struktúrára mutató mutatót adunk át, akkor csak a mutató értéke kerül másolásra. Egy tömb függvénynek való átadása is csak az első elemre mutató mutatót másolja. Ebben az esetben annak kifejezett jelzésére, hogy a tömb elejének címét veszi be a függvény bemeneteként, nem pedig egyetlen változóra mutató mutatót, ahelyett, hogy a változó neve után mutatót deklarálna, szögletes zárójeleket tehet, pl. példa:

void example_func ( int tömb []); A // tömb egy int típusú tömb első elemére mutató mutató

C engedélyezi a beágyazott hívásokat. A hívások egymásba ágyazási mélysége nyilvánvalóan korlátozza a programhoz rendelt verem méretét. Ezért a C implementációk korlátot szabnak a beágyazás mélységének.

A beágyazott hívás speciális esete egy függvényhívás a meghívott függvény törzsében. Az ilyen hívást rekurzívnak nevezik, és egységes számítások szervezésére szolgál. Tekintettel a beágyazott hívásokra vonatkozó természetes korlátozásra, a rekurzív megvalósítást felváltja a hurkokat használó megvalósítás.

Adattípusok

Primitív típusok

Egész számok

Az egész adattípusok mérete legalább 8 és legalább 32 bit között van. A C99 szabvány az egész szám maximális méretét legalább 64 bitre növeli. Az egész adattípusok egész számok tárolására szolgálnak (a típus charaz ASCII karakterek tárolására is szolgál). Az alábbi adattípusok minden tartománymérete minimális, és adott platformon nagyobb is lehet [37] .

A szabvány a típusok minimális méretéből adódóan megköveteli, hogy az integrált típusok méretei teljesítsék a következő feltételt:

1= ≤ ≤ ≤ ≤ . sizeof(char)sizeof(short)sizeof(int)sizeof(long)sizeof(long long)

Így egyes típusok mérete a bájtok számát tekintve megegyezhet, ha teljesül a minimális bitszám feltétele. Akár charés longazonos méretű is lehet, ha egy bájt 32 bitet vagy többet vesz igénybe, de az ilyen platformok nagyon ritkák vagy nem léteznek. A szabvány garantálja, hogy a típus char mindig 1 bájtos. A bájt bitben kifejezett méretét CHAR_BITa fejlécfájlban lévő állandó határozza meg limits.h, amely POSIX -kompatibilis rendszereken 8 bit [38] .

Az egész típusok szabvány szerinti minimális értéktartománya előjeles típusoknál tól -ig , előjel nélküli típusoknál pedig -tól -ig van meghatározva  , ahol N a típus bitmélysége. A fordítóprogramok saját belátásuk szerint bővíthetik ezt a tartományt. A gyakorlatban a -tól -ig terjedő tartományt gyakrabban használják az aláírt típusoknál . Az egyes típusok minimális és maximális értékei a fájlban makródefiníciókként vannak megadva. -(2N-1-1)2N-1-102N-2N-12N-1-1limits.h

Különös figyelmet kell fordítani a típusra char. Formálisan ez egy külön típus, de valójában charegyenértékű a signed char, vagy unsigned char-vel, a fordítótól függően [39] .

A típusméretek összetévesztésének elkerülése érdekében a C99 szabvány új adattípusokat vezetett be, amelyeket a stdint.h. Ezek között vannak olyan típusok, mint: , , , ahol = 8, 16, 32 vagy 64. Az előtag a bitek befogadására alkalmas minimális típust jelöli, az előtag pedig egy legalább 16 bites típust, amely a leggyorsabb ezen a platformon. Az előtag nélküli típusok rögzített bitméretű típusokat jelölnek. intN_tint_leastN_tint_fastN_tNleast-Nfast-N

least-Az és előtagú típusok a , , fast-típusok helyettesítőjének tekinthetők , azzal az egyetlen különbséggel, hogy az előbbiek a programozó számára lehetőséget adnak a sebesség és a méret között. intshortlong

Alapvető adattípusok egész számok tárolására
Adattípus A méret Minimális értéktartomány Alapértelmezett
signed char minimum 8 bit -127-ről [40] (= -(2 7 -1)) 127-re C90 [j]
int_least8_t C99
int_fast8_t
unsigned char minimum 8 bit 0-tól 255-ig (=2 8-1 ) C90 [j]
uint_least8_t C99
uint_fast8_t
char minimum 8 bit −127 és 127 között vagy 0 és 255 között a fordítótól függően C90 [j]
short int minimum 16 bit -32,767-ről (= -(2 15 -1)) 32,767-re C90 [j]
int
int_least16_t C99
int_fast16_t
unsigned short int minimum 16 bit 0 - 65,535 (= 2 16 -1) C90 [j]
unsigned int
uint_least16_t C99
uint_fast16_t
long int minimum 32 bit −2 147 483 647 és 2 147 483 647 között C90 [j]
int_least32_t C99
int_fast32_t
unsigned long int minimum 32 bit 0 - 4 294 967 295 (= 2 32 -1) C90 [j]
uint_least32_t C99
uint_fast32_t
long long int minimum 64 bit -9,223,372,036,854,775,807 - 9,223,372,036,854,775,807 C99
int_least64_t
int_fast64_t
unsigned long long int minimum 64 bit 0 - 18 446 744 073 709 551 615 (= 264 -1 )
uint_least64_t
uint_fast64_t
int8_t 8 bites -127-től 127-ig
uint8_t 8 bites 0-tól 255-ig (=2 8-1 )
int16_t 16 bites -32,767 és 32,767 között
uint16_t 16 bites 0 - 65,535 (= 2 16 -1)
int32_t 32 bites −2 147 483 647 és 2 147 483 647 között
uint32_t 32 bites 0 - 4 294 967 295 (= 2 32 -1)
int64_t 64 bites -9,223,372,036,854,775,807 - 9,223,372,036,854,775,807
uint64_t 64 bites 0 - 18 446 744 073 709 551 615 (= 264 -1 )
A táblázat a nyelvi szabvány szerinti minimális értéktartományt mutatja. A C fordítók bővíthetik az értéktartományt.
Kiegészítő egész típusok

Ezenkívül a C99 szabvány óta a intmax_tés típusok hozzáadásra kerültek uintmax_t, amelyek a legnagyobb aláírt és előjel nélküli típusoknak felelnek meg. Ezek a típusok kényelmesek, ha makrókban használják köztes vagy ideiglenes értékek tárolására az egész argumentumokkal végzett műveletek során, mivel lehetővé teszik bármilyen típusú érték illesztését. Például ezeket a típusokat használják a C Check unit testing library egész számok összehasonlító makróiban [41] .

A C-ben számos további egész típus létezik a mutató adattípusának biztonságos kezeléséhez: intptr_t, uintptr_tés ptrdiff_t. intptr_tA C99 szabvány és típusai uintptr_telőjeles és előjel nélküli értékek tárolására szolgálnak, amelyek méretükben elférnek egy mutatóban. Ezeket a típusokat gyakran használják tetszőleges egész számok mutatóban való tárolására, például annak érdekében, hogy megszabaduljanak a szükségtelen memóriafoglalástól a visszacsatolási függvények regisztrálásakor [42] , vagy amikor harmadik féltől származó linkelt listákat, asszociatív tömböket és más olyan struktúrákat használnak, amelyekben az adatokat mutató tárolja. ptrdiff_tA fejlécfájl típusát stddef.húgy tervezték, hogy biztonságosan tárolja két mutató különbségét.

A méret tárolásához a size_tfejlécfájl előjel nélküli típusát biztosítjuk stddef.h. Ez a típus a mutatónál elérhető maximális számú bájt tárolására képes, és jellemzően a méret bájtokban történő tárolására szolgál. Ennek a típusnak az értékét adja vissza az operátor sizeof[43] .

Integer típusú öntés

Az egész típusú konverzió történhet explicit módon, cast operátor használatával vagy implicit módon. A -nál kisebb típusú értékek int, ha bármilyen műveletben vesznek részt, vagy függvényhívásnak adják át, automatikusan a típusba kerülnek int, és ha az átalakítás nem lehetséges, akkor a típusba unsigned int. Az ilyen implicit öntvények gyakran szükségesek ahhoz, hogy a számítás eredménye helyes legyen, de néha intuitív módon érthetetlen hibákhoz vezetnek a számításokban. Például, ha a műveletben a és típusú számok szerepelnek int, unsigned intés az előjeles érték negatív, akkor egy negatív szám előjel nélküli típusra konvertálása túlcsorduláshoz és nagyon nagy pozitív értékhez vezet, ami az összehasonlítási műveletek helytelen eredményéhez vezethet. [44] .

Helyes és helytelen automata típusú öntés összehasonlítása
Az aláírt és előjel nélküli típusok kisebbek, mintint Az aláírt kevesebb, mint az aláíratlan, és az aláíratlan nem kevesebbint
#include <stdio.h> előjeles karakter x = -1 ; előjel nélküli char y = 0 ; if ( x > y ) { // feltétel false printf ( "Az üzenet nem jelenik meg. \n " ); } if ( x == UCHAR_MAX ) { // feltétel false printf ( "Az üzenet nem jelenik meg. \n " ); } #include <stdio.h> előjeles karakter x = -1 ; előjel nélküli int y = 0 ; if ( x > y ) { // feltétel igaz printf ( "Túlcsordulás az x változóban. \n " ); } if (( x == UINT_MAX ) && ( x == ULONG_MAX )) { // feltétel mindig igaz lesz printf ( "Túlcsordulás az x változóban. \n " ); }
Ebben a példában mindkét típus, az aláírt és az aláíratlan, az aláírt fájlba lesz átküldve int, mert lehetővé teszi mindkét típus tartományának illeszkedését. Ezért a feltételes operátor összehasonlítása helyes lesz. Az előjeles típus előjel nélküli típusba kerül, mert az előjel nélküli típus mérete nagyobb vagy egyenlő, mint a int, de túlcsordulás történik, mert lehetetlen negatív értéket megjeleníteni egy előjel nélküli típusban.

Az automatikus típus-öntés akkor is működik, ha a kifejezésben két vagy több különböző egész szám szerepel. A szabvány meghatároz egy szabályrendszert, amely szerint olyan típuskonverziót választanak ki, amely a számítás helyes eredményét tudja adni. A különböző típusokhoz különböző rangokat rendelnek az átalakításon belül, és maguk a rangok a típus méretétől függenek. Ha egy kifejezésben különböző típusok vesznek részt, általában úgy döntenek, hogy ezeket az értékeket egy magasabb rangú típusba öntjük [44] .

Valós számok

A C-ben lévő lebegőpontos számokat három alapvető típus képviseli: float, doubleés long double.

A valós számok ábrázolása nagyon különbözik az egész számoktól. A különböző típusú valós számok tizedes jelöléssel írt állandói nem feltétlenül egyenlőek egymással. Például a feltétel 0.1 == 0.1fhamis lesz a típus pontosságának elvesztése miatt float, míg a feltétel 0.5 == 0.5figaz lesz, mert ezek a számok végesek a bináris ábrázolásban. Azonban az öntési feltétel (float) 0.1 == 0.1fis igaz lesz, mert egy kevésbé pontos típusra öntve elvesznek azok a bitek, amelyek a két állandót eltérnek egymástól.

A valós számokkal végzett aritmetikai műveletek szintén pontatlanok, és gyakran előfordulnak valamilyen lebegő hibájuk [45] . A legnagyobb hiba akkor fordul elő, ha olyan értékekkel dolgozik, amelyek közel állnak egy adott típusnál lehetséges minimumhoz. Ezenkívül a hiba nagynak bizonyulhat, ha egyidejűleg nagyon kicsi (≪ 1) és nagyon nagy számok (≫ 1) felett számítunk. Egyes esetekben a hiba csökkenthető az algoritmusok és számítási módszerek megváltoztatásával. Például, ha a többszörös összeadást szorzással helyettesíti, a hiba annyiszor csökkenhet, ahányszor eredetileg összeadási műveletek voltak.

Szintén a fejlécfájlban math.htalálható két további típus float_tés double_t, amelyek legalább a floatés típusoknak doublemegfelelnek, de eltérhetnek tőlük. float_tA és típusok double_ta C99 szabványban szerepelnek , az alaptípusokhoz való megfelelésüket a makró értéke határozza meg FLT_EVAL_METHOD.

Valódi adattípusok
Adattípus A méret Alapértelmezett
float 32 bites IEC 60559 ( IEEE 754 ), a C szabvány F kiterjesztése [46] [k] , egyetlen precíziós szám
double 64 bites IEC 60559 (IEEE 754), a C szabvány F kiterjesztése [46] [k] , kettős precíziós szám
long double minimum 64 bit megvalósítás függő
float_t(C99) minimum 32 bit alaptípustól függ
double_t(C99) minimum 64 bit alaptípustól függ
További típusok megfelelése az alaptípusoknak [47]
FLT_EVAL_METHOD float_t double_t
egy float double
2 double double
3 long double long double

Strings

Null végű karakterláncok

Noha a C-ben nincs speciális típusa a karakterláncoknak, a null-végződésű karakterláncok erősen használatosak a nyelvben. Az ASCII karakterláncok egy típusú tömbként vannak deklarálva, charamelynek utolsó eleme a karakterkód 0( '\0'). Az UTF-8 karakterláncokat ugyanabban a formátumban szokás tárolni . Az ASCII karakterláncokkal működő összes függvény azonban minden karaktert bájtnak tekint, ami korlátozza a szabványos függvények használatát ennek a kódolásnak a használatakor.

Annak ellenére, hogy széles körben elterjedt a null-végű karakterláncok gondolata, és néhány algoritmusban kényelmesen használhatók, számos komoly hátrányuk van.

  1. Az, hogy a karakterlánc végére terminálkaraktert kell hozzáadni, nem teszi lehetővé, hogy egy részstringet másolás nélkül kapjunk, és a nyelv nem biztosít funkciókat a részkarakterláncra és annak hosszára mutató mutatóval való munkavégzéshez.
  2. Ha a bemeneti adatokon alapuló algoritmus eredményéhez előzetesen memóriát kell lefoglalni, akkor minden alkalommal be kell járni a teljes karakterláncot a hosszának kiszámításához.
  3. Ha nagy mennyiségű szöveggel dolgozik, a hosszszámítás szűk keresztmetszetet jelenthet .
  4. Ha olyan karakterlánccal dolgozik, amely tévedésből nem nulla véget ért, meghatározatlan programviselkedéshez vezethet, beleértve a szegmentálási hibákat, a puffertúlcsordulási hibákat és a biztonsági réseket .

Modern körülmények között, amikor a kódteljesítmény prioritást élvez a memóriafelhasználással szemben, hatékonyabb és egyszerűbb lehet olyan struktúrákat használni, amelyek magát a karakterláncot és annak méretét is tartalmazzák [48] , például:

struct string_t { char * str ; // mutató a karakterláncra size_t str_size ; // karakterlánc mérete }; typedef struct string_t string_t ; // alternatív név a kód egyszerűsítésére

Egy alternatív, alacsony memóriaigényű karakterláncméret-tárolási megközelítés az lenne, ha a karakterláncot változó hosszúságú formátumban a méretével előtagozná.. Hasonló megközelítést alkalmaznak a protokollpufferekben is , azonban csak az adatátvitel szakaszában, de nem a tárolásukban.

String literálok

A C-beli karakterlánc-literálok eredendően állandók [10] . Deklarációkor dupla idézőjelek közé kerülnek, a terminátort 0a fordító automatikusan hozzáadja. A karakterlánc-literál hozzárendelésének két módja van: mutató és érték alapján. Mutatós hozzárendelés char *esetén a típusváltozóba beírunk egy változtathatatlan karakterláncra mutató mutatót, azaz konstans karakterláncot képezünk. Ha egy string literált ad meg egy tömbben, akkor a karakterlánc a verem területére kerül átmásolásra.

#include <stdio.h> #include <karakterlánc.h> int main ( érvénytelen ) { const char * s1 = "Const string" ; char s2 [] = "Módosítható karakterlánc" ; memcpy ( s2 , " c " , strlen ( " c " )); // módosítsa az első betűt kicsire tesz ( s2 ); // megjelenik a sor szövege memcpy (( char * ) s1 , "to" , strlen ( "to" )); // szegmentálási hiba tesz ( s1 ); // sor nem kerül végrehajtásra }

Mivel a karakterláncok szabályos karaktertömbök, literálok helyett inicializálók is használhatók, mindaddig, amíg minden karakter 1 bájtban elfér:

char s [] = { 'I' , 'n' , 'i' , 't' , 'i' , 'a' , 'l' , 'i' , 'z' , 'e' , ​​'r' , '\0' };

A gyakorlatban azonban ennek a megközelítésnek csak rendkívül ritka esetekben van értelme, amikor nem szükséges lezáró nullát hozzáadni egy ASCII karakterlánchoz.

Széles vonalak Típuskódolás wchar_ta platformtól függően
Felület Kódolás
GNU/Linux USC-4 [49]
Mac operációs rendszer
ablakok USC-2 [50]
AIX
FreeBSD A területtől függ

nincs dokumentálva [50]

Solaris

A szokásos karakterláncok alternatívája a széles karakterlánc, amelyben minden karakter egy speciális típusban van tárolva wchar_t. A szabvány által megadott típusnak képesnek kell lennie arra, hogy magában foglalja a létező legnagyobb területi beállítások összes karakterét . A széles karakterláncokkal való munkavégzés funkciói a fejlécfájlban wchar.h, a széles karakterekkel való munkavégzés funkciói pedig a fejlécfájlban találhatók wctype.h.

A széles karakterláncok karakterlánc-literáljának deklarálásakor a módosítót használjuk L:

const wchar_t * wide_str = L "Széles karakterlánc" ;

A formázott kimenet a specifikátort használja %ls, de a méretmeghatározó, ha adott, bájtokban van megadva, nem karakterekben [51] .

A típust wchar_túgy találták ki, hogy bármilyen karakter elférjen benne, széles karakterláncok pedig - bármilyen területi karakterláncok tárolására, de ennek eredményeként az API kényelmetlennek bizonyult, a megvalósítások pedig platformfüggőek voltak. Így a Windows platformon 16 bitet választottak a típus méretének wchar_t, majd később megjelent az UTF-32 szabvány, így wchar_ta Windows platformon a típus már nem fér bele az UTF-32 kódolásból származó összes karakterbe, aminek következtében ennek a típusnak a jelentése elvész [50] . Ugyanakkor Linux [49] és macOS platformokon ez a típus 32 bitet vesz igénybe, így a típus nem alkalmas többplatformos feladatok megvalósítására.wchar_t

Többbájtos karakterláncok

Számos különböző kódolás létezik, amelyekben egyetlen karakter különböző számú bájttal programozható. Az ilyen kódolásokat multibyte-nak nevezik. Az UTF-8 rájuk is vonatkozik . A C egy sor funkcióval rendelkezik a karakterláncok konvertálásához az aktuális nyelvterületen belüli többbájtosról szélesre és fordítva. A többbájtos karakterekkel való munkavégzés funkcióinak előtagja vagy utótagja mbvan, és leírásuk a fejlécfájlban található stdlib.h. A többbyte-os karakterláncok támogatásához a C programokban az ilyen karakterláncokat támogatni kell az aktuális területi szinten . A kódolás kifejezett beállításához módosíthatja az aktuális területi beállítást setlocale()a locale.h. A nyelvi beállítások kódolásának meghatározását azonban támogatnia kell a használt szabványos könyvtárnak. Például a Glibc szabványkönyvtár teljes mértékben támogatja az UTF-8 kódolást, és képes a szöveget sok más kódolásra konvertálni [52] .

A C11 szabványtól kezdve a nyelv támogatja a 16 bites és 32 bites szélességű többbyte-os karakterláncokat is megfelelő karaktertípusokkal char16_tés char32_tfejlécfájlból uchar.h, valamint az UTF-8 karakterlánc-literálok deklarálását a u8. 16 bites és 32 bites karakterláncok használhatók UTF-16 és UTF-32 kódolások tárolására, ha a uchar.hmakródefiníciók __STDC_UTF_16__és a fejlécfájlban vannak megadva __STDC_UTF_32__. A karakterlánc-literálok megadásához ezekben a formátumokban módosítókat használnak: u16 bites karakterláncokhoz és U32 bites karakterláncokhoz. Példák karakterlánc-literálok deklarálására többbájtos karakterláncokhoz:

const char * s8 = u8 "UTF-8 többbájtos karakterlánc" ; const char16_t * s16 = u "16 bites többbájtos karakterlánc" ; const char32_t * s32 = U "32 bites többbájtos karakterlánc" ;

Ne feledje, hogy a c16rtomb()16 bites karakterláncból többbájtos karakterláncokká konvertáló funkció nem működik a tervezett módon, és a C11 szabványban azt találták, hogy nem tud lefordítani UTF-16-ról UTF-8-ra [53] . Ennek a függvénynek a javítása a fordító konkrét megvalósításától függhet.

Egyéni típusok

Felsorolások

Az enumok elnevezett egész számok konstansok halmaza, amelyeket a kulcsszó jelöl enum. Ha egy konstans nincs számhoz társítva, akkor automatikusan vagy 0a lista első konstansához, vagy az előző konstansban megadottnál eggyel nagyobb számhoz kerül. Ebben az esetben maga a felsorolási adattípus valójában bármilyen előjeles vagy előjel nélküli primitív típusnak felelhet meg, amelynek tartományába az összes felsorolási érték belefér; A fordító dönti el, hogy melyik típust használja. Az állandók explicit értékeinek azonban olyan kifejezéseknek kell lenniük, mint a int[18] .

A felsorolás típusa névtelen is lehet, ha a felsorolásnév nincs megadva. A két különböző enumban megadott állandók két különböző adattípusúak, függetlenül attól, hogy az enumok nevesek vagy névtelenek.

A gyakorlatban a felsorolásokat gyakran használják véges automaták állapotainak jelzésére , működési módok vagy paraméterértékek beállítására [54] , egész szám konstansok létrehozására, valamint bármilyen egyedi objektum vagy tulajdonság felsorolására [55] .

Struktúrák

A struktúrák különböző adattípusok változóinak kombinációi ugyanazon a memóriaterületen belül; kulcsszóval jelöljük struct. A struktúrán belüli változókat a struktúra mezőinek nevezzük. A címtér szempontjából a mezők mindig ugyanabban a sorrendben követik egymást, ahogyan meg vannak adva, de a fordítók egymáshoz igazíthatják a mezőcímeket, hogy egy adott architektúrára optimalizálhassák. Így valójában a mező nagyobb méretű lehet, mint a programban megadott.

Minden mezőnek van egy bizonyos eltolása a struktúra címéhez és egy méretéhez képest. offsetof()Az eltolás a fejlécfájl makrójával érhető el stddef.h. Ebben az esetben az eltolás az előző mezők igazításától és méretétől függ. A mező méretét általában a szerkezet igazítása határozza meg: ha a mező adattípus igazítási mérete kisebb, mint a struktúra igazítás értéke, akkor a mező méretét a szerkezet igazítása határozza meg. Az adattípus-igazítás a fejlécfájl alignof()[f] makrójával érhető el stdalign.h. Maga a szerkezet mérete az összes mezőjének teljes mérete, beleértve az igazítást is. Ugyanakkor egyes fordítók speciális attribútumokat biztosítanak, amelyek lehetővé teszik a struktúrák becsomagolását, eltávolítva belőlük az igazításokat [56] .

A struktúramezőket a mező definíciója és a bitek száma után kettősponttal elválasztott bitekben lehet kifejezetten méretre állítani, ami a mező típusától függetlenül korlátozza a lehetséges értékük tartományát. Ez a megközelítés a jelzők és bitmaszkok alternatívájaként használható a hozzáférésükhöz. A bitek számának megadása azonban nem szünteti meg a struktúrák mezőinek lehetséges igazítását a memóriában. A bitmezőkkel való munkavégzésnek számos korlátja van: nem lehet hozzájuk operátort sizeofvagy makrót alkalmazni alignof(), nem lehet rájuk mutatni.

Egyesületek

Az uniókra akkor van szükség, ha ugyanarra a változóra szeretne hivatkozni különböző adattípusokként; kulcsszóval jelöljük union. Az unión belül tetszőleges számú metsző mező deklarálható, amelyek valójában ugyanahhoz a memóriaterülethez biztosítanak hozzáférést, mint különböző adattípusok. Az unió méretét a fordító választja ki az unió legnagyobb mezőjének mérete alapján. Figyelembe kell venni, hogy az unió egy mezőjének megváltoztatása az összes többi mező változásához vezet, de garantáltan csak a megváltozott mező értéke lesz helyes.

A szakszervezetek kényelmesebb alternatívaként szolgálhatnak a mutató tetszőleges típusra való öntésére. Például egy struktúrában elhelyezett unióval dinamikusan változó adattípusú objektumokat hozhat létre:

Struktúrakód az adattípus menet közbeni megváltoztatásához #include <stddef.h> enum value_type_t { VALUE_TYPE_LONG , // egész szám VALUE_TYPE_DOUBLE , // valós szám VALUE_TYPE_STRING , // karakterlánc VALUE_TYPE_BINARY , // tetszőleges adat }; struct binary_t { érvénytelen * adat ; // mutat az adatokra size_t data_size ; // adatméret }; struct string_t { char * str ; // mutató a karakterláncra size_t str_size ; // karakterlánc mérete }; union value_contents_t { long as_long ; // értéket egész számként double as_double ; // valós számként struct string_t as_string ; // érték karakterláncként struct binary_t as_binary ; // érték tetszőleges adatként }; struktúra érték_t { enum érték_típus_t típusa ; // érték tipusa union value_contents_t contents ; // értéktartalom }; Tömbök

A C tömbök primitívek, és csak szintaktikai absztrakciót jelentenek a mutató aritmetikája felett . A tömb maga egy memóriaterületre mutató mutató, így a tömbdimenzióról és annak határairól minden információ csak fordítási időben érhető el a típusdeklaráció szerint. A tömbök lehetnek egydimenziósak vagy többdimenziósak, de egy tömbelem eléréséhez egyszerűen ki kell számítani az eltolást a tömb kezdetének címéhez képest. Mivel a tömbök címaritmetikán alapulnak, lehetséges indexek használata nélkül is dolgozni velük [57] . Így például a következő két példa 10 szám kiolvasására a bemeneti adatfolyamból megegyezik egymással:

Az indexeken keresztül végzett munka összehasonlítása a címaritmetikával végzett munkával
Példakód az indexek feldolgozásához Példakód a címaritmetika kezeléséhez
#include <stdio.h> int a [ 10 ] = { 0 }; // Nulla inicializálás unsigned int count = sizeof ( a ) / sizeof ( a [ 0 ]); for ( int i = 0 ; i < count ; ++ i ) {     int * ptr = &a [ i ]; // Mutató az aktuális tömbelemre int n = scanf ( "%8d" , ptr );         if ( n != 1 ) {         perror ( "Nem sikerült beolvasni az értéket" );         // A hibatörés kezelése ;            } } #include <stdio.h> int a [ 10 ] = { 0 }; // Nulla inicializálás unsigned int count = sizeof ( a ) / sizeof ( a [ 0 ]); int * a_end = a + count ; // Mutató az utolsó után következő elemre for ( int * ptr = a ; ptr != a_end ; ++ ptr ) { int n = scanf ( "%8d" , ptr ); if ( n != 1 ) { perror ( "Nem sikerült beolvasni az értéket" ); // A hibatörés kezelése ; } }

Az ismert méretű tömbök hosszát a fordításkor számítjuk ki. A C99 szabvány bevezette a változó hosszúságú tömbök deklarálásának lehetőségét, amelyek hossza futás közben beállítható. Az ilyen tömbök a veremterületről kapnak memóriát, ezért óvatosan kell használni őket, ha a méretük a programon kívülről is beállítható. A dinamikus memóriafoglalástól eltérően a veremterület megengedett méretének túllépése előre nem látható következményekhez vezethet, és a negatív tömbhossz meghatározatlan viselkedés . A C11 -től kezdve a változó hosszúságú tömbök nem kötelezőek a fordítók számára, a támogatás hiányát pedig egy makró jelenléte határozza meg __STDC_NO_VLA__[58] .

A lokális vagy globális változóként deklarált rögzített méretű tömbök inicializálhatók úgy, hogy kapcsos kapcsos zárójelekkel és vesszővel elválasztva listázzák a tömbelemeket. A globális tömb inicializálói csak olyan kifejezéseket használhatnak, amelyeket a fordításkor kiértékelnek [59] . Az ilyen kifejezésekben használt változókat konstansként kell deklarálni, a módosítóval const. A helyi tömbök esetében az inicializálók tartalmazhatnak kifejezéseket függvényhívásokkal és más változók használatával, beleértve a deklarált tömbre mutató mutatót is.

A C99 szabvány óta megengedett egy tetszőleges hosszúságú tömb deklarálása a struktúrák utolsó elemeként, amelyet a gyakorlatban széles körben használnak, és különböző fordítók támogatnak. Egy ilyen tömb mérete a struktúrához lefoglalt memória mennyiségétől függ. Ebben az esetben nem deklarálhat ilyen struktúrák tömbjét, és nem helyezheti el őket más struktúrákba. Az ilyen struktúrákon végzett műveleteknél általában figyelmen kívül hagynak egy tetszőleges hosszúságú tömböt, beleértve a struktúra méretének kiszámítását is, és a tömbön túllépés definiálatlan viselkedést von maga után [60] .

A C nyelv nem vezérli a tömb határokon kívüli határait, ezért magának a programozónak kell figyelnie a tömbökkel végzett munkát. A tömbfeldolgozás során fellépő hibák nem mindig befolyásolják közvetlenül a program végrehajtását, de szegmentációs hibákhoz és sebezhetőségekhez vezethetnek .

Szinonimák beírása

A C nyelv lehetővé teszi saját típusnevek létrehozását a typedef. Alternatív nevek adhatók mind a rendszertípusoknak, mind a felhasználó által meghatározottaknak. Az ilyen nevek a globális névtérben vannak deklarálva, és nem ütköznek a szerkezet, a felsorolás és az uniótípusok neveivel.

Alternatív nevek egyaránt használhatók a kód egyszerűsítésére és az absztrakciós szintek létrehozására. Például egyes rendszertípusok lerövidíthetők, hogy a kód olvashatóbb legyen, vagy egységesebb legyen a felhasználói kódban:

#include <stdint.h> typedef int32_t i32_t ; typedef int_fast32_t i32fast_t ; typedef int_least32_t i32least_t ; typedef uint32_t u32_t ; typedef uint_fast32_t u32fast_t ; typedef uint_least32_t u32least_t ;

Az absztrakcióra példa az operációs rendszerek fejlécfájljaiban található típusnevek. Például a POSIX szabvány meghatároz egy típust pid_ta numerikus folyamatazonosító tárolására. Valójában ez a típus egy alternatív neve néhány primitív típushoz, például:

typedef int __kernel_pid_t ; typedef __kernel_pid_t __pid_t typedef __pid_t pid_t ;

Mivel az alternatív nevű típusok csak szinonimái az eredeti típusoknak, a teljes kompatibilitás és felcserélhetőség megmarad közöttük.

Előfeldolgozó

Az előfeldolgozó a fordítás előtt működik, és átalakítja a programfájl szövegét a benne talált vagy az előfeldolgozónak átadott direktívák szerint . Technikailag az előfeldolgozó többféleképpen is megvalósítható, de logikailag kényelmes egy külön modulnak tekinteni, amely minden fordításra szánt fájlt feldolgoz, és kialakítja a szöveget, amely azután a fordító bemenetére kerül. Az előfeldolgozó olyan sorokat keres a szövegben, amelyek karakterrel kezdődnek #, majd az előfeldolgozó direktíváit követik. Minden, ami nem tartozik az előfeldolgozó direktívákhoz, és az irányelvek szerint nincs kizárva a fordításból, változatlanul átkerül a fordító bemenetére.

Az előfeldolgozó jellemzői a következők:

  • adott lexéma helyettesítése szöveggel a direktíva segítségével #define, beleértve a paraméterezett szövegsablonok létrehozásának lehetőségét (a függvényekhez hasonlóan), valamint az ilyen helyettesítések törlését, ami lehetővé teszi a programszöveg korlátozott területein a helyettesítést;
  • feltételes beágyazás és töredékek eltávolítása a szövegből, beleértve magukat az irányelveket is, a #ifdef, #ifndef, #if, #elseés feltételes parancsok használatával #endif;
  • beágyazhat szöveget egy másik fájlból az aktuális fájlba a #include.

Fontos megérteni, hogy az előfeldolgozó csak szöveghelyettesítést biztosít, nem veszi figyelembe a nyelv szintaxisát és szemantikáját. Így például a makródefiníciók #defineelőfordulhatnak függvényekben vagy típusdefiníciókban, a feltételes fordítási direktívák pedig a kód bármely részének kizárásához vezethetnek a program lefordított szövegéből, függetlenül a nyelv nyelvtanától. A paraméteres makró hívása azért is különbözik a függvény meghívásától, mert a vesszővel elválasztott argumentumok szemantikája nincs elemezni. Így például lehetetlen egy tömb inicializálását átadni egy parametrikus makró argumentumainak, mivel az elemei is vesszővel vannak elválasztva:

#define array_of(type, array) (((típus) []) (tömb)) int * a ; a = tömb_of ( int , { 1 , 2 , 3 }); // fordítási hiba: // Az "array_of" makró 4 argumentumot adott át, de csak 2

A makródefiníciókat gyakran használják a könyvtárak különböző verzióival való kompatibilitás biztosítására, amelyek megváltoztatták az API -kat , beleértve a programkönyvtár verziójától függően bizonyos kódszakaszokat. Ebből a célból a könyvtárak gyakran adnak hozzá makródefiníciókat, amelyek leírják a verziójukat [61] , és néha olyan paraméterekkel ellátott makrókat, amelyek az aktuális verziót összehasonlítják az előfeldolgozóban [62] megadottal . A makródefiníciókat a program egyes részeinek feltételes fordítására is használják , például bizonyos további funkciók támogatására.

A paraméterekkel rendelkező makródefiníciókat széles körben használják a C programokban általános függvények analógjainak létrehozására . Korábban inline függvények megvalósítására is szolgáltak, de a C99 szabvány óta ez az igény a inline-függvények kiegészítése miatt megszűnt. Tekintettel azonban arra, hogy a paraméterekkel rendelkező makródefiníciók nem függvények, hanem hasonló módon hívják őket, váratlan problémák léphetnek fel programozói hiba miatt, beleértve a makródefinícióból származó kód egy részének feldolgozását [63] és a hibás prioritásokat. műveletek végrehajtása [64] . Példa a hibás kódra a négyzetesítési makró:

#include <stdio.h> int main ( érvénytelen ) { #define SQR(x) x * x printf ( "%d" , SQR ( 5 )); // minden helyes, 5*5=25 printf ( "%d" , SQR ( 5 + 0 )); // állítólag 25, de 5-öt ad ki (5+0*5+0) printf ( "%d" , SQR ( 4/3 ) ) ; // minden helyes, 1 (mert 4/3=1, 1*4=4, 4/3=1) printf ( "%d" , SQR ( 5/2 ) ) ; // 4-nek (2*2) kellene lennie, de 5-öt ad ki (5/2*5/2) return 0 ; }

A fenti példában az a hiba, hogy a makró argumentum tartalma a szövegbe úgy van behelyettesítve, ahogy van, anélkül, hogy figyelembe vennénk a műveletek elsőbbségét. Ilyen esetekben a inline-függvényeket kell használnia, vagy kifejezetten prioritást kell adnia az operátoroknak olyan kifejezésekben, amelyek makróparamétereket használnak zárójelben:

#include <stdio.h> int main ( érvénytelen ) { #define SQR(x) ((x) * (x)) printf ( "%d" , SQR ( 4 + 1 )); // igaz, 25 return 0 ; }

C programozás

Programstruktúra

Modulok

A program olyan C fájlok halmaza, amelyek objektumfájlokká fordíthatók . Az objektumfájlok ezután egy összekapcsolási lépésen mennek keresztül egymással, valamint a külső könyvtárakkal, ami a végső végrehajtható fájlt vagy könyvtárat eredményezi . A fájlok egymással, valamint könyvtárakkal való összekapcsolásához szükség van az egyes fájlokban a használt funkciók prototípusainak, a külső változóknak és a szükséges adattípusoknak a leírására. Szokásos az ilyen adatokat külön fejlécfájlokba helyezni , amelyek direktívával #include kapcsolódnak azokban a fájlokban, ahol ez vagy az a funkció szükséges, és lehetővé teszik egy modulrendszerhez hasonló rendszer megszervezését. Ebben az esetben a modul lehet:

  • egyedi fájlok forráskóddal ellátott halmaza, amelyekhez a felület fejlécfájlok formájában jelenik meg;
  • objektumkönyvtár vagy annak egy része, a megfelelő fejlécfájlokkal;
  • egy vagy több fejlécfájl önálló halmaza (interfészkönyvtár);
  • statikus könyvtár vagy annak egy része megfelelő fejlécfájlokkal;
  • dinamikus könyvtár vagy annak egy része megfelelő fejlécfájlokkal.

Mivel az irányelv #includecsak egy másik fájl szövegét helyettesíti az előfeldolgozó szakaszban , ugyanazon fájl többszöri beépítése fordítási hibákhoz vezethet. Ezért az ilyen fájlok makrók és [65] használatával védelmet használnak az újraengedélyezés ellen . #define#ifndef

Forráskód fájlok

A C forráskód fájl törzse globális adatdefiníciók, típusok és függvények halmazából áll. A és a specifikációkkal deklarált globális változók és függvények csak abban a fájlban staticérhetők inlineel, amelyben deklarálva vannak, vagy ha az egyik fájl egy másikba a #include. Ebben az esetben a fejlécfájlban a szóval deklarált függvények és változók staticminden alkalommal újra létrejönnek, amikor a fejlécfájl a következő forráskóddal rendelkező fájlhoz kapcsolódik. A külső specifikációval deklarált globális változókat és függvényprototípusokat a rendszer más fájlokból származónak tekinti. Vagyis a leírásnak megfelelően használhatók; feltételezzük, hogy a program felépítése után a linker összekapcsolja őket a fájljaikban leírt eredeti objektumokkal és függvényekkel.

staticA globális változók és függvények – a és kivételével – inlinemás fájlokból is elérhetők, feltéve, hogy ott megfelelően deklarálva vannak a specifikátorral extern. A módosítóval deklarált változók és függvények staticmás fájlokban is elérhetők, de csak akkor, ha a címüket mutató adja át. Írja be a deklarációkat typedef, structés unionnem importálható más fájlba. Ha más fájlokban kell használni őket, akkor ott kell megkettőzni, vagy külön fejlécfájlba helyezni. Ugyanez vonatkozik a inline-függvényekre is.

Program belépési pont

Egy végrehajtható program esetében a szabványos belépési pont egy függvény main, amely nem lehet statikus, és csak a programban kell lennie. A program végrehajtása a függvény első utasításától kezdődik és a main()kilépésig folytatódik, ezután a program leáll, és visszaküldi az operációs rendszernek a munkája eredményének absztrakt egész kódját.

Érvényes függvényprototípusok main()[66]
nincsenek érvek Parancssori argumentumokkal
int main ( void ); int main ( int argc , char ** argv );

Meghíváskor a változót argca programnak átadott argumentumok száma adja át, beleértve magának a programnak az elérési útját is, így az argc változó általában 1-nél nem kisebb értéket tartalmaz. argvMaga a programindító sor tömbként kerül átadásra a változónak szöveges karakterláncokból, amelyek utolsó eleme a NULL. A fordító garantálja, hogy main()a programban lévő összes globális változó inicializálva lesz a függvény futtatásakor [67] .

Ennek eredményeként a függvény main()bármilyen egész számot visszaadhat a típusú értéktartományban , amelyet a program visszatérési kódjaként inttovábbít az operációs rendszernek vagy más környezetnek [66] . A nyelvi szabvány nem határozza meg a visszatérési kódok jelentését [68] . Általában az operációs rendszer, ahol a programok futnak, rendelkezik valamilyen eszközzel a visszatérési kód értékének lekérésére és elemzésére. Néha vannak bizonyos konvenciók ezeknek a kódoknak a jelentésével kapcsolatban. Az általános konvenció az, hogy a nulla visszatérési kód a program sikeres befejezését jelzi, míg a nullától eltérő érték hibakódot jelent. A fejlécfájl két általános makródefiníciót határoz meg és , amelyek megfelelnek a program sikeres és sikertelen befejezésének [68] . A visszatérési kódok olyan alkalmazásokon belül is használhatók, amelyek több folyamatot tartalmaznak a folyamatok közötti kommunikáció biztosítására, ebben az esetben az alkalmazás maga határozza meg az egyes visszatérési kódok szemantikai jelentését. stdlib.hEXIT_SUCCESSEXIT_FAILURE

Munka a memóriával

Memóriamodell

A C 4 módot biztosít a memória lefoglalására, amelyek meghatározzák a változó élettartamát és inicializálásának pillanatát [67] .

Memóriakiosztási módszerek [67]
Kiválasztási módszer Célok Kiválasztás ideje kiadás ideje Általános költségek
Statikus memóriafoglalás Globális változók és kulcsszóval jelölt változók static(de nélkül _Thread_local) A program indításakor A program végén Hiányzó
Memóriakiosztás szál szinten Kulcsszóval jelölt változók_Thread_local Amikor elindul a cérna A patak végén A szál létrehozásakor
Automatikus memóriafoglalás Függvényargumentumok és visszatérési értékek, függvények lokális változói, beleértve a változó hosszúságú regisztereket és tömböket Funkciók veremszintű hívásakor . Automatikusan a funkciók befejezésekor Lényegtelen, mivel csak a verem tetejére mutató mutató változik
Dinamikus memóriafoglalás A függvényeken keresztül lefoglalt memória malloc(), calloc()ésrealloc() Manuálisan a kupacból a használt függvény meghívásának pillanatában. A funkció manuális használatávalfree() Kiosztáshoz és kiadáshoz egyaránt nagy

Mindezek az adattárolási módszerek különböző helyzetekben alkalmasak, és megvannak a maga előnyei és hátrányai. A globális változók nem teszik lehetővé újra belépő algoritmusok írását , és az automatikus memóriafoglalás nem teszi lehetővé tetszőleges memóriaterület visszaadását függvényhívásból. Az automatikus lefoglalás szintén nem alkalmas nagy mennyiségű memória lefoglalására, mivel verem- vagy kupacsérüléshez vezethet [69] . A dinamikus memória nem rendelkezik ezekkel a hiányosságokkal, de használatakor nagy a többletköltsége, és nehezebben használható.

Ahol lehetséges, az automatikus vagy statikus memóriafoglalást részesítjük előnyben: az objektumok tárolásának ezt a módját a fordító vezérli , ami megszabadítja a programozót a kézi memóriafoglalás és -felszabadítás fáradságától, ami általában a nehezen fellelhető memóriaszivárgások forrása . szegmentálási hibák és a program hibáinak újrafelszabadítása . Sajnos sok adatstruktúra futási időben változó méretű, így mivel az automatikusan és statikusan lefoglalt területeknek a fordítási időben ismert fix mérettel kell rendelkezniük, nagyon gyakori a dinamikus kiosztás használata.

Az automatikusan allokált változók esetén egy módosító registersegítségével utalhat a fordítóprogramra, hogy gyorsan hozzáférjen hozzájuk. Az ilyen változókat a processzor regisztereibe lehet helyezni. A korlátozott számú regiszter és a fordítóoptimalizálás miatt előfordulhat, hogy a változók a közönséges memóriába kerülnek, ennek ellenére a programból nem lehet majd mutatót kapni rájuk [70] . A módosító registeraz egyetlen, amely a függvény argumentumaiban adható meg [71] .

Memóriacímzés

A C nyelv lineáris memóriacímzést örökölt, amikor struktúrákkal, tömbökkel és lefoglalt memóriaterületekkel dolgozott. A nyelvi szabvány azt is lehetővé teszi, hogy összehasonlítási műveleteket hajtsanak végre nullmutatókon és tömbökön, struktúrákon és lefoglalt memóriaterületeken belüli címeken. Az utolsó utáni tömbelem címével is dolgozhatunk, ami az írási algoritmusok megkönnyítése érdekében történik. A különböző változókhoz (vagy memóriaterületekhez) kapott címmutatók összehasonlítását azonban nem szabad elvégezni, mivel az eredmény egy adott fordító megvalósításától függ [72] .

Memóriaábrázolás

A program memóriaábrázolása a hardver architektúrától, az operációs rendszertől és a fordítótól függ. Így például a legtöbb architektúrán a verem csökken, de vannak olyan architektúrák, ahol a verem nő [73] . A verem és a kupac közötti határ részben védhető a verem túlcsordulásától egy speciális memóriaterülettel [74] . A könyvtárak adatainak és kódjának helye pedig a fordítási lehetőségektől függhet [75] . A C szabvány elvonatkoztatja a megvalósítást, és lehetővé teszi hordozható kód írását, de a folyamat memóriaszerkezetének megértése segít a hibakeresésben és a biztonságos és hibatűrő alkalmazások írásában.

A folyamatmemória tipikus ábrázolása Unix-szerű operációs rendszerekben

Amikor egy programot elindítanak egy végrehajtható fájlból, a processzor utasításai (gépi kód) és az inicializált adatok importálódnak a RAM-ba. main()Ugyanakkor a parancssori argumentumok ( amelyek a második argumentumban a következő aláírással rendelkező függvényekben érhetők el int argc, char ** argv) és a környezeti változók importálódnak magasabb címekre .

Az inicializálatlan adatterület olyan globális változókat tartalmaz (beleértve a ként deklaráltakat is static), amelyek nincsenek inicializálva a programkódban. Az ilyen változók alapértelmezés szerint nullára vannak inicializálva a program indítása után. Az inicializált adatok területe - az adatszegmens - globális változókat is tartalmaz, de ez a terület azokat a változókat tartalmazza, amelyek kezdeti értéket kaptak. A megváltoztathatatlan adatok, beleértve a módosítóval deklarált változókat const, karakterlánc-literálokat és egyéb összetett literálokat, a programszöveg szegmensbe kerülnek. A programszöveg szegmens végrehajtható kódot is tartalmaz, és csak olvasható, így az ebből a szegmensből származó adatok módosításának kísérlete meghatározatlan viselkedést eredményez szegmentációs hiba formájában .

A veremterület funkcióhívásokhoz és helyi változókhoz kapcsolódó adatokat tartalmaz . Minden egyes függvényvégrehajtás előtt a verem kibővül, hogy illeszkedjen a függvénynek átadott argumentumokhoz. Munkája során a függvény a veremben lokális változókat tud lefoglalni, és memóriát lefoglalni rajta a változó hosszúságú tömbökhöz, illetve egyes fordítók lehetőséget is biztosítanak a veremen belüli memória lefoglalására olyan híváson keresztül alloca(), amely nem szerepel a nyelvi szabványban. . A függvény lejárta után a verem a hívás előtti értékre csökken, de ez nem biztos, hogy megtörténik, ha a veret rosszul kezeli. A dinamikusan lefoglalt memória a halomból származik .

Fontos részlet a véletlenszerű kitöltés jelenléte a verem és a felső terület között [77] , valamint az inicializált adatterület és a kupac között . Ez biztonsági okokból történik, például más funkciók egymásra halmozásának megakadályozása érdekében.

A dinamikus hivatkozási könyvtárak és a fájlrendszer fájlleképezései a verem és a kupac között helyezkednek el [78] .

Hibakezelés

A C nem rendelkezik beépített hibaellenőrző mechanizmusokkal, de számos általánosan elfogadott módszer létezik a hibák kezelésére a nyelv használatával. Általánosságban elmondható, hogy a hibatűrő kódokban a C hibák kezelésének gyakorlata nehézkes, gyakran ismétlődő konstrukciók írására kényszeríti az embert, amelyben az algoritmust hibakezeléssel kombinálják .

Hibajelzők és errno

A C nyelv aktívan használ egy speciális változót errnoa fejlécfájlból errno.h, amelyben a függvények beírják a hibakódot, miközben a hibajelző értéket adják vissza. Az eredmény hibaellenőrzéséhez az eredményt összehasonlítja a hibajelzővel, és ha egyezik, akkor elemezheti a tárolt hibakódot errnoa program javításához vagy hibakeresési üzenet megjelenítéséhez. A szabványos könyvtárban a szabvány gyakran csak a visszaadott hibajelzőket határozza meg, és a beállítás errnoimplementációfüggő [79] .

A következő értékek általában hibajelzőként működnek:

  • -1a típusra intolyan esetekben, amikor nem használunk negatív eredménytartományt [80] ;
  • -1típushoz ssize_t(POSIX) [81] ;
  • (size_t) -1típushoz size_t[80] ;
  • (time_t) -1amikor egyes funkciókat használ az idővel való munkavégzésre [80] ;
  • NULLmutatókhoz [80] ;
  • EOFfájlok streamelésekor [80] ;
  • nem nulla hibakód [80] .

Az a gyakorlat, hogy hibakód helyett hibajelzőt adunk vissza, bár megspórolja a függvénynek átadott argumentumok számát, bizonyos esetekben emberi tényező hatására hibákhoz vezet. Például gyakori, hogy a programozók figyelmen kívül hagyják a típusú eredmény ellenőrzését ssize_t, és magát az eredményt használják tovább a számításokban, ami finom hibákhoz vezet, ha a -1[82] -t adjuk vissza .

A helyes érték visszaadása hibajelzőként [82] tovább járul hozzá a hibák megjelenéséhez , ami egyben több ellenőrzésre, és ennek megfelelően több azonos típusú ismétlődő kód írására kényszeríti a programozót. Ezt a megközelítést olyan adatfolyamfüggvényeknél alkalmazzák, amelyek típusú objektumokkal dolgoznak FILE *: a hibajelző az érték EOF, amely egyben a fájlvégi jelző is. Ezért EOFnéha ellenőriznie kell a karakterfolyamot mind a fájl végének szempontjából a függvény használatával feof(), mind a hiba jelenlétét a ferror()[83] használatával . Ugyanakkor néhány EOF, a szabvány szerint visszatérő függvényt nem kell beállítani errno[79] .

Az egységes hibakezelési gyakorlat hiánya a szabványos könyvtárban az egyedi hibakezelési módszerek megjelenéséhez és az általánosan használt módszerek kombinációjához vezet a harmadik féltől származó projektekben. Például a systemd projektben egy hibakód és egy szám jelölőként való visszaadásának ötleteit -1kombinálták - negatív hibakódot ad vissza [84] . A GLib könyvtár pedig bevezette azt a gyakorlatot, hogy hibajelzőként logikai értéket adnak vissza, miközben a hiba részleteit egy speciális struktúrában helyezik el, amelyre a mutató a függvény utolsó argumentumán keresztül kerül vissza [85] . Hasonló megoldást használ az Enlightenment projekt is, amely szintén logikai típust használ jelölőként, de a szabványos könyvtárhoz hasonló hibainformációkat ad vissza - egy külön függvényen [86] keresztül , amelyet ellenőrizni kell, ha jelölőt adott vissza.

Hibakód visszaküldése

A hibajelzők alternatívája a hibakód közvetlen visszaadása, és a függvény eredményének visszaadása mutató argumentumokon keresztül. A POSIX szabvány fejlesztői ezt az utat választották, melynek funkcióiban a hibakódot típusszámként szokás visszaadni int. A típusérték visszaadása intazonban nem teszi egyértelművé, hogy a hibakódot küldi vissza, és nem a tokent, ami hibákhoz vezethet, ha az ilyen függvények eredményét az értékhez hasonlítják -1. A C11 szabvány K kiterjesztése egy speciális típust vezet be a errno_thibakód tárolására. Javasoljuk, hogy ezt a típust használja a felhasználói kódban a hibák visszaadásához, és ha a szabványos könyvtár nem biztosítja, akkor deklarálja azt [87] :

#ifndef __STDC_LIB_EXT1__ typedef int errno_t ; #endif

Ez a megközelítés a kód minőségének javítása mellett kiküszöböli a használatának szükségességét errno, amely lehetővé teszi, hogy újra belépő függvényekkel rendelkező könyvtárakat készítsen anélkül, hogy további könyvtárakat, például POSIX szálakat kellene tartalmaznia a megfelelő meghatározásához errno.

Hibák a matematikai függvényekben

Bonyolultabb a fejlécfájlból származó matematikai függvények hibáinak kezelése, math.hamelyben 3 típusú hiba fordulhat elő [88] :

  • túllép a bemeneti értékek tartományán;
  • véges bemeneti adatokra végtelen eredményt kapni;
  • az eredmény kívül esik a használt adattípus tartományán.

A három hibatípus közül kettőnek a megelőzése a bemeneti adatok érvényes értéktartományának ellenőrzésén múlik. Az eredmény kimenetét azonban a típus határain túl rendkívül nehéz megjósolni. Ezért a nyelvi szabvány lehetőséget biztosít a matematikai függvények hibaelemzésére. A C99 szabványtól kezdve ez az elemzés kétféleképpen lehetséges, attól függően, hogy mekkora értéket tárol a math_errhandling.

  1. Ha a bit be van állítva MATH_ERRNO, akkor a változót errnoelőször vissza kell állítani -ra 0, majd a matematikai függvény meghívása után ellenőrizze a hibákat EDOMés a ERANGE.
  2. Ha a bit be van állítva MATH_ERREXCEPT, akkor az esetleges matematikai hibákat a függvény korábban visszaállítja feclearexcept()a fejlécfájlból fenv.h, majd a matematikai függvény meghívása után a függvény segítségével teszteli azokat fetestexcept().

Ebben az esetben a hibakezelés módját a szabványos könyvtár konkrét megvalósítása határozza meg, és előfordulhat, hogy teljesen hiányzik. math_errhandlingEzért a platformfüggetlen kódban a [88] értékétől függően szükség lehet az eredmény ellenőrzésére egyszerre kétféleképpen .

Erőforrások felszabadítása

Általában egy hiba előfordulásához a függvénynek ki kell lépnie, és vissza kell adnia egy hibajelzőt. Ha egy funkcióban hiba léphet fel annak különböző részein, akkor a működése során lefoglalt erőforrásokat fel kell szabadítani a szivárgások elkerülése érdekében. Jó gyakorlat az erőforrások felszabadítása a függvényből való visszatérés előtt fordított sorrendben, hiba esetén pedig a fő függvény után fordított sorrendben return. gotoAz ilyen kiadások külön részeiben a [89] operátor segítségével ugorhat . Ez a megközelítés lehetővé teszi, hogy a végrehajtandó algoritmushoz nem kapcsolódó kódrészleteket magán az algoritmuson kívül helyezze át, növelve a kód olvashatóságát, és hasonló defera Go programozási nyelv operátorának munkájához . Az erőforrások felszabadítására egy példa látható alább, a példák részben .

A programon belüli erőforrások felszabadításához egy programkilépés-kezelő mechanizmus biztosított. A kezelők egy függvény segítségével vannak hozzárendelve, atexit()és mind a függvény végén main()utasítással return, mind a függvény végrehajtásakor végrehajtódnak exit(). Ebben az esetben a kezelőket nem hajtják végre a abort()és a _Exit()[90] függvények .

Az erőforrások felszabadítására egy program végén példa a globális változók számára lefoglalt memória felszabadítása. Annak ellenére, hogy a memória így vagy úgy felszabadul a program leállása után az operációs rendszer által, és nem szabad felszabadítani a program működése során szükséges memóriát [91] , az explicit felosztás előnyösebb, mivel ez teszi könnyebben megtalálja a memóriaszivárgást harmadik féltől származó eszközökkel, és csökkenti a hiba következtében fellépő memóriaszivárgás lehetőségét:

Minta programkód erőforrás kiadással #include <stdio.h> #include <stdlib.h> int számok_száma ; int * számok ; void free_numbers ( érvénytelen ) { ingyenes ( számok ); } int main ( int argc , char ** argv ) { if ( arg < 2 ) { kilépés ( EXIT_FAILURE ); } számok_száma = atoi ( argv [ 1 ]); if ( számok_száma <= 0 ) { kilépés ( EXIT_FAILURE ); } számok = calloc ( számok_száma , mérete ( * számok )); if ( ! számok ) { perror ( "Hiba a tömb memóriafoglalása során" ); kilépés ( EXIT_FAILURE ); } atexit ( szabad_számok ); // ... munka számtömbbel // Itt automatikusan meghívódik a free_numbers() kezelő return EXIT_SUCCESS ; }

Ennek a megközelítésnek az a hátránya, hogy a hozzárendelhető kezelők formátuma nem teszi lehetővé tetszőleges adatok átadását a függvénynek, ami lehetővé teszi, hogy csak globális változókhoz hozzon létre kezelőket.

Példák C programokra

Minimális C program

Egy minimális C program, amely nem igényel argumentumfeldolgozást, a következő:

int main ( érvénytelen ){}

Nem szabad operátort írni returna függvényhez main(). Ebben az esetben a szabvány szerint a függvény main()0-t ad vissza, végrehajtva a függvényhez rendelt összes kezelőt exit(). Ez azt feltételezi, hogy a program sikeresen befejeződött [40] .

Helló Világ!

szia világ! Kernighan és Ritchie " The C Programming Language " című könyvének első kiadásában található :

#include <stdio.h> int main ( void ) // Nem vesz fel argumentumokat { printf ( "Hello, world! \n " ); // '\n' - új sor visszatérése 0 ; // Sikeres programlezárás }

Ez a program kinyomtatja a Hello, world! ' szabványos kimeneten .

Hibakezelés a fájlolvasás példakénti használatával

Sok C függvény hibát jelezhet anélkül, hogy megtenné azt, amit kellett volna. A hibákat ellenőrizni kell, és helyesen kell reagálni rájuk, ideértve gyakran azt is, hogy a hibát egy függvényből magasabb szintre kell dobni elemzés céljából. Ugyanakkor az a funkció, amelyben hiba történt, újra beléptetővé tehető , ebben az esetben tévedésből a funkciónak nem szabad megváltoztatnia a bemeneti vagy kimeneti adatokat, ami lehetővé teszi a hibahelyzet kijavítása után biztonságos újraindítását.

A példa megvalósítja a C nyelvű fájl beolvasásának függvényét, de megköveteli, hogy a függvények fopen()és a POSIXfread() szabvány megfeleljen , ellenkező esetben előfordulhat, hogy nem állítják be a változót , ami nagymértékben megnehezíti mind a hibakeresést, mind az univerzális és biztonságos kód írását. Nem POSIX platformokon ennek a programnak a viselkedése meghatározatlan lesz hiba esetén . Az olvashatóságot javító fő algoritmus mögött a hibákkal kapcsolatos erőforrások felosztása áll, és az átmenet a [89] segítségével történik . errnogoto

Fájlolvasó példakód hibakezeléssel #include <errno.h> #include <stdio.h> #include <stdlib.h> // Határozza meg a hibakód tárolásának típusát, ha nincs megadva #ifndef __STDC_LIB_EXT1__ typedef int errno_t ; #endif enum { EOK = 0 , // az errno_t értéke a sikerre }; // Funkció a fájl tartalmának beolvasására errno_t get_file_contents ( const char * fájlnév , void ** contents_ptr , size_t * contents_size_ptr ) { FÁJL * f ; f = fopen ( fájlnév , "rb" ); if ( ! f ) { // A POSIX-ben az fopen() tévedésből errno-t állít be return errno ; } // Fájlméret lekérése fseek ( f , 0 , SEEK_END ); long contents_size = ftell ( f ); if ( contents_size == 0 ) { * contents_ptr = NULL ; * contents_size_ptr = 0 ; goto cleaning_fopen ; } visszatekerés ( f ); // Változó a visszaadott hibakód tárolására errno_t saved_errno ; érvénytelen * tartalom ; tartalom = malloc ( tartalom_méret ); if ( ! tartalom ) { mentve_errno = hiba ; goto aborting_fopen ; } // Olvassa el a fájl teljes tartalmát a tartalommutatóban size_t n ; n = fread ( tartalom , tartalom_méret , 1 , f ); if ( n == 0 ) { // Ne ellenőrizze a feof()-t, mert az fseek() után pufferelt // A POSIX fread() tévedésből errno-t állít be mentve_errno = hiba ; goto aborting_contents ; } // Visszaadja a lefoglalt memóriát és annak méretét * contents_ptr = tartalom ; * contents_size_ptr = contents_size ; // Erőforrás kiadás szakasz a sikerről tisztítás_fopen : fclose ( f ); visszatér EOK ; // Külön szakasz a források véletlen felszabadítására aborting_contents : ingyenes ( tartalom ); aborting_fopen : fclose ( f ); return saved_errno ; } int main ( int argc , char ** argv ) { if ( arg < 2 ) { return EXIT_FAILURE ; } const char * fájlnév = argv [ 1 ]; errno_t errnum ; érvénytelen * tartalom ; méret_t tartalom_méret ; errnum = get_file_contents ( fájlnév , & tartalom , & tartalom_mérete ); if ( errnum ) { charbuf [ 1024 ] ; const char * hiba_szöveg = strerror_r ( errnum , buf , sizeof ( buf )); fprintf ( stderr , "%s \n " , hiba_szöveg ); kilépés ( EXIT_FAILURE ); } printf ( "%.*s" , ( int ) contents_size , contents ); ingyenes ( tartalom ); return EXIT_SUCCESS ; }

Fejlesztő eszközök

Fordítók

Egyes fordítók más programozási nyelvek fordítóival (beleértve a C++- t is) csomagban vannak, vagy a szoftverfejlesztési környezet részét képezik .

  • A GNU Compiler Collection (GCC) teljes mértékben támogatja a C99 és C17 szabványokat ( C11 javításokkal) [92] . Támogatja a GNU-bővítményeket, a kódvédelmet fertőtlenítőkkel és számos további szolgáltatást, beleértve az attribútumokat is.
  • A Clang teljes mértékben támogatja a C99 [93] és C17 [94] szabványokat is . Úgy lett kifejlesztve, hogy nagyrészt kompatibilis legyen a GCC fordítóval, beleértve a GNU-bővítmények támogatását és a kódmentesítő védelmet.
A szabványos könyvtár megvalósításai

Annak ellenére, hogy a szabványos könyvtár a nyelvi szabvány része, megvalósításai elkülönülnek a fordítóktól. Ezért a fordító és a könyvtár által támogatott nyelvi szabványok eltérhetnek.

Integrált fejlesztői környezetek
  • A CLion teljes mértékben támogatja a C99-et, de a C11 támogatása részleges [99] , a build a CMake-en alapul.
  • A Code::Blocks  egy ingyenes, platformok közötti integrált fejlesztői környezet C, C++, D, Fortran nyelvekhez. Több mint két tucat fordítót támogat. A GCC fordítóval az összes C verzió elérhető C90-től C17-ig.
  • Az Eclipse  egy ingyenes IDE, amely támogatja a C99 szabványos C nyelvet. Moduláris felépítésű, amely lehetővé teszi a különböző programozási nyelvek támogatásának és további szolgáltatások csatlakoztatását. Elérhető egy Git integrációs modul , de nincs CMake integráció .
  • A KDevelop  egy ingyenes IDE, amely támogatja a C nyelv egyes funkcióit a C11 szabványból. Lehetővé teszi projektek kezelését különböző programozási nyelvek használatával, beleértve a C++- t és a Pythont , támogatja a CMake build rendszert. Beépített Git támogatással rendelkezik fájlszinten, és testreszabható forráskód-formázással rendelkezik a különböző nyelvekhez.
  • A Microsoft Visual Studio csak részben támogatja a C99 és C11 szabványokat, mivel a C++ fejlesztésre összpontosít, de beépített támogatással rendelkezik a CMake számára.
Egységtesztelő eszközök

Mivel a C nyelv nem ad lehetőséget a biztonságos kódírásra, és a nyelv számos eleme hozzájárul a hibákhoz, a jó minőségű és hibatűrő kód írása csak automatizált tesztek írásával garantálható. Az ilyen tesztelés megkönnyítésére a harmadik féltől származó egységteszt -könyvtárak különféle megvalósításai vannak .

  • A Check könyvtár keretet biztosít a C kód általános xUnit stílusban történő teszteléséhez . A lehetőségek között megemlíthető a tesztek külön folyamatokban történő futtatása a -n keresztül fork(), amely lehetővé teszi a tesztek szegmentálási hibáinak felismerését [100] , valamint lehetővé teszi az egyes tesztek maximális végrehajtási idejének beállítását is.
  • A Google Tesztkönyvtár xUnit-stílusú tesztelést is biztosít, de a C++ kód tesztelésére készült , ami lehetővé teszi a C kód tesztelésére is. Támogatja a program egyes részeinek elkülönített tesztelését is. A könyvtár egyik előnye a tesztmakrók állításokra és hibákra való szétválasztása, ami megkönnyíti a kód hibakeresését.

Számos más rendszer is létezik a C kód tesztelésére, mint például az AceUnit, a GNU Autounit, a cUnit és mások, de ezek vagy nem tesztelnek elszigetelt környezetben, vagy kevés szolgáltatást nyújtanak [100] , vagy már nem fejlesztik őket.

Hibakereső eszközök

A hibák megnyilvánulásai alapján nem mindig lehet egyértelmű következtetést levonni a kódban lévő problématerületről, azonban a különböző hibakereső eszközök gyakran segítenek a probléma lokalizálásában.

  • A Gdb  egy interaktív konzolhibakereső különféle nyelvekhez, beleértve a C-t is.
  • A Valgrind egy dinamikus kódelemző eszköz , amely közvetlenül a program végrehajtása során képes észlelni a kódban lévő hibákat. Támogatja a szivárgások észlelését, az inicializálatlan memóriához való hozzáférést, az érvénytelen címekhez való hozzáférést (beleértve a puffer túlcsordulást). Támogatja a végrehajtást profilalkotási módban is a callgrind [101] profilozó segítségével .
  • A KCacheGrind  egy grafikus felület a callgrind [102] profilkészítővel kapott profilalkotási eredmények megjelenítéséhez .
Fordítóprogramok dinamikus nyelvekhez és platformokhoz

Néha ahhoz, hogy bizonyos C nyelven írt könyvtárakat, függvényeket és eszközöket át lehessen vinni egy másik környezetbe, a C kódot le kell fordítani egy magasabb szintű nyelvre vagy egy ilyen nyelvre tervezett virtuális gép kódjába. A következő projektek erre a célra készültek:

További eszközök

A C esetében is vannak más eszközök, amelyek megkönnyítik és kiegészítik a fejlesztést, beleértve a statikus elemzőket és a kód formázására szolgáló segédprogramokat. A statikus elemzés segít azonosítani a lehetséges hibákat és sebezhetőségeket. Az automatikus kódformázás pedig leegyszerűsíti az együttműködés megszervezését a verziókezelő rendszerekben, minimalizálva a stílusváltozások miatti konfliktusokat.

  • A Cppcheck egy nyílt forráskódú  statikus kódelemző C és C++ nyelvekhez, amely néha hamis pozitív eredményeket ad, amelyeket a kódban található speciálisan formázott megjegyzések elnyomhatnak.
  • A Clang-format  egy parancssori segédprogram a forráskód adott stílusnak megfelelő formázására, amely egy speciálisan kialakított konfigurációs fájlban adható meg. Számos opcióval és számos beépített stílussal rendelkezik. A Clang projekt részeként fejlesztették ki [107] .
  • Az Indent és a GNU Indent segédprogramok kódformázást is biztosítanak, de a formázási beállítások parancssori opciókként vannak megadva [108] .

Hatókör

A nyelvet széles körben használják az operációs rendszer fejlesztésében, az operációs rendszer API szintjén, a beágyazott rendszerekben, valamint nagy teljesítményű vagy hibakritikus kódok írására. Az alacsony szintű programozás széleskörű elterjedésének egyik oka az a képesség, hogy több platformon is írható kód, amely különböző hardvereken és operációs rendszereken eltérően kezelhető.

A nagy teljesítményű kód írásának képessége a programozó teljes cselekvési szabadságának és a fordító általi szigorú ellenőrzés hiányának az rovására megy. Például a Java , a Python , a Perl és a PHP első implementációit C nyelven írták. Ugyanakkor sok programban a leginkább erőforrásigényes részek általában C-ben vannak megírva. A Mathematica [109] magja C-ben van írva, míg az eredetileg Fortran nyelven írt MATLAB -ot 1984-ben írták át C-ben [110] .

A C-t néha köztes nyelvként is használják magasabb szintű nyelvek fordításakor. Például a C++ , Objective-C és Go nyelvek első implementációi ennek az elvnek megfelelően működtek - az ezeken a nyelveken írt kódot a C nyelv köztes reprezentációjává fordították. A modern nyelvek, amelyek ugyanazon az elven működnek, a Vala és a Nim .

A C nyelv másik alkalmazási területe a valós idejű alkalmazások , amelyek igényesek a kód válaszkészsége és végrehajtási ideje tekintetében. Az ilyen alkalmazásoknak szigorúan korlátozott időkereten belül kell megkezdeni a műveletek végrehajtását, és maguknak az akcióknak egy bizonyos időn belül kell beleférniük. Konkrétan a POSIX.1 szabvány a valós idejű alkalmazások építéséhez biztosít egy sor funkciót és képességet [111] [112] [113] , de a kemény valós idejű támogatást is meg kell valósítania az operációs rendszernek [114] .

Leszármazott nyelvek

A C nyelv több mint negyven éve az egyik legszélesebb körben használt programozási nyelv volt és marad is. Hatása természetesen bizonyos mértékig számos későbbi nyelvben is nyomon követhető. Mindazonáltal azon nyelvek között, amelyek elértek egy bizonyos elterjedtséget, kevés a C közvetlen leszármazottja.

Egyes leszármazott nyelvek a C-re épülnek további eszközökkel és mechanizmusokkal, amelyek új programozási paradigmák támogatását adják ( OOP , funkcionális programozás , általános programozás stb.). Ezek a nyelvek elsősorban a C++ és az Objective-C , valamint közvetve ezek leszármazottai a Swift és a D. Ismeretesek a C javítására irányuló kísérletek is a legjelentősebb hiányosságok kijavításával, de vonzó tulajdonságainak megtartásával. Közülük említhetjük a Cyclone (és leszármazottja Rust ) kutatónyelvet. Néha mindkét fejlődési irány egy nyelvben ötvöződik, erre példa a Go .

Külön meg kell említeni a nyelvek egy egész csoportját, amelyek kisebb-nagyobb mértékben örökölték a C alapszintaxisát (kapcsos kapcsos zárójelek használata kódblokkok határolójaként, változók deklarálása, operátorok jellegzetes formái for, while, if, switchzárójelben lévő paraméterekkel, kombinált műveletek ++, --, +=, -=és egyebek) , ezért ezeken a nyelveken a programok jellegzetes megjelenést kapnak, amelyek kifejezetten a C-hez kapcsolódnak. Ezek olyan nyelvek, mint a Java , JavaScript , PHP , Perl , AWK , C# . Valójában ezeknek a nyelveknek a szerkezete és szemantikája nagyon különbözik a C-től, és általában olyan alkalmazásokhoz készültek, ahol az eredeti C-t soha nem használták.

C++

A C++ programozási nyelv C-ből jött létre, és örökölte annak szintaxisát, kiegészítve új konstrukciókkal a Simula-67, Smalltalk, Modula-2, Ada, Mesa és Clu szellemében [116] . A fő kiegészítések az OOP (osztályleírás, többszörös öröklődés, virtuális függvényeken alapuló polimorfizmus) és az általános programozás (sablonmotor) támogatása voltak. De ezen kívül számos különféle kiegészítést tettek a nyelvhez. Jelenleg a C++ az egyik legszélesebb körben használt programozási nyelv a világon, és általános célú nyelvként van elhelyezve, a rendszerprogramozásra helyezve a hangsúlyt [117] .

Kezdetben a C++ megőrizte a C-vel való kompatibilitást, amit az új nyelv egyik előnyének tartottak. A C++ első implementációi egyszerűen lefordították az új konstrukciókat tiszta C-be, majd a kódot egy szokásos C fordító dolgozta fel. A kompatibilitás megőrzése érdekében a C++ megalkotói nem voltak hajlandóak kizárni belőle a C néhány gyakran kritizált funkcióját, ehelyett új, "párhuzamos" mechanizmusokat hoztak létre, amelyek az új C++ kód fejlesztése során javasoltak (makrók helyett sablonok, automatikus helyett explicit típusú casting , szabványos könyvtári tárolók a kézi dinamikus memóriafoglalás helyett, és így tovább). A nyelvek azonban azóta egymástól függetlenül fejlődtek, és mára a legújabb szabványok C és C++ nyelve csak részben kompatibilis: nincs garancia arra, hogy egy C++ fordító sikeresen lefordít egy C programot, és ha sikerül, akkor nincs garancia arra, hogy a lefordított program megfelelően fog futni. Különösen bosszantó néhány finom szemantikai különbség, amelyek ugyanazon kód eltérő viselkedéséhez vezethetnek, amely szintaktikailag mindkét nyelven helyes. Például a karakterkonstansok (egy idézőjelbe zárt karakterek) típusa intC-ben és típusa C++char - ban van , tehát az ilyen állandók által elfoglalt memória mennyisége nyelvenként változik. [118] Ha egy program érzékeny egy karakterkonstans méretére, akkor másképp fog viselkedni, ha a C és C++ fordítókkal fordítják.

Az ehhez hasonló különbségek megnehezítik olyan programok és könyvtárak írását, amelyek C és C++ nyelven is ugyanúgy tudnak fordítani és működni , ami természetesen megzavarja azokat, akik mindkét nyelven programoznak. Mind a C, mind a C++ fejlesztői és felhasználói között a nyelvek közötti különbségek minimalizálásának hívei vannak, ami objektíve kézzelfogható előnyökkel járna. Van azonban egy ellentétes álláspont is, amely szerint a kompatibilitás nem különösebben fontos, bár hasznos, és az inkompatibilitás csökkentésére irányuló erőfeszítések nem akadályozhatják meg az egyes nyelvek egyéni fejlesztését.

Objective-C

Egy másik lehetőség a C kiterjesztésére objektumalapú eszközökkel az 1983-ban létrehozott Objective-C nyelv. Az objektum alrendszert a Smalltalk -tól kölcsönözték , és az ehhez az alrendszerhez tartozó összes elem a saját szintaxisában van implementálva, ami meglehetősen élesen különbözik a C szintaxistól (akár odáig, hogy az osztályleírásokban a mezők deklarálásának szintaxisa ellentétes a szintaxissal a C-beli változók deklarálásának szintaxisa: először a mező nevét írják ki, majd a típusát). A C++-tól eltérően az Objective-C a klasszikus C szuperhalmaza, vagyis megőrzi kompatibilitását a forrásnyelvvel; a helyes C program helyes Objective-C program. A másik lényeges különbség a C++ ideológiához képest, hogy az Objective-C az objektumok interakcióját valósítja meg teljes értékű üzenetek cseréjével, míg a C++ az "üzenet küldése metódushívásként" koncepciót valósítja meg. A teljes üzenetfeldolgozás sokkal rugalmasabb, és természetesen illeszkedik a párhuzamos számításokhoz. Az Objective-C, valamint annak közvetlen leszármazottja , a Swift a legnépszerűbbek közé tartozik az Apple által támogatott platformokon .

Problémák és kritika

A C nyelv egyedülálló abban, hogy ez volt az első magas szintű nyelv , amely komolyan kiszorította az assemblert a rendszerszoftverek fejlesztésében . Továbbra is a legtöbb hardverplatformon megvalósított nyelv és az egyik legnépszerűbb programozási nyelv , különösen a szabad szoftverek világában [119] . Ennek ellenére a nyelvnek számos hiányossága van, kezdete óta számos szakértő bírálta.

Általános kritika

A nyelv nagyon összetett, és tele van veszélyes elemekkel, amelyekkel nagyon könnyű visszaélni. Felépítésével és szabályaival nem támogatja a megbízható és karbantartható programkód létrehozását célzó programozást, éppen ellenkezőleg, a különböző processzorok közvetlen programozásának korszakában született nyelv hozzájárul a nem biztonságos és zavaró kódok írásához [119] . Sok professzionális programozó hajlamos azt gondolni, hogy a C nyelv hatékony eszköz az elegáns programok létrehozásához, ugyanakkor rendkívül rossz minőségű megoldások készítésére is használható [120] [121] .

A nyelv különféle feltételezései miatt a programok több hibával is lefordíthatók, ami gyakran kiszámíthatatlan programviselkedést eredményez. A modern fordítók lehetőséget adnak a statikus kódelemzésre [122] [123] , de még ezek sem képesek minden lehetséges hibát észlelni. Az írástudatlan C-programozás szoftversérülékenységet eredményezhet, ami hatással lehet a használat biztonságára .

Xi-nek magas a belépési küszöbe [119] . A specifikációja több mint 500 oldalnyi szöveget foglal el, amit teljes mértékben át kell tanulmányozni, hiszen a hibamentes és jó minőségű kód létrehozásához a nyelv számos, nem nyilvánvaló tulajdonságát is figyelembe kell venni. Például az egész kifejezések operandusainak gépbe adása intnehezen megjósolható eredményeket adhat bináris operátorok használatakor [44] :

előjel nélküli karakter x = 0xFF ; előjel nélküli char y = ( ~ x | 0x1 ) >> 1 ; // Intuitív módon itt 0x00 várható printf ( "y = 0x%hhX \n " , y ); // 0x80-at nyomtat, ha sizeof(int) > sizeof(char)

Az ilyen árnyalatok megértésének hiánya számos hibához és sebezhetőséghez vezethet. A C elsajátításának bonyolultságát növelő másik tényező a fordítói visszacsatolás hiánya: a nyelv teljes cselekvési szabadságot ad a programozónak, és lehetővé teszi a nyilvánvaló logikai hibákkal járó programok fordítását. Mindez megnehezíti a C használatát az oktatásban , mint első programozási nyelvet [119]

Végül, több mint 40 éves fennállása alatt a nyelv kissé elavulttá vált, és meglehetősen problematikus számos modern programozási technika és paradigma alkalmazása benne .

A nyelv egyes elemeinek hátrányai

Primitív modularitás támogatása

Nincsenek modulok és mechanizmusok az interakciójukra a C szintaxisban. A forráskódfájlokat külön állítják össze, és más fájlokból importált változók, függvények és adattípusok prototípusait kell tartalmazniuk. Ez a fejlécfájlok makróhelyettesítéssel történő felvételével történik . A kódfájlok és a fejlécfájlok közötti megfelelés megsértése esetén mind link-time hibák, mind mindenféle futási hiba előfordulhat: a verem- és kupacsérüléstől a szegmentációs hibákig . Mivel a direktíva csak az egyik fájl szövegét helyettesíti a másikkal, a nagyszámú fejlécfájl beillesztése oda vezet, hogy a lefordított kód tényleges mennyisége sokszorosára növekszik, ez az oka a fájl viszonylag lassú teljesítményének. C fordítók. A fő modulban és a fejlécfájlokban található leírások összehangolásának szükségessége megnehezíti a program karbantartását. #include#include

Figyelmeztetések hibák helyett

A nyelvi szabvány nagyobb cselekvési szabadságot ad a programozónak, és így nagy eséllyel hibázik. A legtöbbször nem megengedett legtöbbet a nyelv engedi meg, és a fordító a legjobb esetben is figyelmeztet. Bár a modern fordítók lehetővé teszik az összes figyelmeztetés hibává alakítását, ezt a szolgáltatást ritkán használják, és a program kielégítő futása esetén a figyelmeztetéseket általában figyelmen kívül hagyják.

Így például a C99 szabvány előtt egy függvény meghívása mallocfejlécfájl nélkül veremsérüléshez stdlib.hvezethet, mivel prototípus hiányában a függvényt úgy hívták meg, hogy egy típust ad vissza int, míg valójában egy típust adott vissza void*(egy hiba történt, amikor a célplatformon a típusok mérete különbözött). Ennek ellenére ez csak figyelmeztetés volt.

A változók inicializálása feletti kontroll hiánya

Az automatikusan és dinamikusan létrehozott objektumok alapértelmezés szerint nem inicializálódnak, és létrehozásuk után tartalmazzák a korábban ott lévő objektumok memóriájában maradt értékeket. Egy ilyen érték teljesen kiszámíthatatlan, gépenként, futásonként, függvényhívásonként változó. Ha a program ilyen értéket használ az inicializálás véletlen kihagyása miatt, akkor az eredmény kiszámíthatatlan lesz, és előfordulhat, hogy nem jelenik meg azonnal. A modern fordítók a forráskód statikus elemzésével próbálják diagnosztizálni ezt a problémát, bár általában rendkívül nehéz ezt a problémát statikus elemzéssel megoldani. További eszközök használhatók ezen problémák azonosítására a program végrehajtása során a tesztelési szakaszban: Valgrind és MemorySanitizer [124] .

A címaritmetika feletti kontroll hiánya

A veszélyes helyzetek forrása a mutatók numerikus típusokkal való kompatibilitása és a címaritmetika szigorú ellenőrzés nélküli használatának lehetősége a fordítás és a végrehajtás szakaszaiban. Ez lehetővé teszi, hogy bármely objektumra mutatót kapjon, beleértve a végrehajtható kódot is, és hivatkozzon erre a mutatóra, kivéve, ha a rendszer memóriavédelmi mechanizmusa ezt megakadályozza.

A mutatók helytelen használata meghatározatlan programviselkedést okozhat, és súlyos következményekkel járhat. Például egy mutató inicializálatlan lehet, vagy hibás aritmetikai műveletek eredményeként tetszőleges memóriahelyre mutathat. Egyes platformokon az ilyen mutató használata leállításra kényszerítheti a programot, másokon pedig tetszőleges adatot ronthat el a memóriában; Az utolsó hiba azért veszélyes, mert a következményei beláthatatlanok, és bármikor megjelenhetnek, még a tényleges hibás cselekvés pillanatánál sokkal később is.

A C tömbökhöz való hozzáférést szintén címaritmetika segítségével valósítják meg, és ez nem jelenti azt, hogy ellenőrizni kell a tömbelemekhez való hozzáférés helyességét index alapján. Például a a[i]és kifejezések i[a]megegyeznek, és egyszerűen lefordítják a formra *(a + i), és a határokon kívüli tömb ellenőrzése nem történik meg. A tömb felső határánál nagyobb indexű hozzáférés a tömb után a memóriában lévő adatokhoz való hozzáférést eredményezi, amit puffertúlcsordulásnak nevezünk . Ha egy ilyen hívás hibás, az kiszámíthatatlan programviselkedéshez vezethet [57] . Ezt a funkciót gyakran használják egy másik alkalmazás vagy az operációs rendszer kernel memóriájának illegális elérésére használt kihasználásokban .

Hibaveszélyes dinamikus memória

A dinamikusan lefoglalt memóriával való munkavégzésre szolgáló rendszerfunkciók nem biztosítják a kiosztás és a felszabadítás helyességét és időszerűségét, a dinamikus memóriával való munkavégzés helyes sorrendjének betartása teljes mértékben a programozó felelőssége. Hibái hibás címekhez való hozzáféréshez, idő előtti kiadáshoz vagy memóriaszivárgáshoz vezethetnek ( utóbbi lehetséges például, ha a fejlesztő elfelejtette meghívni free()vagy meghívni a hívó free()funkciót, amikor arra szükség volt) [125] .

Az egyik gyakori hiba, hogy nem ellenőrzi a memóriafoglalási függvények ( malloc(), calloc()és mások) eredményét a NULL-n, miközben előfordulhat, hogy a memória nem kerül lefoglalásra, ha nincs elég belőle, vagy ha túl sokat kértek, pl. -1az esetleges hibás matematikai műveletek eredményeként kapott szám előjel nélküli típusra való csökkentése size_t, a rajta végzett további műveletekkel . A rendszermemória-függvényekkel kapcsolatos másik probléma a meghatározatlan viselkedés nulla méretű blokkkiosztás kérésekor: a függvények vagy valós mutatóértéket adhatnak vissza, az adott megvalósítástól függően [126] . NULL

Egyes speciális implementációk és harmadik féltől származó könyvtárak olyan funkciókat kínálnak, mint a hivatkozásszámlálás és a gyenge hivatkozások [127] , az intelligens mutatók [128] és a szemétgyűjtés korlátozott formái [129] , de ezek a szolgáltatások nem szabványosak, ami természetesen korlátozza az alkalmazásukat. .

Nem hatékony és nem biztonságos karakterláncok

A nyelv esetében a null-végződésű karakterláncok szabványosak, így minden szabványos függvény működik velük. Ez a megoldás a jelentéktelen memóriamegtakarítás miatt jelentős hatékonyságvesztéssel jár (a méret explicit tárolásához képest): a karakterlánc hosszának kiszámítása (függvény ) megköveteli a teljes karakterlánc áthurkolását az elejétől a végéig, a karakterláncok másolása is nehézkes. optimalizálni egy lezáró nulla jelenléte miatt [48] . Mivel a karakterláncadatokhoz egy lezáró null értéket kell hozzáadni, lehetetlenné válik az alkarakterláncok hatékony szeletként történő kinyerése, és a szokásos karakterláncokhoz hasonlóan való munkavégzés. a karakterláncok részeinek kiosztása és manipulálása általában kézi kiosztást és memóriafelszabadítást igényel, tovább növelve a hibalehetőséget. strlen()

A nulla végű karakterláncok gyakori hibaforrások [130] . Még a szabványos függvények sem ellenőrzik a célpuffer méretét [130] , és előfordulhat, hogy nem adnak hozzá null karaktert [131] a sztring végéhez , nem beszélve arról, hogy programozói hiba miatt előfordulhat, hogy nem kerül hozzáadásra vagy felülírásra. [132] .

A variadic függvények nem biztonságos megvalósítása

Miközben a változó argumentumszámú függvényeket támogatja , a C nem biztosít sem eszközt az ilyen függvénynek átadott tényleges paraméterek számának és típusainak meghatározására, sem pedig mechanizmust ezek biztonságos elérésére [133] . A függvény tájékoztatása az aktuális paraméterek összetételéről a programozó feladata, és az értékük eléréséhez a verem utolsó rögzített paraméterének címéből kell megszámolni a helyes számú bájtot, akár manuálisan, akár egy készlet segítségével. makrók va_arga fejlécfájlból stdarg.h. Ugyanakkor figyelembe kell venni a függvények hívásakor az automatikus implicit típusú előléptetés mechanizmusának működését [134] , miszerint a -nál kisebb egész típusú argumentumok -ra int( intvagy -ra unsigned int) kerülnek, de float-ra double. Hiba a hívásban vagy a függvényen belüli paraméterekkel végzett munkában csak a program végrehajtása során jelenik meg, ami beláthatatlan következményekkel jár, a hibás adatok kiolvasásától a verem megrongálásáig.

printf()Ugyanakkor a változó számú paraméterrel rendelkező függvények ( , scanf()és mások), amelyek nem képesek ellenőrizni, hogy az argumentumlista egyezik-e a formátum karakterláncával , a formázott I/O szabvány eszközei . Sok modern fordító minden hívásnál elvégzi ezt az ellenőrzést, figyelmeztetést generálva, ha eltérést talál, de általában ez az ellenőrzés nem lehetséges, mert minden variadic függvény másként kezeli ezt a listát. Még az összes függvényhívást sem lehet statikusan vezérelni, printf()mert a formátum karakterlánc dinamikusan létrehozható a programban.

A hibakezelés egységességének hiánya

A C szintaxis nem tartalmaz speciális hibakezelési mechanizmust. A szabványos könyvtár csak a legegyszerűbb eszközöket támogatja: a fejlécfájlból egy változót ( POSIX  esetén makrót) az utolsó hibakód beállításához, valamint a kódoknak megfelelő hibaüzeneteket kapó függvényeket. Ez a megközelítés ahhoz vezet, hogy nagy mennyiségű ismétlődő kódot kell írni, keverve a fő algoritmust a hibakezeléssel, ráadásul nem szálbiztos. Sőt, még ebben a mechanizmusban sincs egyetlen sorrend: errnoerrno.h

  • jelölőt adja vissza hiba esetén, és magát a kódot kell lekérni, ha a függvény felfedi;-1errno
  • a POSIX-ben bevett szokás, hogy közvetlenül hibakódot ad vissza, de nem minden POSIX függvény teszi ezt;
  • sok funkcióban, például , fopen()és fread(), fwrite()a beállítás errnonem szabványos, és a különböző megvalósításokban eltérhet [79] (a POSIX-ben szigorúbbak a követelmények, és az esetleges hibákra vonatkozó opciók egy része meg van adva );
  • vannak olyan függvények, ahol a hibajelző az egyik megengedett visszatérési érték, és ezek meghívása előtt nullára kell állítani, errnohogy megbizonyosodjunk arról, hogy a hibakódot ez a függvény állította be [79] .

A szabványos könyvtárban a kódok errnomakródefiníciókon keresztül vannak kijelölve, és ugyanazokkal az értékekkel rendelkezhetnek, ami lehetetlenné teszi a hibakódok operátoron keresztüli elemzését switch. A nyelvnek nincs speciális adattípusa a zászlókhoz és hibakódokhoz, ezek típusértékként kerülnek átadásra int. errno_tA hibakód tárolására külön típus csak a C11 szabvány K kiterjesztésében jelent meg, és előfordulhat, hogy a fordítók nem támogatják [87] .

A nyelv hiányosságainak kiküszöbölésének módjai

A C hiányosságai régóta ismertek, és a nyelv kezdete óta számos kísérlet történt a C kód minőségének és biztonságának javítására anélkül, hogy a képességeit feláldozták volna.

A kódhelyesség-elemzés eszközei

Szinte az összes modern C fordító lehetővé teszi a korlátozott statikus kódelemzést, figyelmeztetve az esetleges hibákra. Az opciók szintén támogatottak a tömb határokon kívüli, verem megsemmisülése, kupackorlátozáson kívüli ellenőrzések, inicializálatlan változók, nem definiált viselkedés stb. kódba történő beágyazására. A további ellenőrzések azonban befolyásolhatják a végső alkalmazás teljesítményét, ezért leggyakrabban csak hibakeresési szakaszban használják.

Speciális szoftvereszközök állnak rendelkezésre a C-kód statikus elemzésére a nem szintaktikai hibák észlelésére. Használatuk nem garantálja a programok hibamentességét, de lehetővé teszi a tipikus hibák és a potenciális sérülékenységek jelentős részének azonosítását. Ezeknek az eszközöknek a maximális hatása nem alkalmi használattal érhető el, hanem egy jól bevált állandó kódminőség-ellenőrzési rendszer részeként, például folyamatos integrációs és telepítési rendszerekben. Szükség lehet arra is, hogy a kódot speciális megjegyzésekkel lássák el, hogy kizárják az analizátor téves riasztásait a kód megfelelő részein, amelyek formálisan a hibásak kritériumai alá tartoznak.

Biztonságos programozási szabványok

Jelentős mennyiségű kutatás jelent meg a megfelelő C programozásról, a kis cikkektől a terjedelmes könyvekig. Vállalati és iparági szabványokat fogadtak el a C kód minőségének megőrzése érdekében. Különösen:

  • A MISRA C  egy szabvány, amelyet a Motor Industry Software Reliability Association fejlesztett ki a C járműbeágyazott rendszerek fejlesztésében való felhasználására. Jelenleg a MISRA C-t számos iparágban használják, beleértve a katonai, az orvosi és a repülési területeket. A 2013-as kiadás 16 direktívát és 143 szabályt tartalmaz, beleértve a kódkövetelményeket és bizonyos nyelvi jellemzők használatára vonatkozó korlátozásokat (pl. változó paraméterű függvények használata tilos). Körülbelül egy tucat MISRA C kódellenőrző eszköz van a piacon, és több fordító is beépített MISRA C kényszerellenőrzéssel.
  • A CERT C kódolási szabvány a CERT Koordinációs Központ által  kidolgozott szabvány [135] . Célja továbbá, hogy megbízható és biztonságos C programozást biztosítson. Szabályokat és iránymutatásokat tartalmaz a fejlesztők számára, beleértve eseti alapon példákat a helytelen és helyes kódra. A szabványt olyan cégek használják termékfejlesztésben, mint a Cisco és az Oracle [136] .
POSIX szabványok

A POSIX szabványkészlet hozzájárul a nyelv bizonyos hiányosságainak ellensúlyozásához . A telepítést errnoszámos funkció szabványosítja, lehetővé téve például a fájlműveletek során előforduló hibák kezelését, és a szabványos könyvtár egyes funkcióinak szálbiztos analógjai kerülnek bevezetésre, amelyek biztonságos verziói csak a nyelvi szabványban találhatók meg. a K kiterjesztést [137] .

Lásd még

Jegyzetek

Megjegyzések

  1. A B az angol ábécé második betűje, a C pedig az angol ábécé harmadik betűje .
  2. A fejlécfájl makrója a kulcsszó feletti burkoló .boolstdbool.h_Bool
  3. A fejlécfájl makrója a kulcsszó feletti burkoló .complexcomplex.h_Complex
  4. A fejlécfájl makrója a kulcsszó feletti burkoló .imaginarycomplex.h_Imaginary
  5. A fejlécfájl makrója a kulcsszó feletti burkoló .alignasstdalign.h_Alignas
  6. 1 2 3 A fejlécfájl makrója a kulcsszó feletti wrapper .alignofstdalign.h_Alignof
  7. A fejlécfájl makrója a kulcsszó feletti burkoló .noreturnstdnoreturn.h_Noreturn
  8. A fejlécfájl makrója a kulcsszó feletti burkoló .static_assertassert.h_Static_assert
  9. A fejlécfájl makrója a kulcsszó feletti burkoló .thread_localthreads.h_Thread_local
  10. 1 2 3 4 5 6 7 Az előjeles és előjel nélküli , és típusok első megjelenése a charK & R C-ben volt short.intlong
  11. 1 2 Az IEC 60559 szabványnak való megfelelő típusformátumot floataz doubleF C bővítmény határozza meg, így a formátum az egyes platformokon vagy fordítókonként eltérő lehet.

Források

  1. 1 2 http://www.bell-labs.com/usr/dmr/www/chist.html
  2. Rui Ueyama. Hogyan írtam egy önkiszolgáló C-fordítót 40 nap alatt  ? www.sigbus.info (2015. december). Letöltve: 2019. február 18. Az eredetiből archiválva : 2019. március 23.
  3. Szemétgyűjtő C-hez és C++-hoz Archiválva : 2005. október 13. a Wayback Machine -nél 
  4. Objektum-orientált programozás ANSI-C- vel archiválva : 2016. március 6. a Wayback Machine -nél 
  5. Létrehozható osztályozott típusok:  objektumok . GObject kézikönyv . developer.gnome.org. Letöltve: 2019. május 27. Az eredetiből archiválva : 2019. május 27.
  6. Nem példányosítható osztályozott típusok:  interfészek . GObject kézikönyv . developer.gnome.org. Letöltve: 2019. május 27. Az eredetiből archiválva : 2019. május 27.
  7. 1 2 A C17 szabvány tervezete , 5.2.1 Karakterkészletek, p. 17.
  8. 12 A C17 szabvány tervezete , 6.4.2 Azonosítók, p. 43-44.
  9. A C17 szabvány tervezete , 6.4.4 Állandók, p. 45-50.
  10. 1 2 Podbelsky, Fomin, 2012 , p. 19.
  11. 12 A C17 szabvány tervezete , 6.4.4.1 Egész szám konstansok, p. 46.
  12. A C17 szabvány tervezete , 6.4.4.2 Lebegő állandók, p. 47-48.
  13. 1 2 A C17 szabvány tervezete , 6.4.4.4 Karakterállandók, p. 49-50.
  14. STR30-C.  Ne próbálja meg módosítani a string literálokat - SEI CERT C kódolási szabvány - Confluence . wiki.sei.cmu.edu. Letöltve: 2019. május 27. Az eredetiből archiválva : 2019. május 27.
  15. A C17 szabvány tervezete , 6.4.5 String literálok, p. 50-52.
  16. Clang-Format Style Options - Clang 9  dokumentáció . clang.llvm.org. Letöltve: 2019. május 19. Az eredetiből archiválva : 2019. május 20.
  17. ↑ 1 2 3 4 DCL06-C. Használjon értelmes szimbolikus állandókat a szó szerinti értékek megjelenítéséhez - SEI CERT C kódolási szabvány -  Confluence . wiki.sei.cmu.edu. Letöltve: 2019. február 6. Az eredetiből archiválva : 2019. február 7..
  18. 1 2 A C17 szabvány tervezete , p. 84.
  19. A C17 szabvány tervezete , 6.4.1 Kulcsszavak, p. 42.
  20. ↑ 1 2 Free Software Foundation (FSF). A C99 szolgáltatásainak állapota a GCC-ben  . GNU projekt . gcc.gnu.org. Letöltve: 2019. május 31. Az eredetiből archiválva : 2019. június 3.
  21. 1 2 3 4 A C17 szabvány tervezete , 7.1.3 Fenntartott azonosítók, p. 132.
  22. A C17 szabvány tervezete , 6.5.3 Unáris operátorok, p. 63-65.
  23. A C17 szabvány tervezete , 6.5 Kifejezések, p. 66-72.
  24. A C17 szabvány tervezete , 6.5.16 Hozzárendelési operátorok, p. 72-74.
  25. A C17 szabvány tervezete , p. 55-75.
  26. ↑ 1 2 A GNU C kézikönyv . 3.19 Kezelői  elsőbbség . www.gnu.org . Letöltve: 2019. február 13. Az eredetiből archiválva : 2019. február 7..
  27. ↑ 1 2 3 4 5 EXP30-C. Ne függjön a mellékhatások értékelési sorrendjétől – SEI CERT C kódolási szabvány –  Confluence . wiki.sei.cmu.edu. Letöltve: 2019. február 14. Az eredetiből archiválva : 2019. február 15.
  28. ↑ 12BB . _ Definíciók - SEI CERT C kódolási szabvány -  Confluence . wiki.sei.cmu.edu. Letöltve: 2019. február 16. Az eredetiből archiválva : 2019. február 16.
  29. Podbelsky, Fomin, 2012 , 1.4. Műveletek, p. 42.
  30. Podbelsky, Fomin, 2012 , 2.3. Hurokutasítások, p. 78.
  31. ↑ 12 EXP19 -C. Használjon kapcsos zárójelet az if, for vagy while utasítás törzséhez - SEI CERT C kódolási szabvány -  Confluence . wiki.sei.cmu.edu. Letöltve: 2019. június 2. Az eredetiből archiválva : 2019. június 2.
  32. Dinamikusan betöltött (DL)  könyvtárak . tldp.org. Letöltve: 2019. február 18. Az eredetiből archiválva : 2020. november 12.
  33. 1 2 A C17 szabvány tervezete, 6.7.4 Funkcióspecifikátorok , p. 90-91.
  34. PRE00-C. Inline vagy statikus függvények előnyben részesítése a függvényszerű makrók helyett - SEI CERT C kódolási szabvány -  Confluence . wiki.sei.cmu.edu. Letöltve: 2019. június 4. Az eredetiből archiválva : 2021. augusztus 7..
  35. A C17 szabvány tervezete , 6.11 Jövőbeli nyelvi irányok, p. 130.
  36. A C támogatja a funkció túlterhelését? | GeeksforGeeks . Hozzáférés időpontja: 2013. december 15. Az eredetiből archiválva : 2013. december 15.
  37. A GNU C kézikönyv . www.gnu.org. Letöltve: 2017. május 21. Az eredetiből archiválva : 2021. április 27.
  38. A típus szélessége (A GNU C könyvtár  ) . www.gnu.org. Letöltve: 2018. december 7. Az eredetiből archiválva : 2018. december 9..
  39. A C17 szabvány tervezete , 6.2.5 Típusok, p. 31.
  40. ↑ 1 2 Vegyes Műszaki Bizottság ISO/IEC JTC 1. ISO/IEC 9899:201x. Programozási nyelvek - C. - ISO / IEC, 2011. - P. 14. - 678 p. Archiválva : 2017. május 30. a Wayback Machine -nál
  41. 0.10.0 ellenőrzése: 4. Speciális  szolgáltatások . Ellenőrizze . check.sourceforge.net. Letöltve: 2019. február 11. Az eredetiből archiválva : 2018. május 18.
  42. Típuskonverziós makrók: GLib Reference  Manual . developer.gnome.org. Letöltve: 2019. január 14. Az eredetiből archiválva : 2019. január 14.
  43. INT01-C. Használja az rsize_t vagy a size_t értéket minden egész értékhez, amely egy objektum méretét jelenti - SEI CERT C kódolási szabvány -  Confluence . wiki.sei.cmu.edu. Letöltve: 2019. február 22. Az eredetiből archiválva : 2021. augusztus 7..
  44. ↑ 1 2 3 INT02-C. Ismerje meg az egész számok konverziós szabályait – SEI CERT C kódolási szabvány –  Confluence . wiki.sei.cmu.edu. Hozzáférés dátuma: 2019. február 22. Az eredetiből archiválva : 2019. február 22.
  45. FLP02-C. Kerülje a lebegőpontos számok használatát, ha pontos számításra van szükség – SEI CERT C kódolási szabvány –  Confluence . wiki.sei.cmu.edu. Letöltve: 2019. május 21. Az eredetiből archiválva : 2021. augusztus 7..
  46. 1 2 A C17 szabvány tervezete , IEC 60559 lebegőpontos aritmetika, p. 370.
  47. A C17 szabvány tervezete , 7.12 Matematika <math.h>, p. 169-170.
  48. ↑ 1 2 Poul-Henning tábor. A legdrágább egybájtos hiba – ACM-sor  . queue.acm.org (2011. július 25.). Letöltve: 2019. május 28. Az eredetiből archiválva : 2019. április 30.
  49. ↑ 1 2 unicode(7) - Linux kézikönyv  oldal . man7.org. Letöltve: 2019. február 24. Az eredetiből archiválva : 2019. február 25.
  50. ↑ 1 2 3 A wchar_t mess - GNU libunistring  . www.gnu.org. Letöltve: 2019. január 2. Az eredetiből archiválva : 2019. szeptember 17.
  51. ↑ Programozás széles karakterekkel  . linux.com | A Linux-információk forrása (2006. február 11.). Letöltve: 2019. június 7. Az eredetiből archiválva : 2019. június 7.
  52. Markus Kuhn . UTF-8 és Unicode GYIK  . www.cl.cam.ac.uk. Letöltve: 2019. február 25. Az eredetiből archiválva : 2019. február 27.
  53. Hibajelentés összefoglalása a C11-hez . www.open-std.org. Letöltve: 2019. január 2. Az eredetiből archiválva : 2019. január 1..
  54. ↑ Szabványos felsorolások : GTK+ 3 Reference Manual  . developer.gnome.org. Letöltve: 2019. január 15. Az eredetiből archiválva : 2019. január 14.
  55. ↑ Az objektum tulajdonságai : GObject Reference Manual  . developer.gnome.org. Letöltve: 2019. január 15. Az eredetiből archiválva : 2019. január 16.
  56. A GNU fordítógyűjtemény (GCC) használata: Közös  típusattribútumok . gcc.gnu.org. Hozzáférés dátuma: 2019. január 19. Az eredetiből archiválva : 2019. január 16.
  57. ↑ 12 ARR00 -C.  Ismerje meg a tömbök működését – SEI CERT C kódolási szabvány – Confluence . wiki.sei.cmu.edu. Letöltve: 2019. május 30. Az eredetiből archiválva : 2019. május 30.
  58. ARR32-C. Győződjön meg arról , hogy a változó hosszúságú tömbök méret argumentumai érvényes tartományban vannak - SEI CERT C Coding Standard -  Confluence . wiki.sei.cmu.edu. Letöltve: 2019. február 18. Az eredetiből archiválva : 2019. február 19.
  59. A C17 szabvány tervezete , 6.7.9 Inicializálás, p. 101.
  60. DCL38-C. Rugalmas tömbtag deklarálásakor használja a helyes szintaxist - SEI CERT C Coding Standard -  Confluence . wiki.sei.cmu.edu. Letöltve: 2019. február 21. Az eredetiből archiválva : 2019. február 22.
  61. OpenSSL_verzió  . _ www.openssl.org. Letöltve: 2018. december 9. Az eredetiből archiválva : 2018. december 9..
  62. ↑ Verzióinformáció : GTK+ 3 kézikönyv  . developer.gnome.org. Letöltve: 2018. december 9. Az eredetiből archiválva : 2018. november 16.
  63. PRE10-C. A többutasításos makrók körbefűzése do-while ciklusba – SEI CERT C kódolási szabvány –  Confluence . wiki.sei.cmu.edu. Letöltve: 2018. december 9. Az eredetiből archiválva : 2018. december 9..
  64. PRE01-C.  Használjon zárójeleket a makrókban a paraméternevek körül - SEI CERT C kódolási szabvány - Confluence . wiki.sei.cmu.edu. Letöltve: 2018. december 9. Az eredetiből archiválva : 2018. december 9..
  65. PRE06-C. Zárja be a fejlécfájlokat egy include guard - SEI CERT C Coding Standard -  Confluence - ba . wiki.sei.cmu.edu. Letöltve: 2019. május 25. Az eredetiből archiválva : 2019. május 25.
  66. 1 2 C17 vázlat, 5.1.2.2 Hosztolt környezet , p. 10-11.
  67. 1 2 3 A C17 szabvány tervezete , 6.2.4 Objektumok tárolási időtartama, p. harminc.
  68. 1 2 Vázlat C17, 7.22.4.4 A kilépési funkció , p. 256.
  69. MEM05-C. Kerülje a nagy veremkiosztást – SEI CERT C kódolási szabvány –  Confluence . wiki.sei.cmu.edu. Letöltve: 2019. május 24. Az eredetiből archiválva : 2019. május 24.
  70. C17 Vázlat , 6.7.1 Tárolási osztályok specifikációi, 1. o. 79.
  71. A C17 szabvány tervezete , 6.7.6.3 Funkciódeklarátorok (beleértve a prototípusokat is), p. 96.
  72. A C-ben lévő mutatók elvontabbak, mint gondolnád . www.viva64.com. Letöltve: 2018. december 30. Az eredetiből archiválva : 2018. december 30.
  73. Tanenbaum Andrew S, Bos Herbert. modern operációs rendszerek. 4. kiadás . - Szentpétervár. : Piter Kiadó, 2019. - S. 828. - 1120 p. — (Klasszikusok "Számítástechnika"). — ISBN 9785446111558 . Archiválva : 2021. augusztus 7. a Wayback Machine -nél
  74. Jonathan Corbet. Ripples a Stack  Clash -től . lwn.net (2017. június 28.). Letöltve: 2019. május 25. Az eredetiből archiválva : 2019. május 25.
  75. ELF binárisok keményítése csak olvasható áthelyezéssel (RELRO  ) . www.redhat.com. Letöltve: 2019. május 25. Az eredetiből archiválva : 2019. május 25.
  76. Hagyományos folyamatcímtér – statikus  program . www.openbsd.org. Letöltve: 2019. március 4. Az eredetiből archiválva : 2019. december 8..
  77. Dr. Thabang Mokoteli. ICMLG 2017 5th International Conference on Management Leadership and Governance . - Akadémiai konferenciák és publikálás korlátozott, 2017-03. - S. 42. - 567 p. — ISBN 9781911218289 . Archiválva : 2021. augusztus 7. a Wayback Machine -nél
  78. Hagyományos folyamatcímtér - Program megosztott  könyvtárakkal . www.openbsd.org. Letöltve: 2019. március 4. Az eredetiből archiválva : 2019. december 8..
  79. ↑ 1 2 3 4 ERR30-C. Állítsa az errno értéket nullára egy olyan könyvtári függvény meghívása előtt, amelyről ismert, hogy beállítja az errno-t, és csak azután ellenőrizze az errno értéket, miután a függvény hibát jelez - SEI CERT C kódolási szabvány -  Confluence . wiki.sei.cmu.edu. Letöltve: 2019. május 23. Az eredetiből archiválva : 2018. november 19.
  80. ↑ 1 2 3 4 5 6 ERR33-C. Szabványos könyvtári hibák észlelése és kezelése - SEI CERT C kódolási szabvány -  Confluence . wiki.sei.cmu.edu. Letöltve: 2019. május 23. Az eredetiből archiválva : 2019. május 23.
  81. sys_types.h.0p - Linux kézikönyv  oldal . man7.org. Letöltve: 2019. május 23. Az eredetiből archiválva : 2019. május 23.
  82. ↑ 12 ERR02 -C. Kerülje el a sávon belüli hibajelzőket - SEI CERT C kódolási szabvány -  Confluence . wiki.sei.cmu.edu. Letöltve: 2019. január 4. Az eredetiből archiválva : 2019. január 5..
  83. FIO34-C. Különbséget tehet a fájlból kiolvasott karakterek és az EOF vagy a WEOF-SEI CERT C kódolási szabvány-  konfluence között . wiki.sei.cmu.edu. Hozzáférés dátuma: 2019. január 4. Az eredetiből archiválva : 2019. január 4.
  84. Kódolási stílus  . A rendszer- és szolgáltatásmenedzser . github.com. Letöltve: 2019. február 1. Az eredetiből archiválva : 2020. december 31.
  85. ↑ Hibajelentés : GLib Reference Manual  . developer.gnome.org. Letöltve: 2019. február 1. Az eredetiből archiválva : 2019. február 2..
  86. ↑ Eina : Hiba  . docs.enlightenment.org. Letöltve: 2019. február 1. Az eredetiből archiválva : 2019. február 2..
  87. ↑ 1 2 DCL09-C. Az errno értéket visszaadó függvények deklarálása errno_t - SEI CERT C Coding Standard - Confluence visszatérési típussal . wiki.sei.cmu.edu. Letöltve: 2018. december 21. Az eredetiből archiválva : 2018. december 21..
  88. ↑ 1 2 FLP32-C. Tartomány- és tartományhibák megelőzése vagy észlelése a matematikai függvényekben – SEI CERT C kódolási szabvány –  Confluence . wiki.sei.cmu.edu. Letöltve: 2019. január 5. Az eredetiből archiválva : 2019. január 5..
  89. ↑ 12 MEM12 -C. Fontolja meg a goto lánc használatát, amikor egy függvényt hibásan hagy az erőforrások használatakor és felszabadításakor – SEI CERT C kódolási szabvány –  Confluence . wiki.sei.cmu.edu. Letöltve: 2019. január 4. Az eredetiből archiválva : 2019. január 5..
  90. ERR04-C.  Válassza ki a megfelelő lezárási stratégiát – SEI CERT C kódolási szabvány – Confluence . wiki.sei.cmu.edu. Letöltve: 2019. január 4. Az eredetiből archiválva : 2019. január 5..
  91. MEM31-C. Dinamikusan lefoglalt memória felszabadítása, ha már nincs rá szükség – SEI CERT C kódolási szabvány –  Confluence . wiki.sei.cmu.edu. Letöltve: 2019. január 6. Az eredetiből archiválva : 2019. január 6..
  92. A GNU fordítógyűjtemény (GCC) használata:  Szabványok . gcc.gnu.org. Letöltve: 2019. február 23. Az eredetiből archiválva : 2012. június 17.
  93. Nyelvi kompatibilitás  . clang.llvm.org. Letöltve: 2019. február 23. Az eredetiből archiválva : 2019. február 19.
  94. Clang 6.0.0 Kiadási megjegyzések – Clang 6 dokumentáció . releases.llvm.org. Letöltve: 2019. február 23. Az eredetiből archiválva : 2019. február 23.
  95. Siddhesh Poyarekar – A GNU C Library 2.29-es verziója már  elérhető . sourceware.org. Letöltve: 2019. február 2. Az eredetiből archiválva : 2019. február 2..
  96. Az Alpine Linux musl libc |-re váltott  Alpine Linux . alpinelinux.org. Letöltve: 2019. február 2. Az eredetiből archiválva : 2019. február 3..
  97. musl - Void Linux Handbook . docs.voidlinux.org . Letöltve: 2022. január 29. Az eredetiből archiválva : 2021. december 9..
  98. A CRT könyvtár jellemzői . docs.microsoft.com. Letöltve: 2019. február 2. Az eredetiből archiválva : 2021. augusztus 7..
  99. Támogatott nyelvek - Funkciók | CLion  (angol) . jetbrains. Letöltve: 2019. február 23. Az eredetiből archiválva : 2019. március 25.
  100. ↑ 1 2 Ellenőrzés 0.10.0: 2. Unit Testing C  nyelven . check.sourceforge.net. Letöltve: 2019. február 23. Az eredetiből archiválva : 2018. június 5..
  101. ↑ 6. Callgrind : egy hívásgráf, amely gyorsítótárat és elágazás előrejelzést generál  . Valgrind dokumentáció . valgrind.org. Letöltve: 2019. május 21. Az eredetiből archiválva : 2019. május 23.
  102. Kcachegrind . kcachegrind.sourceforge.net. Letöltve: 2019. május 21. Az eredetiből archiválva : 2019. április 6..
  103. Emscripten LLVM-JavaScript fordító . Letöltve: 2012. szeptember 25. Az eredetiből archiválva : 2012. december 17..
  104. Flash C++ fordító . Letöltve: 2013. január 25. Az eredetiből archiválva : 2013. május 25.
  105. Project Clue a SourceForge.net oldalon
  106. Axiomatic Solutions Sdn Bhd . Hozzáférés dátuma: 2009. március 7. Az eredetiből archiválva : 2009. február 23.
  107. ClangFormat - Clang 9  dokumentáció . clang.llvm.org. Letöltve: 2019. március 5. Az eredetiből archiválva : 2019. március 6..
  108. indent(1) - Linux  kézikönyvoldal . linux.die.net. Letöltve: 2019. március 5. Az eredetiből archiválva : 2019. május 13.
  109. Wolfram Research, Inc. RENDSZER INTERFÉSZEK ÉS  BEVEZETÉS . Wolfram Mathematica® Tutorial Collection 36-37. library.wolfram.com (2008). Letöltve: 2019. május 29. Az eredetiből archiválva : 2015. szeptember 6..
  110. Cleve Moler. A MATLAB és a MathWorks növekedése két évtized alatt . TheMathWorks News&Notes . www.mathworks.com (2006. január). Letöltve: 2019. május 29. Az eredetiből archiválva : 2016. március 4.
  111. sched_setscheduler  . _ pubs.opengroup.org. Hozzáférés dátuma: 2019. február 4. Az eredetiből archiválva : 2019. február 24.
  112. clock_gettime  . _ pubs.opengroup.org. Hozzáférés dátuma: 2019. február 4. Az eredetiből archiválva : 2019. február 24.
  113. óra_nanosalvás  . _ pubs.opengroup.org. Hozzáférés dátuma: 2019. február 4. Az eredetiből archiválva : 2019. február 24.
  114. M. Jones. A valós idejű Linux architektúrák anatómiája . www.ibm.com (2008. október 30.). Letöltve: 2019. február 4. Az eredetiből archiválva : 2019. február 7..
  115. TIOBE Index  . www.tiobe.com . Letöltve: 2019. február 2. Az eredetiből archiválva : 2018. február 25.
  116. Stroustrup, Bjarne Egy nyelv fejlesztése a való világban: C++ 1991-2006 . Letöltve: 2018. július 9. Az eredetiből archiválva : 2007. november 20.
  117. Stroustrup GYIK . www.stroustrup.com. Letöltve: 2019. június 3. Az eredetiből archiválva : 2016. február 6..
  118. 0. melléklet: Kompatibilitás. 1.2. C++ és ISO C . Munkadokumentum az információs rendszerek nemzetközi szabványának javasolt tervezetéhez – C++ programozási nyelv (1996. december 2.). — lásd 1.2.1p3 (3. bekezdés az 1.2.1. szakaszban). Letöltve: 2009. június 6. Az eredetiből archiválva : 2011. augusztus 22..
  119. 1 2 3 4 Stolyarov, 2010 , 1. Előszó, p. 79.
  120. Nyelvek krónikája. Si . "Nyitott rendszerek" kiadó. Letöltve: 2018. december 8. Az eredetiből archiválva : 2018. december 9..
  121. Allen I. Holub. Elég kötél ahhoz, hogy lábon lődd magad: A C és C++ programozás szabályai . - McGraw-Hill, 1995. - 214 p. — ISBN 9780070296893 . Archiválva : 2018. december 9. a Wayback Machine -nál
  122. A GNU fordítógyűjtemény (GCC) használata: Figyelmeztetési beállítások . gcc.gnu.org. Letöltve: 2018. december 8. Az eredetiből archiválva : 2018. december 5..
  123. Diagnosztikai zászlók a Clang-Clang 8 dokumentációjában . clang.llvm.org. Letöltve: 2018. december 8. Az eredetiből archiválva : 2018. december 9..
  124. MemorySanitizer - Clang 8  dokumentáció . clang.llvm.org. Letöltve: 2018. december 8. Az eredetiből archiválva : 2018. december 1..
  125. MEM00-C. Memória lefoglalása és felszabadítása ugyanabban a modulban, ugyanazon absztrakciós szinten - SEI CERT C kódolási szabvány -  Confluence . wiki.sei.cmu.edu. Letöltve: 2019. június 4. Az eredetiből archiválva : 2019. június 4..
  126. MEM04-C. Ügyeljen a nulla hosszúságú kiosztásokra - SEI CERT C kódolási szabvány -  Confluence . wiki.sei.cmu.edu. Letöltve: 2019. január 11. Az eredetiből archiválva : 2019. január 12.
  127. Objektum memóriakezelés: GObject Reference Manual . developer.gnome.org. Letöltve: 2018. december 9. Az eredetiből archiválva : 2018. szeptember 7..
  128. Például a snai.pe c-smart-pointers archiválva : 2018. augusztus 14. a Wayback Machine -nél
  129. Szemétgyűjtés a C programokban . Letöltve: 2019. május 16. Az eredetiből archiválva : 2019. március 27.
  130. ↑ 1 2 CERN számítógép-biztonsági információk . security.web.cern.ch. Letöltve: 2019. január 12. Az eredetiből archiválva : 2019. január 5..
  131. CWE - CWE-170: Nem megfelelő nulla lezárás (3.2  ) . cwe.mitre.org. Letöltve: 2019. január 12. Az eredetiből archiválva : 2019. január 13.
  132. STR32-C. Ne adjon át nem nulla végű karaktersorozatot olyan könyvtári függvénynek, amely karakterláncot vár - SEI CERT C kódolási szabvány -  Confluence . wiki.sei.cmu.edu. Letöltve: 2019. január 12. Az eredetiből archiválva : 2019. január 13.
  133. DCL50-CPP. Ne definiáljon C-stílusú variadic függvényt - SEI CERT C++ kódolási szabvány -  Confluence . wiki.sei.cmu.edu. Letöltve: 2019. május 25. Az eredetiből archiválva : 2019. május 25.
  134. EXP47-C. Ne hívja meg a va_arg paramétert hibás típusú argumentummal - SEI CERT C Coding Standard -  Confluence . wiki.sei.cmu.edu. Letöltve: 2018. december 8. Az eredetiből archiválva : 2018. december 9..
  135. SEI CERT C kódolási szabvány - SEI CERT C kódolási szabvány - Confluence . wiki.sei.cmu.edu. Letöltve: 2018. december 9. Az eredetiből archiválva : 2018. december 8..
  136. Bevezetés – SEI CERT C kódolási szabvány – Confluence . wiki.sei.cmu.edu. Letöltve: 2019. május 24. Az eredetiből archiválva : 2019. május 24.
  137. CON33-C.  Kerülje el a versenyfeltételeket a könyvtári funkciók használatakor - SEI CERT C kódolási szabvány - Confluence . wiki.sei.cmu.edu. Letöltve: 2019. január 23. Az eredetiből archiválva : 2019. január 23.

Irodalom

  • ISO/IEC. ISO/IEC9899:2017 . Programozási nyelvek - C (downlink) . www.open-std.org (2017) . Letöltve: 2018. december 3. Az eredetiből archiválva : 2018. október 24.. 
  • Kernigan B. , Ritchie D. A C programozási nyelv = A C programozási nyelv. - 2. kiadás - M .: Williams , 2007. - S. 304. - ISBN 0-13-110362-8 .
  • Gukin D. A C programozási nyelv a dummieshoz = C For Dummies. - M . : Dialektika , 2006. - S. 352. - ISBN 0-7645-7068-4 .
  • Podbelsky V. V., Fomin S. S. Programozási tanfolyam C nyelven: tankönyv . - M. : DMK Press, 2012. - 318 p. - ISBN 978-5-94074-449-8 .
  • Prata S. A C programozási nyelv: Előadások és gyakorlatok = C Primer Plus. - M. : Williams, 2006. - S. 960. - ISBN 5-8459-0986-4 .
  • Prata S. A C programozási nyelv (C11). Előadások és gyakorlatok, 6. kiadás = C Primer Plus, 6. kiadás. - M. : Williams, 2015. - 928 p. - ISBN 978-5-8459-1950-2 .
  • Stolyarov A. V. A C nyelv és a kezdeti programozási képzés  // A CMC MSU karának fiatal tudósainak cikkgyűjteménye. - A Moszkvai Állami Egyetem CMC karának kiadói osztálya, 2010. - 7. sz . - S. 78-90 .
  • Schildt G. C: The Complete Reference, Classic Edition = C: The Complete Reference, 4. kiadás. - M .: Williams , 2010. - S. 704. - ISBN 978-5-8459-1709-6 .
  • Programozási nyelvek Ada, C, Pascal = Az Ada, C és Pascal programozási nyelvek összehasonlítása és értékelése / A. Feuer, N. Jehani. - M . : Rádió és Sayaz, 1989. - 368 p. — 50.000 példány.  — ISBN 5-256-00309-7 .

Linkek