C | |
---|---|
Kieliluokka | menettelyllinen |
Toteutustyyppi | koottu |
Esiintyi | 1972 |
Tekijä | Dennis Ritchie |
Kehittäjä | Bell Labs , Dennis Ritchie [1] , US National Standards Institute , ISO ja Ken Thompson |
Tiedostotunniste _ | .c— kooditiedostot, .h— otsikkotiedostot |
Vapauta | ISO/IEC 9899:2018 ( 5. heinäkuuta 2018 ) |
Tyyppijärjestelmä | staattinen heikko |
Tärkeimmät toteutukset | GCC , Clang , TCC , Turbo C , Watcom , Oracle Solaris Studio C, Pelles C |
Murteet |
"K&R" C ( 1978 ) ANSI C ( 1989 ) C99 ( 1999 ) C11 ( 2011 ) |
Vaikutettu | BCPL , B |
vaikutti | C++ , Objective-C , C# , Java , Nim |
OS | Microsoft Windows ja Unix kaltainen käyttöjärjestelmä |
Mediatiedostot Wikimedia Commonsissa |
ISO/IEC 9899 | |
Tietotekniikka — Ohjelmointikielet — C | |
Kustantaja | Kansainvälinen standardointijärjestö (ISO) |
Verkkosivusto | www.iso.org |
Toimikunta (kehittäjä) | ISO/IEC JTC 1/SC 22 |
komitean verkkosivuilla | Ohjelmointikielet, niiden ympäristöt ja järjestelmäohjelmistoliitännät |
ISS (ICS) | 35.060 |
Nykyinen painos | ISO/IEC 9899:2018 |
Edelliset painokset | ISO/IEC 9899:1990/COR2:1996 ISO/IEC 9899:1999/COR3:2007 ISO/IEC 9899:2011/COR1:2012 |
C ( latinan kirjaimesta C , englannin kieli ) on yleiskäyttöinen käännetty staattisesti kirjoitettu ohjelmointikieli , jonka Bell Labsin työntekijä Dennis Ritchie kehitti vuosina 1969-1973 Bee -kielen kehitykseksi . Se kehitettiin alun perin toteuttamaan UNIX - käyttöjärjestelmää , mutta on sittemmin siirretty monille muille alustoille. Suunnittelultaan kieli sopii tiiviisti tyypillisiin koneen ohjeisiin ja on löytänyt käyttöä projekteissa, jotka olivat syntyperäisiä assembly-kielelle , mukaan lukien sekä käyttöjärjestelmät että erilaiset sovellusohjelmistot erilaisille laitteille supertietokoneista sulautettuihin järjestelmiin . C-ohjelmointikielellä on ollut merkittävä vaikutus ohjelmistoteollisuuden kehitykseen, ja sen syntaksista tuli perusta sellaisille ohjelmointikielille kuin C++ , C# , Java ja Objective-C .
C-ohjelmointikieli kehitettiin vuosina 1969-1973 Bell Labsissa , ja vuoteen 1973 mennessä suurin osa UNIX - ytimestä , joka oli alun perin kirjoitettu PDP-11 /20-asentajalla, oli kirjoitettu uudelleen tälle kielelle. Kielen nimestä tuli looginen jatko vanhalle kielelle " Bi " [a] , jonka monet piirteet otettiin perustaksi.
Kielen kehittyessä se standardoitiin ensin nimellä ANSI C , ja sitten ISO :n kansainvälinen standardointikomitea hyväksyi tämän standardin nimellä ISO C, joka tunnetaan myös nimellä C90. C99-standardi lisäsi kieleen uusia ominaisuuksia, kuten muuttuvapituisia taulukoita ja rivifunktioita. Ja C11 -standardissa kieleen lisättiin streamien toteutus ja tuki atomityypeille. Siitä lähtien kieli on kuitenkin kehittynyt hitaasti, ja vain C11-standardin bugikorjaukset pääsivät C18-standardiin.
C-kieli suunniteltiin järjestelmän ohjelmointikieleksi, jolle voitiin luoda yhden vaiheen kääntäjä . Myös vakiokirjasto on pieni. Näiden tekijöiden seurauksena kääntäjiä on suhteellisen helppo kehittää [2] . Siksi tämä kieli on saatavilla useilla alustoilla. Lisäksi kieli on matalatasoisuudestaan huolimatta keskittynyt siirrettävyyteen. Eri tietokonearkkitehtuureille voidaan kääntää kielistandardin mukaisia ohjelmia.
Kielen tavoitteena oli helpottaa suurten ohjelmien kirjoittamista minimaalisilla virheillä assembleriin verrattuna, noudattaen prosessiohjelmoinnin periaatteita , mutta välttäen kaikkea, mikä aiheuttaisi korkean tason kielille ominaisia lisäkustannuksia.
C:n tärkeimmät ominaisuudet:
Samaan aikaan C:stä puuttuu:
Osa puuttuvista ominaisuuksista voidaan simuloida sisäänrakennetuilla työkaluilla (esimerkiksi korutiineja voidaan simuloida käyttämällä setjmpjalongjmp -toimintoja ), osa lisätään kolmannen osapuolen kirjastojen avulla (esimerkiksi moniajo- ja verkkotoimintojen tukemiseksi voit käyttää kirjastot pthreads , sockets ja vastaavat, on kirjastoja, jotka tukevat automaattista roskienkeräystä [3] ), osa on toteutettu joissakin kääntäjissä kielilaajennuksina (esimerkiksi sisäkkäiset funktiot GCC :ssä ). On olemassa hieman hankala, mutta varsin toimiva tekniikka, joka mahdollistaa OOP -mekanismien toteuttamisen C:ssä [4] , joka perustuu C:n osoittimien todelliseen polymorfismiin ja osoittimien tukeen tämän kielen funktioihin. Tähän malliin perustuvat OOP-mekanismit on toteutettu GLib -kirjastossa ja niitä käytetään aktiivisesti GTK+ -kehyksessä . GLib tarjoaa perusluokan GObject, mahdollisuuden periä yhdestä luokasta [5] ja toteuttaa useita rajapintoja [6] .
Käyttöönoton jälkeen kieli otettiin hyvin vastaan, koska se mahdollisti kääntäjien nopean luomisen uusille alustoille ja antoi ohjelmoijille mahdollisuuden olla melko tarkkoja ohjelmiensa suorittamisessa. Koska C-ohjelmat olivat lähellä matalan tason kieliä, ne toimivat tehokkaammin kuin monilla muilla korkean tason kielillä kirjoitetut, ja vain käsin optimoitu kokoonpanokielikoodi pystyi ajamaan vielä nopeammin, koska se antoi täyden hallinnan koneeseen. Tähän mennessä kääntäjien kehitys ja prosessorien monimutkaisuus ovat johtaneet siihen, että käsin kirjoitetulla kokoonpanokoodilla (lukuun ottamatta ehkä hyvin lyhyitä ohjelmia) ei ole käytännössä mitään etua kääntäjien luomaan koodiin verrattuna, kun taas C on edelleen yksi parhaista. tehokkaita korkean tason kieliä.
Kielessä käytetään kaikkia latinalaisten aakkosten merkkejä , numeroita ja joitain erikoismerkkejä [7] .
Latinalaisen aakkosten merkit |
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 |
Numerot | 0, 1, 2, 3, 4, 5, 6, 7, 8,9 |
Erikoissymbolit | , (pilkku) , ;, . (piste) , +, -, *, ^, & (et-merkki) , =, ~ (tilde) , !, /, <, >, (, ), {, }, [, ], |, %, (heittomerkki)? , (lainausmerkit) , (kaksoispiste) , ( alaviiva ) ) , ,' " : _ \# |
Tokenit muodostetaan kelvollisista merkeistä - ennalta määritetyistä vakioista , tunnisteista ja operaatiomerkeistä . Lekseemit puolestaan ovat osa ilmaisuja ; ja lausekkeet ja operaattorit koostuvat lausekkeista .
Kun ohjelma käännetään C:ksi, ohjelmakoodista poimitaan lekseemejä, joiden pituus on enimmäispituus ja jotka sisältävät kelvollisia merkkejä. Jos ohjelma sisältää virheellisen merkin, leksikaalinen analysaattori (tai kääntäjä) tuottaa virheen ja ohjelman kääntäminen on mahdotonta.
Symboli #ei voi olla osa mitään merkkiä, ja sitä käytetään esiprosessorissa .
TunnisteetKelvollinen tunniste on sana, joka voi sisältää latinalaisia merkkejä, numeroita ja alaviivoja [8] . Tunnisteet annetaan operaattoreille, vakioille, muuttujille, tyypeille ja funktioille.
Avainsanatunnisteita ja sisäänrakennettuja tunnisteita ei voi käyttää ohjelmaobjektin tunnisteina. On myös varattu tunnisteita, joille kääntäjä ei anna virheitä, mutta joista voi tulevaisuudessa muodostua avainsanoja, mikä johtaa yhteensopimattomuuteen.
On vain yksi sisäänrakennettu tunniste - __func__, joka määritellään vakiomerkkijonoksi, joka on implisiittisesti ilmoitettu jokaisessa funktiossa ja joka sisältää sen nimen [8] .
LiteraalivakiotErityisesti muotoiltuja C:n literaaleja kutsutaan vakioiksi. Literaalivakiot voivat olla kokonaislukuja, reaalilukuja, merkkiä [9] ja merkkijonoa [10] .
Kokonaisluvut asetetaan oletuksena desimaaleina . Jos etuliite on määritetty 0x, se on heksadesimaali . 0-etuliite osoittaa, että numero on oktaali . Suffiksi määrittää vakiotyypin vähimmäiskoon ja määrittää myös, onko numero etumerkitty vai etumerkitmätön. Lopullinen tyyppi on pienin mahdollinen, jossa annettu vakio voidaan esittää [11] .
Suffiksi | Desimaalille | Oktaali- ja heksadesimaalilukuille |
---|---|---|
Ei | int
long long long |
int
unsigned int long unsigned long long long unsigned long long |
utaiU | unsigned int
unsigned long unsigned long long |
unsigned int
unsigned long unsigned long long |
ltaiL | long
long long |
long
unsigned long long long unsigned long long |
utai Uyhdessä ltaiL | unsigned long
unsigned long long |
unsigned long
unsigned long long |
lltaiLL | long long | long long
unsigned long long |
utai Uyhdessä lltaiLL | unsigned long long | unsigned long long |
Desimaali
muoto |
Eksponentin kanssa | Heksadesimaali
muoto |
---|---|---|
1.5 | 1.5e+0 | 0x1.8p+0 |
15e-1 | 0x3.0p-1 | |
0.15e+1 | 0x0.cp+1 |
Reaalilukuvakiot ovat oletusarvoisesti tyyppisiä double. Suffiksia määritettäessä ftyyppi määritetään vakiolle ja floatmääritettäessä or- . Vakiota pidetään todellisena, jos se sisältää pistemerkin tai kirjaimen tai jos kyseessä on heksadesimaalimerkintä, jossa on etuliite . Desimaalimerkintä voi sisältää eksponentin kirjainten tai . Heksadesimaalimerkinnässä eksponentti määritetään kirjainten jälkeen tai on pakollinen, mikä erottaa todelliset heksadesimaalivakiot kokonaisluvuista. Heksadesimaalimuodossa eksponentti on 2:n potenssi [12] . lLlong doublepP0xeEpP
Merkkivakiot on suljettu lainausmerkkeihin ( '), ja etuliite määrittää sekä merkkivakion tietotyypin että koodauksen, jossa merkki esitetään. C:ssä merkkivakio ilman etuliitettä on tyyppiä int[13] , toisin kuin C++ , jossa merkkivakio on char.
Etuliite | Tietotyyppi | Koodaus |
---|---|---|
Ei | int | ASCII |
u | char16_t | 16-bittinen monitavuinen merkkijonokoodaus |
U | char32_t | 32-bittinen monitavuinen merkkijonokoodaus |
L | wchar_t | Leveä merkkijonokoodaus |
Merkkijonoliteraalit on suljettu lainausmerkkeihin ja niihin voidaan liittää merkkijonon tietotyyppi ja koodaus. Merkkijonoliteraalit ovat tavallisia taulukoita. Kuitenkin monitavuisissa koodauksissa, kuten UTF-8 , yksi merkki voi sisältää useamman kuin yhden taulukkoelementin. Itse asiassa merkkijonoliteraalit ovat const [14] , mutta toisin kuin C++, niiden tietotyypit eivät sisällä muuntajaa const.
Etuliite | Tietotyyppi | Koodaus |
---|---|---|
Ei | char * | ASCII tai monitavuinen koodaus |
u8 | char * | UTF-8 |
u | char16_t * | 16-bittinen monitavuinen koodaus |
U | char32_t * | 32-bittinen monitavuinen koodaus |
L | wchar_t * | Leveä merkkijonokoodaus |
Useita peräkkäisiä välilyönnillä tai rivinvaihdoilla erotettuja merkkijonovakioita yhdistetään käännöksen yhteydessä yhdeksi merkkijonoksi, jota käytetään usein merkkijonon koodin tyylistämiseen erottamalla merkkijonovakion osia eri riveillä luettavuuden parantamiseksi [16] .
Nimetyt vakiotMakro | #define BUFFER_SIZE 1024 |
Anonyymi luettelointi |
enum { BUFFER_SIZE = 1024 }; |
Muuttuja vakiona _ |
const int puskurin_koko = 1024 ; extern const int puskurin_koko ; |
C-kielessä vakioiden määrittämiseen on tapana käyttää makromäärityksiä, jotka on ilmoitettu preprocessor-direktiivillä [17] : #define
#define vakionimi [ arvo ]Tällä tavalla lisätty vakio on voimassa laajuudessaan vakion asettamishetkestä ohjelmakoodin loppuun asti tai kunnes annetun vakion vaikutus kumotaan käskyllä #undef:
#undef vakio nimiKuten minkä tahansa makron kohdalla, nimetyn vakion tapauksessa vakion arvo korvataan automaattisesti ohjelmakoodissa aina, kun vakion nimeä käytetään. Siksi makron sisällä kokonaislukuja tai reaalilukuja määritettäessä saattaa olla tarpeen määrittää tietotyyppi eksplisiittisesti käyttämällä sopivaa kirjaimellista loppuliitettä, muuten luku on oletuksena tyyppi int, jos kyseessä on kokonaisluku tai tyyppi double , jos kyseessä on todellinen.
Kokonaisluvuille on toinen tapa luoda nimettyjä vakioita - operaattorienummien enum[17] avulla . Tämä menetelmä soveltuu kuitenkin vain tyypeille, jotka ovat pienempiä tai yhtä suuria kuin tyyppi , eikä sitä käytetä vakiokirjastossa [18] . int
On myös mahdollista luoda vakioita muuttujiksi tarkenteen constavulla, mutta toisin kuin kahdessa muussa menetelmässä, tällaiset vakiot kuluttavat muistia, niihin voidaan osoittaa, eikä niitä voida käyttää käännöshetkellä [17] :
Avainsanat ovat tunnisteita, jotka on suunniteltu suorittamaan tietty tehtävä käännösvaiheessa tai antamaan vihjeitä ja ohjeita kääntäjälle.
Avainsanat | Tarkoitus | Vakio |
---|---|---|
sizeof | Objektin koon saaminen käännöshetkellä | C89 |
typedef | Vaihtoehtoisen nimen määrittäminen tyypille | |
auto,register | Kääntäjän vihjeitä muuttujien tallennuspaikalle | |
extern | Kääntäjän käskeminen etsimään objektia nykyisen tiedoston ulkopuolelta | |
static | Staattisen objektin ilmoittaminen | |
void | Ei arvomerkkiä; osoittimissa tarkoittaa mielivaltaista dataa | |
char... short_ int_long | Kokonaislukutyypit ja niiden koon muuttajat | |
signed,unsigned | Kokonaislukutyyppiset muokkaimet, jotka määrittelevät ne allekirjoitetuiksi tai allekirjoittamattomiksi | |
float,double | Todelliset tietotyypit | |
const | Tietotyypin muuntaja, joka kertoo kääntäjälle, että kyseisen tyypin muuttujat ovat vain luku -tilassa | |
volatile | Kääntäjän käsky muuttamaan muuttujan arvoa ulkopuolelta | |
struct | Tietotyyppi, määritetty rakenteeksi, jossa on joukko kenttiä | |
enum | Tietotyyppi, joka tallentaa yhden kokonaislukuarvojen joukosta | |
union | Tietotyyppi, joka voi tallentaa tietoja eri tietotyyppien esityksiin | |
do. for_while | Loop-lauseet | |
if,else | Ehdollinen operaattori | |
switch. case_default | Operaattorin valinta kokonaislukuparametrin mukaan | |
break,continue | Loop Break -lauseet | |
goto | Ehdoton hyppyoperaattori | |
return | Paluu funktiosta | |
inline | Sisäinen funktion ilmoitus | C99 [20] |
restrict | Osoittimen ilmoittaminen, joka viittaa muistilohkoon, johon mikään muu osoitin ei viittaa | |
_Bool[b] | boolen tietotyyppi | |
_Complex[c] ,_Imaginary [d] | Kompleksilukulaskelmissa käytetyt tyypit | |
_Atomic | Tyyppimuuntaja, joka tekee siitä atomisen | C11 |
_Alignas[e] | Tietotyypin tavutasauksen nimenomainen määrittäminen | |
_Alignof[f] | Tietyn tietotyypin kohdistuksen hakeminen käännöshetkellä | |
_Generic | Yhden arvojoukon valitseminen käännöshetkellä ohjatun tietotyypin perusteella | |
_Noreturn[g] | Osoittaa kääntäjälle, että funktio ei voi päättyä normaalisti (eli return) | |
_Static_assert[h] | Käännöshetkellä tarkistettavien väitteiden määrittäminen | |
_Thread_local[i] | Säikeen paikallismuuttujan ilmoittaminen |
Avainsanojen lisäksi kielistandardi määrittelee varatut tunnisteet, joiden käyttö voi johtaa yhteensopimattomuuteen standardin tulevien versioiden kanssa. Kaikki paitsi avainsanasanat, jotka alkavat alaviivalla ( _), jota seuraa joko iso kirjain ( A- Z) tai toinen alaviiva [21] , on varattu . C99- ja C11-standardeissa joitain näistä tunnisteista käytettiin uusien kielten avainsanoihin.
_Tiedoston laajuudessa alaviivalla ( ) [21] alkavien nimien käyttö on varattu , eli se on sallittua nimetä tyyppejä, vakioita ja muuttujia, jotka on ilmoitettu käskylohkon sisällä, esimerkiksi funktioiden sisällä, alaviivalla.
Varattuina tunnisteina ovat myös kaikki vakiokirjaston makrot ja linkitysvaiheessa linkitettävät nimet [21] .
Varattujen tunnisteiden käyttö ohjelmissa on standardin mukaan määrittelemätön käyttäytyminen . Jos yritetään peruuttaa mikä tahansa vakiomakro vialla #undef, seurauksena on myös määrittelemätön toiminta [21] .
C - ohjelman teksti voi sisältää fragmentteja , jotka eivät ole osa ohjelmakoodia - kommentteja . Kommentit on merkitty erityisellä tavalla ohjelman tekstiin ja ohitetaan kokoamisen aikana.
Aluksi C89- standardissa oli saatavilla tekstin sisäisiä kommentteja, jotka voitiin sijoittaa merkkijonojen /*ja merkkijonojen väliin */. Tässä tapauksessa on mahdotonta upottaa kommenttia toiseen, koska ensimmäinen havaittu sekvenssi */lopettaa kommentin ja */kääntäjä näkee merkintää välittömästi seuraavan tekstin ohjelman lähdekoodina.
Seuraava standardi, C99 , esitteli vielä toisen tavan merkitä kommentteja: kommentiksi katsotaan teksti, joka alkaa merkkijonolla //ja päättyy rivin loppuun [20] .
Kommentteja käytetään usein lähdekoodin itsedokumentoimiseen, monimutkaisten osien selittämiseen, tiettyjen tiedostojen tarkoituksen kuvaamiseen ja tiettyjen funktioiden, makrojen, tietotyyppien ja muuttujien käytön ja käytön sääntöjen kuvaamiseen. On jälkiprosessoreita, jotka voivat muuntaa erityisesti muotoillut kommentit dokumentaatioksi. Tällaisten C-kielen jälkiprosessorien joukossa Doxygen- dokumentaatiojärjestelmä voi toimia .
Lausekkeissa käytetyt operaattorit ovat operandeille suoritettuja operaatioita, jotka palauttavat lasketun arvon - operaation tuloksen. Operandi voi olla vakio, muuttuja, lauseke tai funktiokutsu. Operaattori voi olla erikoismerkki, erikoismerkkijoukko tai erikoissana. Operaattoreita erottaa mukana olevien operandien lukumäärä, nimittäin ne erottavat toisistaan yksi-, binääri- ja kolmioperaattorit.
UnaarioperaattoritUnaarioperaattorit suorittavat toiminnon yhdelle argumentille ja niillä on seuraava operaatiomuoto:
[ operaattori ] [ operandi ]Postfixin lisäys- ja vähennysoperaatioilla on käänteinen muoto:
[ operandi ] [ operaattori ]+ | yksipuolinen plus | ~ | Palautuskoodin ottaminen | & | Osoitteen ottaminen | ++ | Etuliitteen tai jälkiliitteen lisäys | sizeof | Muistissa olevan objektin käyttämien tavujen lukumäärän saaminen; voidaan käyttää sekä operaattorina että operaattorina |
- | yksipuolinen miinus | ! | loogista kieltämistä | * | Osoittimen viittauksen poisto | -- | Etuliitteen tai jälkiliitteen vähennys | _Alignof | Haetaan tasausta tietylle tietotyypille |
Inkrementi- ja vähennysoperaattorit muuttavat operandinsa arvoa, toisin kuin muut unaarioperaattorit. Etuliiteoperaattori muuttaa ensin arvoa ja palauttaa sen sitten. Postfix palauttaa ensin arvon ja vasta sitten muuttaa sitä.
BinäärioperaattoritBinäärioperaattorit sijaitsevat kahden argumentin välissä ja suorittavat niille toiminnon:
[ operandi ] [ operaattori ] [ operandi ]+ | Lisäys | % | Ottaa loput divisioonasta | << | Bittisuuntainen vasen vaihto | > | Lisää | == | Yhtä |
- | Vähennyslasku | & | Bitittain JA | >> | Hieman siirto oikealle | < | Vähemmän | != | Ei tasa-arvoista |
* | Kertominen | | | Bittikohtaisesti TAI | && | looginen JA | >= | Suurempi tai yhtä suuri | ||
/ | Division | ^ | Bitittainen XOR | || | Looginen TAI | <= | Pienempi tai yhtä suuri |
Myös C:n binäärioperaattorit sisältävät vasemmanpuoleisia osoitusoperaattoreita, jotka suorittavat toiminnon vasemmalle ja oikealle argumentille ja asettavat tuloksen vasemmanpuoleiseen argumenttiin.
= | Oikean argumentin arvon määrittäminen vasemmalle | %= | Vasemman operandin jakamisen jäännös oikealla | ^= | Oikean operandin bittikohtainen XOR vasempaan operandiin |
+= | Lisäys oikean vasempaan operandiin | /= | Vasemman operandin jako oikealla | <<= | Vasemman operandin bittisiirto vasemmalle oikean operandin antamalla bittimäärällä |
-= | Vähennys oikeanpuoleisesta operandista | &= | Bittikohtaisesti JA oikea operandi vasemmalle | >>= | Vasemman operandin bittisiirto oikealle oikean operandin määrittämän bittimäärän verran |
*= | Vasemman operandin kertominen oikealla | |= | Oikean operandin bittikohtainen TAI vasemmalle |
C:ssä on vain yksi kolmiosainen operaattori, lyhennetty ehdollinen operaattori, jolla on seuraava muoto:
[ ehto ] ?[ lauseke1 ] :[ lauseke2 ]Lyhyellä ehdollisella operaattorilla on kolme operandia:
Operaattori on tässä tapauksessa merkkien ?ja yhdistelmä :.
Lauseke on vakioiden, muuttujien ja funktioiden järjestetty joukko operaatioita. Lausekkeet sisältävät operandeista ja operaattoreista koostuvia operaatioita . Toimintojen suoritusjärjestys riippuu tietuemuodosta ja toimintojen tärkeydestä. Jokaisella lausekkeella on arvo - tulos kaikkien lausekkeeseen sisältyvien toimintojen suorittamisesta. Lausekkeen arvioinnin aikana operaatioista riippuen muuttujien arvot voivat muuttua ja funktioita voidaan myös suorittaa, jos niiden kutsuja esiintyy lausekkeessa.
Lausekkeiden joukossa erotetaan vasemmanpuoleisten lausekkeiden luokka - lausekkeet, jotka voivat olla tehtävämerkin vasemmalla puolella.
Operaatioiden suorittamisen prioriteettiToimintojen prioriteetti on määritelty standardissa ja se määrittelee toimintojen suoritusjärjestyksen. Toiminnot C:ssä suoritetaan alla olevan tärkeysjärjestystaulukon mukaisesti [25] [26] .
Prioriteetti | rahakkeita | Operaatio | Luokka | Assosiatiivisuus |
---|---|---|---|---|
yksi | a[indeksi] | Viittaus indeksillä | postfix | vasemmalta oikealle → |
f(argumentteja) | Toimintokutsu | |||
. | Kenttäpääsy | |||
-> | Kentälle pääsy osoittimella | |||
++ -- | Positiivinen ja negatiivinen lisäys | |||
(tyypin nimen ) {alustus} | Yhdistetty literaali (C99) | |||
(tyypin nimen ) {alustus,} | ||||
2 | ++ -- | Positiiviset ja negatiiviset etuliitteen lisäykset | yksipuolinen | ← oikealta vasemmalle |
sizeof | Koon saaminen | |||
_Alignof[f] | Hanki tasaus ( C11 ) | |||
~ | Bitittain EI | |||
! | Loogista EI | |||
- + | Merkkimerkintä (miinus tai plus) | |||
& | Osoitteen saaminen | |||
* | Osoittimen viite (poisviittaus) | |||
(tyypin nimi) | Tyyppivalu | |||
3 | * / % | Kerto-, jako- ja jakojäännös | binääri | vasemmalta oikealle → |
neljä | + - | Yhteen-ja vähennyslasku | ||
5 | << >> | Vaihto vasemmalle ja oikealle | ||
6 | < > <= >= | Vertailutoiminnot | ||
7 | == != | Tasa-arvon tai eriarvoisuuden tarkistaminen | ||
kahdeksan | & | Bitittain JA | ||
9 | ^ | Bitittainen XOR | ||
kymmenen | | | Bittikohtaisesti TAI | ||
yksitoista | && | looginen JA | ||
12 | || | Looginen TAI | ||
13 | ? : | Kunto | kolmiosainen | ← oikealta vasemmalle |
neljätoista | = | Arvon määritys | binääri | |
+= -= *= /= %= <<= >>= &= ^= |= | Toiminnot vasemman arvon muuttamiseen | |||
viisitoista | , | Jaksollinen laskenta | vasemmalta oikealle → |
Operaattoreiden prioriteetit C:ssä eivät aina oikeuta itseään ja johtavat joskus intuitiivisesti vaikeasti ennakoitaviin tuloksiin. Esimerkiksi koska unaarisilla operaattoreilla on oikealta vasemmalle assosiatiivisuus, lausekkeen arviointi *p++johtaa osoittimen lisäykseen, jota seuraa viittaus ( *(p++)), osoittimen lisäyksen ( (*p)++) sijaan. Siksi vaikeasti ymmärrettävissä tilanteissa on suositeltavaa ryhmitellä lausekkeet selkeästi hakasulkeilla [26] .
Toinen tärkeä C-kielen ominaisuus on, että funktiokutsuun siirrettyjen argumenttiarvojen arviointi ei ole peräkkäistä [27] , eli argumentteja erottava pilkku ei vastaa peräkkäistä arviointia etusijataulukosta. Seuraavassa esimerkissä toiselle funktiolle argumenteiksi annetut funktiokutsut voivat olla missä tahansa järjestyksessä:
int x ; x = laske ( get_arg1 (), get_arg2 ()); // kutsu ensin get_arg2().Et voi myöskään luottaa operaatioiden ensisijaisuuteen lausekkeen arvioinnin aikana ilmenevien sivuvaikutusten yhteydessä, koska tämä johtaa määrittelemättömään käyttäytymiseen [27] .
Sekvenssipisteet ja sivuvaikutuksetKielistandardin liite C määrittelee joukon sekvenssipisteitä , joilla ei takuulla ole jatkuvia sivuvaikutuksia laskelmista. Toisin sanoen järjestyspiste on laskutoimitusvaihe, joka erottaa lausekkeiden arvioinnin keskenään siten, että ennen sarjakohtaa tapahtuneet laskelmat sivuvaikutukset mukaan lukien ovat jo päättyneet ja sekvenssipisteen jälkeen eivät ole vielä alkaneet [28 ] . Sivuvaikutus voi olla muuttujan arvon muutos lausekkeen arvioinnin aikana. Laskennassa mukana olevan arvon muuttaminen yhdessä sivuvaikutuksen kanssa, joka aiheutuu saman arvon muuttamisesta seuraavaan sekvenssipisteeseen, johtaa määrittelemättömään käyttäytymiseen. Sama tapahtuu, jos laskennassa on mukana kaksi tai useampia sivumuutoksia samaan arvoon [27] .
Reittipiste | Tapahtuma ennen | Tapahtuman jälkeen |
---|---|---|
Toimintokutsu | Osoittimen laskeminen funktioon ja sen argumentteihin | Toimintokutsu |
Loogiset AND-operaattorit ( &&), OR ( ||) ja peräkkäinen laskenta ( ,) | Ensimmäisen operandin laskeminen | Toisen operandin laskenta |
Pikakirjoitusoperaattori ( ?:) | Ehdona toimivan operandin laskenta | 2. tai 3. operandin laskenta |
Kahden täydellisen lausekkeen välillä (ei sisäkkäisiä) | Yksi täydellinen ilmaisu | Seuraava täydellinen lauseke |
Valmis täydellinen kuvaus | ||
Juuri ennen paluuta kirjastotoiminnosta | ||
Jokaisen muotoiltuun I/O-määritteeseen liittyvän muunnoksen jälkeen | ||
Välittömästi ennen ja välittömästi jokaisen vertailutoiminnon kutsun jälkeen sekä vertailufunktion kutsun ja vertailufunktiolle välitetyille argumenteille suoritettujen liikkeiden välillä |
Täydelliset lausekkeet ovat [27] :
Seuraavassa esimerkissä muuttujaa muutetaan kolme kertaa sekvenssipisteiden välillä, jolloin tuloksena on määrittelemätön:
int i = 1 ; // Kuvaaja on ensimmäinen sekvenssipiste, koko lauseke on toinen i += ++ i + 1 ; // Täysi lauseke - kolmas sekvenssipiste printf ( "%d \n " , i ); // Voi tulostaa joko 4 tai 5Muita yksinkertaisia esimerkkejä määrittelemättömästä käyttäytymisestä, jota tulee välttää:
i = i ++ + 1 ; // määrittelemätön käyttäytyminen i = ++ i + 1 ; // myös määrittelemätön käyttäytyminen printf ( "%d, %d \n " , -- i , ++ i ); // määrittelemätön toiminta printf ( "%d, %d \n " , ++ i , ++ i ); // myös määrittelemätön käyttäytyminen printf ( "%d, %d \n " , i = 0 , i = 1 ); // määrittelemätön toiminta printf ( "%d, %d \n " , i = 0 , i = 0 ); // myös määrittelemätön käyttäytyminen a [ i ] = i ++ ; // määrittelemätön käyttäytyminen a [ i ++ ] = i ; // myös määrittelemätön käyttäytyminenOhjauskäskyt on suunniteltu suorittamaan toimintoja ja ohjaamaan ohjelman suorituskulkua. Useat peräkkäiset lauseet muodostavat lausesarjan .
Tyhjä lausuntoYksinkertaisin kielikonstrukti on tyhjä lauseke, jota kutsutaan tyhjäksi lauseeksi [29] :
;Tyhjä lause ei tee mitään, ja se voidaan sijoittaa mihin tahansa ohjelmaan. Käytetään yleisesti silmukoissa, joista puuttuu runko [30] .
OhjeetOhje on eräänlainen perustoiminto:
( ilmaisu );Tämän operaattorin tehtävänä on suorittaa operaattorin rungossa määritetty lauseke.
Useat peräkkäiset käskyt muodostavat käskysarjan .
OhjelohkoOhjeet voidaan ryhmitellä seuraavan muodon erityisiin lohkoihin:
{
( ohjesarja )},
Lausuntolohko, jota joskus kutsutaan myös yhdistetyksi lauseeksi, on rajattu vasemmalla aaltosuljetuksella ( {) alussa ja oikealla aaltosulkeella ( }) lopussa.
Funktioissa lausekelohko ilmaisee funktion rungon ja on osa funktion määritelmää. Yhdistelmälauseketta voidaan käyttää myös silmukka-, ehto- ja valintalausekkeissa.
Ehdolliset lauseetKielessä on kaksi ehdollista operaattoria, jotka toteuttavat ohjelman haarautumisen:
Operaattorin yksinkertaisin muotoif
if(( kunto ) )( operaattori ) ( seuraava lausunto )Operaattori iftoimii näin:
Erityisesti seuraava koodi, jos määritetty ehto täyttyy, ei suorita mitään toimintoa, koska itse asiassa suoritetaan tyhjä käsky:
if(( kunto )) ;Monimutkaisempi operaattorin muoto ifsisältää avainsanan else:
if(( kunto ) )( operaattori ) else( vaihtoehtoinen operaattori ) ( seuraava lausunto )Tässä, jos suluissa määritetty ehto ei täyty, avainsanan jälkeen määritetty käsky suoritetaan else.
Vaikka standardi sallii lausekkeiden määrittämisen yhdelle riville iftai elseyksittäiseksi riviksi, tätä pidetään huonona tyylinä ja heikentää koodin luettavuutta. Suosittelemme, että määrität aina lauselohkon käyttämällä aaltosulkeiden runko-osaa [31] .
Silmukan suorituslausekkeetSilmukka on koodinpätkä, joka sisältää
Näin ollen on olemassa kahdentyyppisiä syklejä:
Jälkiehdollinen silmukka takaa, että silmukan runko suoritetaan vähintään kerran.
C-kieli tarjoaa kaksi muunnelmaa silmukoista, joissa on ennakkoehto: whileja for.
while(kunto) [ loop body ] for( alustuslohkon ;ehtolause [ loop body ] ;,)Silmukkaa forkutsutaan myös parametriseksi, se vastaa seuraavaa lauseketta:
[ alustuslohko ] while(kunto) { [ loop body ] [ operaattori ] }Normaalitilanteessa alustuslohko sisältää muuttujan alkuarvon asettamisen, jota kutsutaan silmukkamuuttujaksi, ja käsky, joka suoritetaan välittömästi sen jälkeen, kun silmukan runko muuttaa käytetyn muuttujan arvoja, ehto sisältää käytetyn silmukkamuuttujan arvon vertailu johonkin ennalta määritettyyn arvoon, ja heti kun vertailu lopetetaan, silmukka keskeytyy ja silmukkakäskyä välittömästi seuraavan ohjelmakoodin suorittaminen alkaa.
Silmukalle do-whileehto määritetään silmukan rungon jälkeen:
do[ loop body ] while( kunto)Silmukan ehto on boolen lauseke. Implisiittisen tyypin valu mahdollistaa kuitenkin aritmeettisen lausekkeen käyttämisen silmukkaehtona. Tämän avulla voit järjestää niin kutsutun "äärettömän silmukan":
while(1);Sama voidaan tehdä operaattorin kanssa for:
for(;;);Käytännössä tällaisia äärettömiä silmukoita käytetään yleensä yhdessä break, gototai :n kanssa return, jotka katkaisevat silmukan eri tavoin.
Ehdollisen lauseen tavoin yksirivisen rungon käyttäminen ilman, että sitä suljetaan käskylohkossa, jossa on aaltosulkeet, katsotaan huonoksi tyyliksi, mikä heikentää koodin luettavuutta [31] .
Ehdottomat hyppyoperaattoritEhdottomien haaraoperaattorien avulla voit keskeyttää minkä tahansa laskutoimituslohkon suorittamisen ja siirtyä toiseen paikkaan ohjelmassa nykyisen funktion sisällä. Ehdottomia hyppyoperaattoreita käytetään yleensä ehdollisten operaattoreiden yhteydessä.
goto[ etiketti ],Tarra on jokin tunniste, joka siirtää ohjauksen operaattorille, joka on merkitty ohjelmassa määritetyllä tunnisteella:
[ etiketti ] :[ operaattori ]Jos määritettyä tunnistetta ei ole ohjelmassa tai jos samalla tunnisteella on useita lauseita, kääntäjä ilmoittaa virheestä.
Ohjauksen siirto on mahdollista vain sen toiminnon sisällä, jossa siirtymäoperaattoria käytetään, joten operaattoria käyttämällä gotoei voi siirtää ohjausta toiselle toiminnolle.
Muut hyppykäskyt liittyvät silmukoihin ja mahdollistavat silmukan rungon suorittamisen keskeyttämisen:
Käsky breakvoi myös keskeyttää lauseen toiminnan switch, joten switchsilmukassa olevan käskyn sisällä lauseke breakei voi keskeyttää silmukkaa. Silmukan rungossa määritetty se keskeyttää lähimmän sisäkkäisen silmukan työn.
Operaattoria continuevoidaan käyttää vain do, whileja -operaattoreiden sisällä for. Silmukoille whileja do-whileoperaattori continueaiheuttaa silmukan ehdon testin ja silmukan tapauksessa silmukan for 3. parametrissa määritellyn operaattorin suorittamisen ennen silmukan jatkamisen ehdon tarkistamista.
FunktiopalautuslauseOperaattori returnkeskeyttää sen toiminnon suorittamisen, jossa sitä käytetään. Jos funktion ei pitäisi palauttaa arvoa, käytetään kutsua ilman palautusarvoa:
return;Jos funktion on palautettava arvo, palautusarvo ilmoitetaan operaattorin jälkeen:
return[ arvo ];Jos funktion rungossa on return-käskyn jälkeen muita käskyjä, näitä käskyjä ei koskaan suoriteta, jolloin kääntäjä voi antaa varoituksen. Operaattorin jälkeen returnvoidaan kuitenkin ilmoittaa ohjeet toiminnon vaihtoehtoiseen lopettamiseen, esimerkiksi vahingossa, ja siirtyminen näihin operaattoreihin voidaan suorittaa käyttämällä operaattoria gotoehtojen mukaan .
Kun muuttujaa määritetään, sen tyyppi ja nimi määritetään, ja alkuarvo voidaan myös määrittää:
[kuvaus] [nimi];tai
[descriptor] [name] =[initializer] ;,missä
Jos muuttujalle ei ole annettu alkuarvoa, globaalin muuttujan tapauksessa sen arvo täytetään nolilla ja paikallisen muuttujan aloitusarvo on määrittelemätön.
Muuttujan kuvaajassa voit määrittää muuttujan globaaliksi, mutta vain tiedoston tai funktion laajuudeksi käyttämällä avainsanaa static. Jos muuttuja on ilmoitettu globaaliksi ilman avainsanaa static, siihen pääsee myös muista tiedostoista, joissa tämä muuttuja on ilmoitettava ilman alustusta, mutta avainsanalla extern. Tällaisten muuttujien osoitteet määritetään linkin aikana .
Funktio on itsenäinen ohjelmakoodin osa, jota voidaan käyttää uudelleen ohjelmassa. Funktiot voivat ottaa argumentteja ja palauttaa arvoja. Funktioilla voi olla myös sivuvaikutuksia niiden suorittamisen aikana: globaalien muuttujien muuttaminen, tiedostojen käsittely, vuorovaikutus käyttöjärjestelmän tai laitteiston kanssa [28] .
Määrittääksesi funktion C:ssä, sinun on määritettävä se:
On myös tarpeen tarjota funktion määritelmä, joka sisältää lohkon lauseita, jotka toteuttavat funktion käyttäytymisen.
Tietyn funktion ilmoittamatta jättäminen on virhe, jos funktiota käytetään määritelmän ulkopuolella, mikä johtaa toteutuksesta riippuen ilmoituksiin tai varoituksiin.
Funktiota kutsuaksesi riittää, että määrität sen nimen suluissa määritetyillä parametreilla. Tällöin pinoon sijoitetaan kutsupisteen osoite, funktioparametreista vastaavat muuttujat luodaan ja alustetaan ja ohjaus siirretään kutsutun funktion toteuttavalle koodille. Kun funktio on suoritettu, funktiokutsun aikana varattu muisti vapautetaan, paluu kutsupisteeseen ja jos funktiokutsu on osa jotakin lauseketta, funktion sisällä laskettu arvo välitetään paluupisteeseen.
Jos sulkuja ei ole määritetty funktion jälkeen, kääntäjä tulkitsee tämän saavansa funktion osoitteen. Toiminnon osoite voidaan syöttää osoittimeen ja kutsua sen jälkeen funktioksi osoittimella, jota käytetään aktiivisesti esimerkiksi liitännäisjärjestelmissä [32] .
Avainsanalla inlinevoit merkitä toimintoja, joiden kutsut haluat suorittaa mahdollisimman nopeasti. Kääntäjä voi korvata tällaisten funktioiden koodin suoraan niiden kutsun kohdalla [33] . Tämä toisaalta lisää suoritettavan koodin määrää, mutta toisaalta säästää sen suorittamiseen kuluvaa aikaa, koska aikaa vievää funktiokutsuoperaatiota ei käytetä. Tietokoneiden arkkitehtuurista johtuen inline-toiminnot voivat kuitenkin joko nopeuttaa tai hidastaa sovellusta kokonaisuutena. Kuitenkin monissa tapauksissa rivifunktiot ovat suositeltavin makrojen korvike [34] .
ToimintoilmoitusToimintomäärityksellä on seuraava muoto:
[descriptor] [name] ([list] );,missä
Toimintomäärityksen merkki on “ ;”-symboli, joten funktiomäärittely on käsky.
Yksinkertaisimmassa tapauksessa [declarator] sisältää viitteen tietyntyyppisestä palautusarvosta. Funktio, jonka ei pitäisi palauttaa arvoa, ilmoitetaan tyyppiseksi void.
Tarvittaessa kuvaaja voi sisältää muunnoksia, jotka on määritetty avainsanoilla:
Funktioparametrien luettelo määrittää funktion allekirjoituksen.
C ei salli useiden funktioiden ilmoittamista samalla nimellä, funktion ylikuormitusta ei tueta [36] .
FunktiomäärittelyFunktiomäärittelyllä on seuraava muoto:
[descriptor] [name] ([list] )[body]Missä [declarator], [name] ja [list] ovat samat kuin ilmoituksessa ja [body] on yhdistelmälause, joka edustaa funktion konkreettista toteutusta. Kääntäjä erottaa samannimisen funktion määritelmät niiden allekirjoituksen perusteella ja näin (allekirjoituksella) muodostetaan yhteys määritelmän ja vastaavan ilmoituksen välille.
Toiminnon runko näyttää tältä:
{ [lausuntosekvenssi] return([palautusarvo]); }Palautus funktiosta suoritetaan -operaattorilla , joka joko määrittää palautusarvon tai ei määritä sitä riippuen funktion palauttamasta tietotyypistä. Harvinaisissa tapauksissa funktio voidaan merkitä otsikkotiedoston makron avulla palautumatta jättäväksi , jolloin lauseketta ei vaadita. Esimerkiksi funktiot, jotka kutsuvat sisällään ehdoitta, voidaan merkitä tällä tavalla [33] . returnnoreturnstdnoreturn.hreturnabort()
ToimintokutsuToimintokutsun tarkoituksena on suorittaa seuraavat toiminnot:
Toteutuksesta riippuen kääntäjä joko varmistaa tiukasti, että varsinaisen parametrin tyyppi vastaa muodollisen parametrin tyyppiä, tai, jos mahdollista, suorittaa implisiittisen tyyppimuunnoksen, joka luonnollisesti johtaa sivuvaikutuksiin.
Jos funktiolle välitetään muuttuja, funktiota kutsuttaessa siitä luodaan kopio ( pinoon varataan muisti ja arvo kopioidaan). Esimerkiksi rakenteen välittäminen funktiolle aiheuttaa koko rakenteen kopioimisen. Jos osoitin rakenteeseen välitetään, vain osoittimen arvo kopioidaan. Matriisin välittäminen funktiolle aiheuttaa myös vain osoittimen kopioimisen sen ensimmäiseen elementtiin. Tässä tapauksessa voit nimenomaisesti osoittaa, että taulukon alun osoite otetaan syötteeksi funktiolle, ei osoitin yksittäiseen muuttujaan, sen sijaan, että ilmoittaisit osoittimen muuttujan nimen jälkeen, voit laittaa hakasulkeet, esimerkki:
void esimerkki_funktio ( int array []); // taulukko on osoitin int-tyypin taulukon ensimmäiseen elementtiinC sallii sisäkkäiset puhelut. Puheluiden sisäkkäisyydellä on ilmeinen rajoitus, joka liittyy ohjelmalle varatun pinon kokoon. Siksi C-toteutukset asettavat rajan sisäkkäisyyden syvyydelle.
Sisäkkäisen kutsun erikoistapaus on funktiokutsu kutsutun funktion rungossa. Tällaista kutsua kutsutaan rekursiiviseksi, ja sitä käytetään yhtenäisten laskelmien järjestämiseen. Sisäkkäisten puhelujen luonnollisen rajoituksen vuoksi rekursiivinen toteutus korvataan silmukoita käyttävällä toteutuksella.
Kokonaislukutietotyypit ovat kooltaan vähintään 8 - vähintään 32 bittiä. C99-standardi kasvattaa kokonaisluvun maksimikoon vähintään 64 bittiin. Kokonaislukutietotyyppejä käytetään kokonaislukujen tallentamiseen (tyyppiä charkäytetään myös ASCII-merkkien tallentamiseen). Kaikki alla olevien tietotyyppien aluekoot ovat minimiä ja voivat olla suurempia tietyllä alustalla [37] .
Tyyppien vähimmäiskokojen seurauksena standardi edellyttää, että integroitujen tyyppien koot täyttävät ehdon:
1= ≤ ≤ ≤ ≤ . sizeof(char)sizeof(short)sizeof(int)sizeof(long)sizeof(long long)
Siten joidenkin tyyppien koot tavumäärän suhteen voivat täsmää, jos ehto bittien vähimmäismäärälle täyttyy. Jopa charja longvoi olla samankokoisia, jos yksi tavu kestää 32 bittiä tai enemmän, mutta tällaiset alustat ovat hyvin harvinaisia tai niitä ei ole olemassa. Standardi takaa, että tyyppi on char aina 1 tavu. Tavun koon bitteinä määrittää CHAR_BITotsikkotiedoston vakio limits.h, joka on 8 bittiä POSIX -yhteensopivissa järjestelmissä [38] .
Standardin mukainen kokonaislukutyyppien minimiarvoalue määritellään etumerkittyille tyypeille välillä - ja etumerkittömille tyypeille alkaen - , missä N on tyypin bittisyvyys. Kääntäjätoteutukset voivat laajentaa tätä aluetta harkintansa mukaan. Käytännössä aluetta - - käytetään yleisemmin allekirjoitetuille tyypeille . Kunkin tyypin minimi- ja maksimiarvot on määritetty tiedostossa makromäärityksinä. -(2N-1-1)2N-1-102N-2N-12N-1-1limits.h
Erityistä huomiota tulee kiinnittää tyyppiin char. Muodollisesti tämä on erillinen tyyppi, mutta itse asiassa charvastaa joko signed char, tai unsigned charkääntäjästä riippuen [39] .
Välttääkseen sekaannuksia tyyppikokojen välillä C99-standardi esitteli uusia tietotyyppejä, jotka on kuvattu stdint.h. Niiden joukossa on tyyppejä, kuten: , , , jossa = 8, 16, 32 tai 64. Etuliite tarkoittaa minimityyppiä, johon bittejä mahtuu , etuliite tarkoittaa vähintään 16 bitin tyyppiä, joka on nopein tällä alustalla. Tyypit ilman etuliitteitä tarkoittavat tyyppejä, joiden bittikoko on kiinteä . intN_tint_leastN_tint_fastN_tNleast-Nfast-N
Tyypit, joissa on etuliitteet least-ja niitä voidaan pitää tyyppien , , fast-korvaavina , sillä ainoalla erolla, että edelliset antavat ohjelmoijalle mahdollisuuden valita nopeuden ja koon välillä. intshortlong
Tietotyyppi | Koko | Minimiarvoalue | Vakio |
---|---|---|---|
signed char | vähintään 8 bittiä | arvosta −127 [40] (= -(2 7 −1)) arvoon 127 | C90 [j] |
int_least8_t | C99 | ||
int_fast8_t | |||
unsigned char | vähintään 8 bittiä | 0 - 255 (=2 8 -1) | C90 [j] |
uint_least8_t | C99 | ||
uint_fast8_t | |||
char | vähintään 8 bittiä | −127 - 127 tai 0 - 255 kääntäjästä riippuen | C90 [j] |
short int | vähintään 16 bittiä | -32,767 (= -(2 15 -1)) arvoon 32,767 | C90 [j] |
int | |||
int_least16_t | C99 | ||
int_fast16_t | |||
unsigned short int | vähintään 16 bittiä | 0 - 65,535 (= 2 16 -1) | C90 [j] |
unsigned int | |||
uint_least16_t | C99 | ||
uint_fast16_t | |||
long int | vähintään 32 bittiä | −2 147 483 647 - 2 147 483 647 | C90 [j] |
int_least32_t | C99 | ||
int_fast32_t | |||
unsigned long int | vähintään 32 bittiä | 0 - 4 294 967 295 (= 2 32 -1) | C90 [j] |
uint_least32_t | C99 | ||
uint_fast32_t | |||
long long int | vähintään 64 bittiä | -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 | vähintään 64 bittiä | 0 - 18 446 744 073 709 551 615 (= 264 −1 ) | |
uint_least64_t | |||
uint_fast64_t | |||
int8_t | 8-bittinen | -127-127 | |
uint8_t | 8-bittinen | 0 - 255 (=2 8 -1) | |
int16_t | 16-bittinen | -32.767 - 32.767 | |
uint16_t | 16-bittinen | 0 - 65,535 (= 2 16 -1) | |
int32_t | 32 bittiä | −2 147 483 647 - 2 147 483 647 | |
uint32_t | 32 bittiä | 0 - 4 294 967 295 (= 2 32 -1) | |
int64_t | 64 bittiä | -9 223 372 036 854 775 807 - 9 223 372 036 854 775 807 | |
uint64_t | 64 bittiä | 0 - 18 446 744 073 709 551 615 (= 264 −1 ) | |
Taulukko näyttää arvojen vähimmäisalueen kielistandardin mukaan. C-kääntäjät voivat laajentaa arvoaluetta. |
Lisäksi C99-standardin jälkeen tyypit intmax_tja on lisätty uintmax_t, jotka vastaavat suurinta allekirjoitettua ja allekirjoittamatonta tyyppiä. Nämä tyypit ovat käteviä, kun niitä käytetään makroissa väli- tai väliaikaisten arvojen tallentamiseen kokonaislukuargumenttien aikana, koska niiden avulla voit sovittaa minkä tahansa tyyppisiä arvoja. Näitä tyyppejä käytetään esimerkiksi Check unit testing -kirjaston C :n kokonaislukuvertailumakroissa [41] .
C:ssä on useita muita kokonaislukutyyppejä osoittimen tietotyypin turvalliseen käsittelyyn: intptr_t, uintptr_tja ptrdiff_t. intptr_tC99-standardin ja - tyypit uintptr_ton suunniteltu tallentamaan etumerkittyjä ja etumerkittömiä arvoja, jotka mahtuvat osoittimen kokoon. Näitä tyyppejä käytetään usein mielivaltaisen kokonaisluvun tallentamiseen osoittimeen, esimerkiksi keinona päästä eroon tarpeettomasta muistin varauksesta palautefunktioita rekisteröitäessä [42] tai käytettäessä kolmannen osapuolen linkitettyjä listoja, assosiatiivisia taulukoita ja muita rakenteita, joissa tiedot tallennetaan osoittimella. ptrdiff_tOtsikkotiedoston tyyppi stddef.hon suunniteltu tallentamaan turvallisesti kahden osoittimen erot.
size_tKoon tallentamiseksi tarjotaan otsikkotiedostosta allekirjoittamaton tyyppi stddef.h. Tämä tyyppi pystyy pitämään suurimman mahdollisen määrän osoittimessa käytettävissä olevia tavuja, ja sitä käytetään tyypillisesti tallentamaan koko tavuina. Tämän tyypin arvon palauttaa operaattori sizeof[43] .
Kokonaislukutyyppinen valuKokonaislukutyypin muunnokset voivat tapahtua joko eksplisiittisesti, käyttämällä cast-operaattoria tai implisiittisesti. Tyyppien arvot, jotka ovat pienempiä kuin int, kun osallistuvat mihin tahansa toimintoon tai siirretään funktiokutsuun, lähetetään automaattisesti tyyppiin int, ja jos muuntaminen on mahdotonta, tyyppiin unsigned int. Usein tällaiset implisiittiset heitot ovat välttämättömiä, jotta laskennan tulos olisi oikea, mutta joskus ne johtavat intuitiivisesti käsittämättömiin virheisiin laskelmissa. Jos operaatioon liittyy esimerkiksi tyypin intja lukuja unsigned intja etumerkillinen arvo on negatiivinen, negatiivisen luvun muuntaminen etumerkittömäksi tyypiksi johtaa ylivuotoon ja erittäin suureen positiiviseen arvoon, mikä voi johtaa vertailutoimintojen virheelliseen tulokseen. [44] .
Allekirjoitetut ja allekirjoittamattomat tyypit ovat pienempiä kuinint | Allekirjoitettu on vähemmän kuin allekirjoittamaton, ja allekirjoittamaton ei ole pienempiint |
---|---|
#include <stdio.h> etumerkillinen merkki x = -1 ; etumerkitön char y = 0 ; if ( x > y ) { // ehto on false printf ( "Viestiä ei näytetä. \n " ); } if ( x == UCHAR_MAX ) { // ehto on false printf ( "Viestiä ei näytetä. \n " ); } | #include <stdio.h> etumerkillinen merkki x = -1 ; unsigned int y = 0 ; if ( x > y ) { // ehto on tosi printf ( "Ylivuoto muuttujassa x. \n " ); } if (( x == UINT_MAX ) && ( x == ULONG_MAX )) { // ehto on aina tosi printf ( "Ylivuoto muuttujassa x. \n " ); } |
Tässä esimerkissä molemmat tyypit, allekirjoitetut ja allekirjoittamattomat, välitetään allekirjoitettuun muotoon int, koska sen avulla molempien tyyppien alueet mahtuvat. Siksi ehdollisen operaattorin vertailu on oikea. | Etumerkillinen tyyppi välitetään etumerkittömäksi, koska etumerkitön tyyppi on suurempi tai yhtä suuri kuin int, mutta ylivuoto tapahtuu, koska on mahdotonta esittää negatiivista arvoa etumerkittömässä tyypissä. |
Myös automaattinen tyyppien valu toimii, jos lausekkeessa käytetään kahta tai useampaa erilaista kokonaislukutyyppiä. Standardi määrittelee sääntöjoukon, jonka mukaan valitaan sellainen tyyppimuunnos, joka voi antaa oikean laskennan tuloksen. Eri tyypeille annetaan eri arvot muunnoksen sisällä, ja itse arvot perustuvat tyypin kokoon. Kun lausekkeessa on mukana eri tyyppejä, nämä arvot valitaan yleensä heittämään korkeamman tason tyyppiin [44] .
ReaaliluvutC:n liukulukuja edustavat kolme perustyyppiä: float, doubleja long double.
Reaaliluvuilla on hyvin erilainen esitys kuin kokonaisluvuilla. Erityyppisten reaalilukujen vakiot, jotka on kirjoitettu desimaalimuodossa, eivät välttämättä ole keskenään samanarvoisia. Esimerkiksi ehto 0.1 == 0.1fon epätosi tyypin tarkkuuden menettämisen vuoksi float, kun taas ehto 0.5 == 0.5fon tosi, koska nämä luvut ovat äärellisiä binääriesityksenä. Valintaehto (float) 0.1 == 0.1fon kuitenkin myös totta, koska valettaessa vähemmän tarkkaan tyyppiin menetetään bitit, jotka tekevät kahdesta vakiosta erilaisia.
Aritmeettiset operaatiot reaaliluvuilla ovat myös epätarkkoja ja niissä on usein kelluva virhe [45] . Suurin virhe tapahtuu käytettäessä arvoja, jotka ovat lähellä tietyn tyypin mahdollista minimiä. Virhe voi myös osoittautua suureksi, kun lasketaan samanaikaisesti hyvin pienistä (≪ 1) ja erittäin suurista luvuista (≫ 1). Joissakin tapauksissa virhettä voidaan pienentää muuttamalla algoritmeja ja laskentamenetelmiä. Esimerkiksi, kun useiden yhteenlasku korvataan kertolaskulla, virhe voi pienentyä niin monta kertaa kuin alunperin summausoperaatioita oli.
Myös otsikkotiedostossa math.hon kaksi lisätyyppiä float_tja double_t, jotka vastaavat vähintään tyyppejä floatja doublevastaavasti, mutta voivat poiketa niistä. Tyypit float_tja double_tlisätään C99-standardiin ja niiden vastaavuus perustyyppeihin määräytyy makron arvon mukaan FLT_EVAL_METHOD.
Tietotyyppi | Koko | Vakio |
---|---|---|
float | 32 bittiä | IEC 60559 ( IEEE 754 ), C-standardin laajennus F [46] [k] , yksi tarkkuusnumero |
double | 64 bittiä | IEC 60559 (IEEE 754), C-standardin laajennus F [46] [k] , kaksinkertainen tarkkuusnumero |
long double | vähintään 64 bittiä | täytäntöönpanosta riippuvainen |
float_t(C99) | vähintään 32 bittiä | riippuu pohjatyypistä |
double_t(C99) | vähintään 64 bittiä | riippuu pohjatyypistä |
FLT_EVAL_METHOD | float_t | double_t |
---|---|---|
yksi | float | double |
2 | double | double |
3 | long double | long double |
Vaikka C:n merkkijonoille ei sinänsä ole erityistä tyyppiä, nollapääteisiä merkkijonoja käytetään kielessä voimakkaasti. ASCII -merkkijonot ilmoitetaan tyypin taulukona char, jonka viimeisen elementin on oltava merkkikoodi 0( '\0'). On tapana tallentaa UTF-8- merkkijonot samassa muodossa . Kuitenkin kaikki funktiot, jotka toimivat ASCII-merkkijonojen kanssa, pitävät jokaista merkkiä tavuna, mikä rajoittaa standardifunktioiden käyttöä tätä koodausta käytettäessä.
Huolimatta nollapääteisten merkkijonojen idean laajasta käytöstä ja niiden käyttömukavuudesta joissakin algoritmeissa, niillä on useita vakavia haittoja.
Nykyaikaisissa olosuhteissa, kun koodin suorituskyky asetetaan etusijalle muistinkulutukseen nähden, voi olla tehokkaampaa ja helpompaa käyttää rakenteita, jotka sisältävät sekä itse merkkijonon että sen koon [48] , esimerkiksi:
struct string_t { char * str ; // osoitin merkkijonoon koko_t str_size ; // merkkijonon koko }; typedef rakenne merkkijono_t merkkijono_t ; // vaihtoehtoinen nimi koodin yksinkertaistamiseksiVaihtoehtoinen vähän muistia sisältävä merkkijonokoon tallennustapa olisi liittää merkkijonon eteen sen koko muuttuvan pituisen koon muodossa.. Samanlaista lähestymistapaa käytetään protokollapuskureissa , kuitenkin vain tiedonsiirtovaiheessa, mutta ei niiden tallentamisessa.
Merkkijono literaalitC:n merkkijonoliteraalit ovat luonnostaan vakioita [10] . Ilmoitaessa ne laitetaan lainausmerkkeihin ja 0kääntäjä lisää automaattisesti päätteen. On kaksi tapaa määrittää merkkijonoliteraali: osoittimen ja arvon perusteella. Osoittimella määritettäessä char *tyyppimuuttujaan syötetään osoitin muuttumattomaan merkkijonoon, eli muodostetaan vakiomerkkijono. Jos syötät taulukkoon merkkijonoliteraalin, merkkijono kopioidaan pinoalueelle.
#include <stdio.h> #include <string.h> int main ( tyhjä ) { const char * s1 = "Vakiomerkkijono" ; char s2 [] = "Merkkijono, jota voidaan muuttaa" ; memcpy ( s2 , " c " , strlen ( " c " )); // muuta ensimmäinen kirjain pieneksi laittaa ( s2 ); // rivin teksti tulee näkyviin memcpy (( char * ) s1 , "to" , strlen ( "to" )); // segmentointivirhe laittaa ( s1 ); // riviä ei suoriteta }Koska merkkijonot ovat tavallisia merkkijonoja, alustajia voidaan käyttää literaalien sijasta, kunhan jokainen merkki mahtuu 1 tavuun:
char s [] = { 'minä' , 'n' , 'i' , 't' , 'i' , 'a' , 'l' , 'i' , 'z' , 'e' , 'r' , '\0' };Käytännössä tämä lähestymistapa on kuitenkin järkevä vain erittäin harvoissa tapauksissa, kun ASCII-merkkijonoon ei vaadita lopettavaa nollaa.
Leveät viivatAlusta | Koodaus |
---|---|
GNU/Linux | USC-4 [49] |
Mac käyttöjärjestelmä | |
Windows | USC-2 [50] |
AIX | |
FreeBSD | Riippuu alueesta
ei dokumentoitu [50] |
Solaris |
Vaihtoehto tavallisille merkkijonoille ovat leveät merkkijonot, joissa jokainen merkki on tallennettu erityiseen tyyppiin wchar_t. Standardin antaman tyypin pitäisi pystyä sisältämään itsessään kaikki suurimman olemassa olevan paikan merkit . Leveiden merkkijonojen työskentelyn toiminnot kuvataan otsikkotiedostossa wchar.hja leveiden merkkien kanssa työskentelyn toiminnot otsikkotiedostossa wctype.h.
Kun määritetään merkkijonoliteraaleja leveille merkkijonoille, käytetään muuntajaa L:
const wchar_t * wide_str = L "Leveä merkkijono" ;Muotoiltu tulos käyttää määritettä %ls, mutta koon määrittäjä, jos se on annettu, määritellään tavuissa, ei merkeissä [51] .
Tyyppi wchar_tsuunniteltiin niin, että siihen mahtui mikä tahansa merkki ja leveät merkkijonot - minkä tahansa kielen merkkijonojen tallentamiseen, mutta sen seurauksena API osoittautui hankalaksi ja toteutukset olivat alustariippuvaisia. Joten Windows -alustalla tyypin kooksi valittiin 16 bittiä wchar_tja myöhemmin UTF-32-standardi ilmestyi, joten wchar_tWindows-alustalla oleva tyyppi ei enää mahdu kaikkia UTF-32-koodauksen merkkejä, jonka seurauksena tämän tyypin merkitys menetetään [50] . Samaan aikaan Linux [49] ja macOS-alustoilla tämä tyyppi vie 32 bittiä, joten tyyppi ei sovellu useiden alustojen tehtävien toteuttamiseen.wchar_t
Monitavuiset merkkijonotOn olemassa monia erilaisia koodauksia, joissa yksi merkki voidaan ohjelmoida eri määrällä tavuja. Tällaisia koodauksia kutsutaan monitavuiksi. UTF-8 koskee myös niitä . C:llä on joukko toimintoja, joilla muunnetaan merkkijonoja nykyisen kielen monitavuisista leveiksi ja päinvastoin. Monitavuisten merkkien kanssa työskentelyyn tarkoitetuilla funktioilla on etu- tai pääte mbja ne kuvataan otsikkotiedostossa stdlib.h. Jotta C-ohjelmien monitavuisia merkkijonoja voidaan tukea, tällaisia merkkijonoja on tuettava nykyisellä kielitasolla . Jos haluat määrittää koodauksen nimenomaisesti, voit muuttaa nykyistä aluetta käyttämällä funktiota setlocale(). locale.hKäytettävän vakiokirjaston on kuitenkin tuettava maa-asetuksen koodauksen määrittämistä. Esimerkiksi Glibc -standardikirjasto tukee täysin UTF-8-koodausta ja pystyy muuttamaan tekstin moniin muihin koodauksiin [52] .
C11-standardista alkaen kieli tukee myös 16-bittisiä ja 32-bittisiä monitavuisia merkkijonoja sopivilla merkkityypeillä char16_tja char32_totsikkotiedostosta uchar.hsekä UTF-8-merkkijonoliteraalien ilmoittamista käyttämällä u8. 16-bittisiä ja 32-bittisiä merkkijonoja voidaan käyttää UTF-16- ja UTF-32-koodausten tallentamiseen, jos uchar.hmakromääritykset __STDC_UTF_16__ja ne on määritetty otsikkotiedostossa __STDC_UTF_32__. Merkkijonoliteraalien määrittämiseen näissä muodoissa käytetään muuntajia: u16-bittisille merkkijonoille ja U32-bittisille merkkijonoille. Esimerkkejä merkkijonoliteraalien ilmoittamisesta monitavuisille merkkijonoille:
const char * s8 = u8 "UTF-8 monitavuinen merkkijono" ; const char16_t * s16 = u "16-bittinen monitavuinen merkkijono" ; const char32_t * s32 = U "32-bittinen monitavuinen merkkijono" ;Huomaa, että toiminto c16rtomb()16-bittisen merkkijonon muuntamiseksi monitavuiseksi merkkijonoksi ei toimi tarkoitetulla tavalla, ja C11-standardissa havaittiin, ettei se pysty kääntämään UTF-16:sta UTF-8:aan [53] . Tämän toiminnon korjaaminen voi riippua kääntäjän erityisestä toteutuksesta.
Enumit ovat joukko nimettyjä kokonaislukuvakioita, ja niitä merkitään avainsanalla enum. Jos vakio ei liity numeroon, se asetetaan automaattisesti joko 0luettelon ensimmäiselle vakiolle tai numerolle, joka on yhtä suurempi kuin edellisessä vakiossa määritetty. Tässä tapauksessa itse luettelointitietotyyppi voi itse asiassa vastata mitä tahansa etumerkittyä tai etumerkitöntä primitiivityyppiä, jonka alueelle kaikki luettelointiarvot sopivat; Kääntäjä päättää, mitä tyyppiä käyttää. Vakioiden eksplisiittisten arvojen on kuitenkin oltava lausekkeita, kuten int[18] .
Luettelotyyppi voi olla myös anonyymi, jos luettelon nimeä ei ole määritetty. Kahdessa eri enumissa määritetyt vakiot ovat kahta eri tietotyyppiä riippumatta siitä, ovatko enumit nimettyjä vai anonyymejä.
Käytännössä luetteloita käytetään usein osoittamaan äärellisten automaattien tiloja , asettamaan vaihtoehtoja toimintatiloihin tai parametriarvoihin [54] , luomaan kokonaislukuvakioita ja myös luettelemaan yksilöllisiä objekteja tai ominaisuuksia [55] .
RakenteetRakenteet ovat eri tietotyyppien muuttujien yhdistelmä samalla muistialueella; merkitty avainsanalla struct. Rakenteen sisällä olevia muuttujia kutsutaan rakenteen kentiksi. Osoiteavaruuden kannalta kentät seuraavat aina toisiaan samassa järjestyksessä, jossa ne on määritelty, mutta kääntäjät voivat kohdistaa kenttäosoitteet optimoidakseen tietyn arkkitehtuurin mukaan. Siten itse asiassa kenttä voi olla suurempi kuin ohjelmassa on määritetty.
Jokaisella kentällä on tietty siirtymä suhteessa rakenteen osoitteeseen ja kokoon. Poikkeama saadaan käyttämällä makroa offsetof()otsikkotiedostosta stddef.h. Tässä tapauksessa siirtymä riippuu edellisten kenttien kohdistamisesta ja koosta. Kentän koko määräytyy yleensä rakenteen kohdistuksen mukaan: jos kentän tietotyypin kohdistuskoko on pienempi kuin rakenteen tasausarvo, kentän koon määrää rakenteen kohdistus. Tietotyyppikohdistus saadaan käyttämällä makroa alignof()[f] otsikkotiedostosta stdalign.h. Itse rakenteen koko on sen kaikkien kenttien kokonaiskoko, mukaan lukien kohdistus. Samaan aikaan jotkin kääntäjät tarjoavat erikoisattribuutteja, joiden avulla voit pakata rakenteita poistamalla niistä tasaukset [56] .
Rakennekentät voidaan nimenomaisesti asettaa kooltaan kaksoispisteellä erotettuina bitteinä kentän määrittelyn ja bittien lukumäärän jälkeen, mikä rajoittaa niiden mahdollisten arvojen vaihteluväliä kentän tyypistä riippumatta. Tätä lähestymistapaa voidaan käyttää vaihtoehtona lipuille ja bitmaskeille niiden käyttämiseksi. Bittimäärän määrittäminen ei kuitenkaan peruuta muistissa olevien rakenteiden kenttien mahdollista kohdistusta. Bittikenttien kanssa työskentelemisessä on useita rajoituksia: niihin on mahdotonta soveltaa operaattoria sizeoftai makroa alignof(), niihin on mahdotonta saada osoitinta.
YhdistyksetLiitoksia tarvitaan, kun halutaan viitata samaan muuttujaan eri tietotyypeinä; merkitty avainsanalla union. Liiton sisällä voidaan ilmoittaa mielivaltainen määrä leikkaavia kenttiä, jotka itse asiassa tarjoavat pääsyn samalle muistialueelle eri tietotyypeinä. Liiton koon valitsee kääntäjä liiton suurimman kentän koon perusteella. On syytä muistaa, että yhden liiton kentän muuttaminen johtaa muutokseen kaikissa muissa kentissä, mutta vain muuttuneen kentän arvo on taatusti oikea.
Liitot voivat toimia kätevämpänä vaihtoehtona osoittimen heittämiselle mielivaltaiseen tyyppiin. Esimerkiksi rakenteeseen sijoitetun liitoksen avulla voit luoda objekteja dynaamisesti muuttuvilla tietotyypeillä:
Rakennekoodi tietotyypin muuttamiseen lennossa #include <stddef.h> enum arvo_tyyppi_t { VALUE_TYPE_LONG , // kokonaisluku VALUE_TYPE_DOUBLE , // todellinen luku VALUE_TYPE_STRING , // merkkijono VALUE_TYPE_BINARY , // mielivaltaiset tiedot }; struct binary_t { mitätön * tiedot ; // osoitin dataan size_t data_size ; // tiedon koko }; struct string_t { char * str ; // osoitin merkkijonoon koko_t str_size ; // merkkijonon koko }; liiton arvo_sisältö_t { long as_long ; // arvo kokonaislukuna double as_double ; // arvo reaalilukuna struct string_t as_string ; // arvo merkkijonona struct binary_t as_binary ; // arvo mielivaltaisena datana }; rakenne arvo_t { enum arvo_tyyppi_t -tyyppi ; // arvon tyyppi liiton arvo_sisältö_t sisältö ; // arvosisältö }; TaulukotC:n taulukot ovat primitiivisiä ja ovat vain syntaktista abstraktiota osoittimen aritmetiikkaa vastaan . Joukko itsessään on osoitin muistialueelle, joten kaikki tiedot taulukon dimensiosta ja sen rajoista ovat käytettävissä vain käännöshetkellä tyyppimäärityksen mukaisesti. Taulukot voivat olla joko yksiulotteisia tai moniulotteisia, mutta taulukkoelementtiin pääsy edellyttää yksinkertaisesti siirtymän laskemista suhteessa taulukon alun osoitteeseen. Koska taulukot perustuvat osoitearitmetiikkaan, niiden kanssa on mahdollista työskennellä ilman indeksejä [57] . Joten esimerkiksi seuraavat kaksi esimerkkiä 10 numeron lukemisesta syöttövirrasta ovat identtisiä keskenään:
Indeksien kautta tehtävän työn vertailu osoitearitmetiikkaan perustuvaan työhönEsimerkkikoodi hakemistojen käsittelyyn | Esimerkkikoodi osoitearitmetiikkaa varten |
---|---|
#include <stdio.h> int a [ 10 ] = { 0 }; // Nolla alustus unsigned int count = sizeof ( a ) / sizeof ( a [ 0 ]); for ( int i = 0 ; i < count ; ++ i ) { int * ptr = &a [ i ]; // Osoitin nykyiseen taulukon elementtiin int n = scanf ( "%8d" , ptr ); if ( n != 1 ) { perror ( "arvon lukeminen epäonnistui" ); // Virhekatkon käsittely ; } } | #include <stdio.h> int a [ 10 ] = { 0 }; // Nolla alustus unsigned int count = sizeof ( a ) / sizeof ( a [ 0 ]); int * a_end = a + count ; // Osoitin elementille, joka seuraa viimeistä for ( int * ptr = a ; ptr != a_end ; ++ ptr ) { int n = scanf ( "%8d" , ptr ); if ( n != 1 ) { perror ( "arvon lukeminen epäonnistui" ); // Virhekatkon käsittely ; } } |
Tunnetun kokoisten taulukoiden pituus lasketaan käännöshetkellä. C99-standardi esitteli mahdollisuuden ilmoittaa vaihtelevan pituisia taulukoita, joiden pituus voidaan asettaa ajon aikana. Tällaisille taulukoille on varattu muistia pinoalueelta, joten niitä on käytettävä varoen, jos niiden koko voidaan asettaa ohjelman ulkopuolelta. Toisin kuin dynaaminen muistin varaus, pinoalueen sallitun koon ylittäminen voi johtaa arvaamattomiin seurauksiin, ja negatiivinen taulukon pituus on määrittelemätön toiminta . Alkaen C11 :stä muuttuvapituiset taulukot ovat valinnaisia kääntäjille, ja tuen puute määräytyy makron läsnäolosta __STDC_NO_VLA__[58] .
Paikallisiksi tai globaaleiksi muuttujiksi ilmoitetut kiinteän kokoiset taulukot voidaan alustaa antamalla niille alkuarvo aaltosulkeilla ja listaamalla taulukon elementit pilkuilla erotettuina. Globaalit taulukon alustajat voivat käyttää vain lausekkeita, jotka arvioidaan käännöshetkellä [59] . Tällaisissa lausekkeissa käytetyt muuttujat on ilmoitettava vakioina modifierilla const. Paikallisille taulukoille alustusohjelmat voivat sisältää lausekkeita, joissa on funktiokutsuja ja muita muuttujia, mukaan lukien osoitin itse ilmoitettuun taulukkoon.
C99-standardista lähtien rakenteiden viimeisenä elementtinä on sallittu ilmoittaa mielivaltaisen pituinen taulukko, jota käytetään laajasti käytännössä ja jota eri kääntäjät tukevat. Tällaisen taulukon koko riippuu rakenteelle varatun muistin määrästä. Tässä tapauksessa et voi ilmoittaa tällaisten rakenteiden joukkoa etkä voi sijoittaa niitä muihin rakenteisiin. Tällaista rakennetta koskevissa operaatioissa mielivaltaisen pituinen matriisi jätetään yleensä huomiotta, myös rakenteen kokoa laskettaessa, ja taulukon yli meneminen johtaa määrittelemättömään käyttäytymiseen [60] .
C-kieli ei tarjoa minkäänlaista ohjausta taulukon ulkopuolisiin rajoihin, joten ohjelmoijan on itse valvottava taulukoiden käyttöä. Virheet taulukon käsittelyssä eivät aina vaikuta suoraan ohjelman suorittamiseen, mutta voivat johtaa segmentointivirheisiin ja haavoittuvuuksiin .
Kirjoita synonyymitC-kielen avulla voit luoda omia tyyppinimiä typedef. Vaihtoehtoisia nimiä voidaan antaa sekä järjestelmätyypeille että käyttäjän määrittämille. Tällaiset nimet ilmoitetaan globaalissa nimiavaruudessa, eivätkä ne ole ristiriidassa rakenteen, numeroinnin ja liittotyyppien nimien kanssa.
Vaihtoehtoisia nimiä voidaan käyttää sekä yksinkertaistamaan koodia että luomaan abstraktiotasoja. Esimerkiksi joitain järjestelmätyyppejä voidaan lyhentää koodin luettavuuden lisäämiseksi tai yhtenäistämiseksi käyttäjäkoodissa:
#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 ;Esimerkki abstraktiosta ovat tyyppien nimet käyttöjärjestelmien otsikkotiedostoissa. Esimerkiksi POSIX -standardi määrittelee tyypin pid_tnumeerisen prosessitunnuksen tallentamista varten. Itse asiassa tämä tyyppi on vaihtoehtoinen nimi jollekin primitiivityypille, esimerkiksi:
typedef int __kernel_pid_t ; typedef __kernel_pid_t __pid_t typedef __pid_t pid_t ;Koska tyypit, joilla on vaihtoehtoiset nimet, ovat vain synonyymejä alkuperäisille tyypeille, niiden välinen täydellinen yhteensopivuus ja vaihdettavuus säilyy.
Esiprosessori toimii ennen kääntämistä ja muuntaa ohjelmatiedoston tekstin siinä havaittujen tai esiprosessorille välitettyjen ohjeiden mukaan . Teknisesti esiprosessori voidaan toteuttaa eri tavoin, mutta on loogisesti kätevää ajatella sitä erillisenä moduulina, joka käsittelee jokaisen käännettäväksi tarkoitetun tiedoston ja muodostaa tekstin, joka tulee kääntäjän syötteeseen. Esiprosessori etsii tekstistä rivejä, jotka alkavat merkillä #, jota seuraa esikäsittelijän käskyt. Kaikki mikä ei kuulu esiprosessoridirektiiveihin ja jota ei ole jätetty kääntämisen ulkopuolelle ohjeiden mukaan, välitetään kääntäjän syötteeseen muuttumattomana.
Esiprosessorin ominaisuuksia ovat:
On tärkeää ymmärtää, että esiprosessori tarjoaa vain tekstin korvaamisen, ottamatta huomioon kielen syntaksia ja semantiikkaa. Joten esimerkiksi makromäärityksiä #definevoi esiintyä funktioiden tai tyyppimääritelmien sisällä, ja ehdolliset käännösdirektiivit voivat johtaa koodin minkä tahansa osan poissulkemiseen ohjelman käännetystä tekstistä, kielen kielioppista riippumatta. Parametrisen makron kutsuminen eroaa myös funktion kutsumisesta, koska pilkuilla eroteltujen argumenttien semantiikkaa ei jäsennetä. Joten esimerkiksi taulukon alustusta on mahdotonta siirtää parametrisen makron argumenteille, koska sen elementit erotetaan myös pilkulla:
#define array_of(type, array) (((tyyppi) []) (taulukko)) int * a ; a = matriisi_of ( int , { 1 , 2 , 3 }); // käännösvirhe: // "array_of" -makro läpäisi 4 argumenttia, mutta se kestää vain 2Makromäärityksiä käytetään usein varmistamaan yhteensopivuus kirjastojen eri versioiden kanssa, jotka ovat muuttaneet sovellusliittymiä , mukaan lukien tietyt koodiosat kirjaston versiosta riippuen. Näitä tarkoituksia varten kirjastot tarjoavat usein makromääritelmiä, jotka kuvaavat niiden versiota [61] , ja joskus makroja parametreillä, joiden avulla voidaan verrata nykyistä versiota esiprosessorissa määritettyyn versioon [62] . Makromäärityksiä käytetään myös ohjelman yksittäisten osien ehdolliseen kokoamiseen , esimerkiksi joidenkin lisätoimintojen tukemiseksi.
Parametreja sisältäviä makromäärityksiä käytetään laajalti C - ohjelmissa luomaan analogeja geneerisille funktioille . Aikaisemmin niitä käytettiin myös inline-toimintojen toteuttamiseen, mutta C99-standardin jälkeen tämä tarve on eliminoitu inline-toimintojen lisäyksen vuoksi. Kuitenkin, koska makromääritykset parametreilla eivät ole toimintoja, vaan niitä kutsutaan samalla tavalla, voi ilmetä odottamattomia ongelmia ohjelmointivirheen vuoksi, mukaan lukien vain osan makromääritelmän koodin käsittelystä [63] ja virheellisistä prioriteeteista. toimintojen suorittaminen [64] . Esimerkki virheellisestä koodista on neliöintimakro:
#include <stdio.h> int main ( tyhjä ) { #define SQR(x) x * x printf ( "%d" , SQR ( 5 )); // kaikki on oikein, 5*5=25 printf ( "%d" , SQR ( 5 + 0 )); // oletetaan olevan 25, mutta tulostaa 5 (5+0*5+0) printf ( "%d" , SQR ( 4/3 ) ) ; // kaikki on oikein, 1 (koska 4/3=1, 1*4=4, 4/3=1) printf ( "%d" , SQR ( 5/2 ) ) ; // sen pitäisi olla 4 (2*2), mutta tulostaa 5 (5/2*5/2) paluu 0 ; }Yllä olevassa esimerkissä virhe on, että makroargumentin sisältö korvataan tekstiin sellaisenaan, ottamatta huomioon operaatioiden ensisijaisuutta. Tällaisissa tapauksissa sinun on käytettävä inline-funktioita tai nimenomaisesti priorisoitava operaattorit lausekkeissa, jotka käyttävät makroparametreja suluissa:
#include <stdio.h> int main ( tyhjä ) { #define SQR(x) ((x) * (x)) printf ( "%d" , SQR ( 4 + 1 )); // totta, 25 paluu 0 ; }Ohjelma on joukko C-tiedostoja, jotka voidaan kääntää objektitiedostoiksi . Tämän jälkeen objektitiedostot käyvät läpi linkitysvaiheen keskenään sekä ulkoisten kirjastojen kanssa, jolloin tuloksena on lopullinen suoritettava tiedosto tai kirjasto . Tiedostojen linkittäminen keskenään sekä kirjastojen kanssa vaatii kuvauksen käytettävien toimintojen prototyypeistä, ulkoisista muuttujista ja tarvittavista tietotyypeistä kussakin tiedostossa. Tällaiset tiedot on tapana sijoittaa erillisiin otsikkotiedostoihin , jotka yhdistetään käskyllä niissä #include tiedostoissa, joissa vaaditaan tätä tai toista toiminnallisuutta, ja mahdollistaa moduulijärjestelmän kaltaisen järjestelmän järjestämisen. Tässä tapauksessa moduuli voi olla:
Koska direktiivi #includevain korvaa toisen tiedoston tekstin esikäsittelyvaiheessa , saman tiedoston sisällyttäminen useita kertoja voi johtaa käännösaikavirheisiin. Siksi tällaiset tiedostot suojaavat uudelleenkäyttöönottoa vastaan makrojen ja #define [ #ifndef65] .
LähdekooditiedostotC-lähdekooditiedoston runko koostuu joukosta yleisiä datamääritelmiä, -tyyppejä ja -funktioita. Yleiset muuttujat ja funktiot, jotka on ilmoitettu ja-määritteiden avulla, staticovat inlinekäytettävissä vain siinä tiedostossa, jossa ne on ilmoitettu, tai kun tiedosto sisältyy toiseen tiedostoon #include. Tässä tapauksessa otsikkotiedostossa sanalla ilmoitetut funktiot ja muuttujat staticluodaan uudelleen aina, kun otsikkotiedosto yhdistetään seuraavaan lähdekoodin sisältävään tiedostoon. Globaalit muuttujat ja funktioprototyypit, jotka on ilmoitettu ulkoisella määritteellä, katsotaan sisällytetyiksi muista tiedostoista. Eli niitä saa käyttää kuvauksen mukaisesti; oletetaan, että ohjelman rakentamisen jälkeen linkkiohjelma linkittää ne tiedostoissa kuvattuihin alkuperäisiin objekteihin ja toimintoihin.
Yleisiä muuttujia ja funktioita, paitsi staticja inline, voidaan käyttää muista tiedostoista, jos ne on ilmoitettu siellä oikein määritteen kanssa extern. Muokkaajalla ilmoitettuja muuttujia ja funktioita staticvoidaan käyttää myös muissa tiedostoissa, mutta vain silloin, kun niiden osoite välitetään osoittimella. Kirjoita ilmoitukset typedef, structeikä unionniitä voi tuoda muihin tiedostoihin. Jos niitä on tarpeen käyttää muissa tiedostoissa, ne tulee kopioida sinne tai sijoittaa erilliseen otsikkotiedostoon. Sama koskee inline-funktioita.
Ohjelman sisääntulopisteSuoritettavan ohjelman vakiolähtökohta on funktio nimeltä main, joka ei voi olla staattinen ja jonka on oltava ohjelmassa ainoa. Ohjelman suoritus alkaa funktion ensimmäisestä käskystä main()ja jatkuu sen poistumiseen asti, jonka jälkeen ohjelma lopettaa ja palauttaa käyttöjärjestelmälle abstraktin kokonaislukukoodin työnsä tuloksesta.
ei argumentteja | Komentoriviargumenteilla |
---|---|
int main ( void ); | int main ( int argc , char ** argv ); |
Kutsuttaessa muuttuja argcvälitetään ohjelmalle lähetettyjen argumenttien määrä, mukaan lukien polku itse ohjelmaan, joten argc-muuttuja sisältää yleensä arvon, joka on vähintään 1. Itse argvohjelman käynnistysrivi välitetään muuttujalle taulukkona. merkkijonoista, joiden viimeinen elementti on NULL. Kääntäjä takaa, että main()kaikki ohjelman globaalit muuttujat alustetaan, kun toimintoa ajetaan [67] .
Tämän seurauksena funktio main()voi palauttaa minkä tahansa kokonaisluvun tyypin arvoalueella int, joka välitetään käyttöjärjestelmään tai muuhun ympäristöön ohjelman palautuskoodina [66 ] . Kielistandardi ei määrittele paluukoodien merkitystä [68] . Yleensä käyttöjärjestelmällä, jossa ohjelmat ovat käynnissä, on jokin keino saada palautuskoodin arvo ja analysoida se. Joskus näiden koodien merkityksestä on tiettyjä sopimuksia. Yleinen käytäntö on, että palautuskoodi nolla osoittaa ohjelman onnistuneen valmistumisen, kun taas nollasta poikkeava arvo edustaa virhekoodia. Otsikkotiedosto stdlib.hmäärittelee kaksi yleistä makromääritelmää EXIT_SUCCESSja EXIT_FAILURE, jotka vastaavat ohjelman onnistunutta ja epäonnistunutta valmistumista [68] . Paluukoodeja voidaan käyttää myös sovelluksissa, jotka sisältävät useita prosesseja viestinnän tarjoamiseksi näiden prosessien välillä, jolloin sovellus itse määrittää kunkin palautuskoodin semanttisen merkityksen.
C tarjoaa neljä tapaa varata muistia, jotka määrittävät muuttujan eliniän ja sen alustushetken [67] .
Valintamenetelmä | Tavoitteet | Valinnan aika | julkaisuaika | Yleiskulut |
---|---|---|---|---|
Staattinen muistin varaus | Yleiset muuttujat ja muuttujat, jotka on merkitty avainsanalla static(mutta ilman _Thread_local) | Ohjelman alkaessa | Ohjelman lopussa | Puuttuu |
Muistin varaus säietasolla | Muuttujat merkitty avainsanalla_Thread_local | Kun lanka alkaa | Virran lopussa | Kun luot lankaa |
Automaattinen muistin varaus | Funktioiden argumentit ja palautusarvot, funktioiden paikalliset muuttujat, mukaan lukien vaihtelevan pituiset rekisterit ja taulukot | Kutsuttaessa toimintoja pinotasolla . | Automaattinen toimintojen päätyttyä | Merkitön, koska vain pinon yläosassa oleva osoitin muuttuu |
Dynaaminen muistin varaus | Muisti jaettu toimintojen malloc()ja calloc()jarealloc() | Manuaalisesti kasosta käytetyn funktion kutsumishetkellä. | Manuaalisesti käyttämällä toimintoafree() | Suuri sekä jakamiseen että vapauttamiseen |
Kaikki nämä tiedon tallennusmenetelmät sopivat erilaisiin tilanteisiin ja niillä on omat etunsa ja haittansa. Globaalit muuttujat eivät anna sinun kirjoittaa uudelleentuloalgoritmeja , ja automaattinen muistin varaus ei salli mielivaltaisen muistialueen palauttamista funktiokutsusta . Automaattinen allokointi ei myöskään sovellu suurten muistimäärien varaamiseen, koska se voi johtaa pinon tai kasan korruptioon [69] . Dynaamisessa muistissa ei ole näitä puutteita, mutta sillä on suuri yläraja sen käytössä ja sitä on vaikeampi käyttää.
Mikäli mahdollista, automaattista tai staattista muistin varausta suositellaan: tätä objektien tallennustapaa ohjaa kääntäjä , mikä vapauttaa ohjelmoijan manuaalisen varauksen ja muistin vapauttamisen vaivasta, joka on yleensä vaikeasti löydettävien muistivuotojen syy. segmentointivirheet ja virheiden vapauttaminen ohjelmassa . Valitettavasti monet tietorakenteet ovat kooltaan vaihtelevia ajon aikana, joten koska automaattisesti ja staattisesti allokoiduilla alueilla on oltava tiedossa kiinteä koko käännöshetkellä, on hyvin yleistä käyttää dynaamista allokointia.
Automaattisesti allokoiduille muuttujille registervoidaan käyttää muuntajaa, joka vihjaa kääntäjälle, jotta ne voivat nopeasti käyttää niitä. Tällaisia muuttujia voidaan sijoittaa prosessorirekistereihin. Rekistereiden rajallisesta määrästä ja mahdollisista kääntäjien optimoinneista johtuen muuttujat voivat päätyä tavalliseen muistiin, mutta niistä ei kuitenkaan ole mahdollista saada osoitinta ohjelmasta [70] . Muokkaus registeron ainoa, joka voidaan määrittää funktion argumenteissa [71] .
Muistin osoitusC-kieli peri lineaarisen muistiosoitteen työskennellessään rakenteiden, taulukoiden ja allokoitujen muistialueiden kanssa. Kielistandardi mahdollistaa myös vertailutoimintojen suorittamisen nollaosoittimille ja osoitteille taulukoissa, rakenteissa ja varatuilla muistialueilla. On myös sallittua työskennellä viimeistä seuraavan taulukkoelementin osoitteen kanssa, mikä tehdään kirjoitusalgoritmien helpottamiseksi. Eri muuttujille (tai muistialueille) saatujen osoiteosoittimien vertailua ei kuitenkaan pidä suorittaa, koska tulos riippuu tietyn kääntäjän toteutuksesta [72] .
Muistin esitysOhjelman muistiesitys riippuu laitteistoarkkitehtuurista, käyttöjärjestelmästä ja kääntäjästä. Joten esimerkiksi useimmissa arkkitehtuureissa pino kasvaa alas, mutta on arkkitehtuureja, joissa pino kasvaa [73] . Pinon ja pinon välinen raja voidaan osittain suojata pinon ylivuodolta erityisellä muistialueella [74] . Ja kirjastojen datan ja koodin sijainti voi riippua käännösvaihtoehdoista [75] . C-standardi abstrahoituu toteutuksesta ja mahdollistaa kannettavan koodin kirjoittamisen, mutta prosessin muistirakenteen ymmärtäminen auttaa virheenkorjauksessa ja turvallisten ja vikasietoisten sovellusten kirjoittamisessa.
Tyypillinen esitys prosessimuistista Unix-tyyppisissä käyttöjärjestelmissäKun ohjelma käynnistetään suoritettavasta tiedostosta, prosessoriohjeet (konekoodi) ja alustetut tiedot tuodaan RAM-muistiin. main()Samanaikaisesti komentoriviargumentit (saatavilla funktioissa , joissa on seuraava allekirjoitus toisessa argumentissa int argc, char ** argv) ja ympäristömuuttujat tuodaan korkeampiin osoitteisiin .
Alustamaton tietoalue sisältää globaaleja muuttujia (mukaan lukien ne, jotka on ilmoitettu muodossa static), joita ei ole alustettu ohjelmakoodissa. Tällaiset muuttujat alustetaan oletusarvoisesti nollaan ohjelman käynnistymisen jälkeen. Alustavien tietojen alue - datasegmentti - sisältää myös globaaleja muuttujia, mutta tämä alue sisältää ne muuttujat, joille on annettu alkuarvo. Muuttumaton data, mukaan lukien muuttujat, jotka on ilmoitettu muuntimella const, merkkijonoliteraalit ja muut yhdistelmäliteraalit, sijoitetaan ohjelman tekstisegmenttiin. Ohjelman tekstisegmentti sisältää myös suoritettavaa koodia ja on vain luku -tilassa, joten yritys muokata tämän segmentin tietoja johtaa määrittelemättömään toimintaan segmentointivian muodossa .
Pinoalueen on tarkoitus sisältää funktiokutsuihin ja paikallisiin muuttujiin liittyvää dataa. Ennen jokaista funktion suoritusta pino laajennetaan funktiolle välitettävien argumenttien mukaan. Toimintansa aikana funktio voi allokoida pinossa paikallisia muuttujia ja varata sille muistia vaihtelevan pituisille taulukoille, ja jotkut kääntäjät tarjoavat myös keinoja varata pinon sisällä muistia kutsulla alloca(), joka ei sisälly kielistandardiin. . Toiminnon päätyttyä pino pienennetään arvoon, joka oli ennen kutsua, mutta tämä ei välttämättä tapahdu, jos pinoa käsitellään väärin. Dynaamisesti varattu muisti saadaan kasasta .
Tärkeä yksityiskohta on satunnainen täyttö pinon ja yläalueen välillä [77] sekä alustetun tietoalueen ja kasan välillä . Tämä tehdään turvallisuussyistä, kuten muiden toimintojen pinoamisen estämiseksi.
Dynaamiset linkkikirjastot ja tiedostojärjestelmän tiedostokartoitukset sijaitsevat pinon ja keon välissä [78] .
C:ssä ei ole sisäänrakennettuja virheenhallintamekanismeja, mutta on olemassa useita yleisesti hyväksyttyjä tapoja käsitellä virheitä kielen avulla. Yleensä C-virheiden käsittely vikasietoisessa koodissa pakottaa kirjoittamaan hankalia, usein toistuvia rakenteita, joissa algoritmi on yhdistetty virheenkäsittelyyn .
Virhemerkit ja errnoC-kieli käyttää aktiivisesti erikoismuuttujaa errnootsikkotiedostosta errno.h, jossa funktiot syöttävät virhekoodin ja palauttavat arvon, joka on virhemerkki. Tuloksen virheiden tarkistamiseksi tulosta verrataan virhemerkkiin, ja jos ne täsmäävät, voit analysoida tallennettua virhekoodia errnoohjelman korjaamiseksi tai virheenkorjausviestin näyttämiseksi. Standardikirjastossa standardi määrittelee usein vain palautetut virhemerkit, ja asetus errnoon toteutusriippuvainen [79] .
Seuraavat arvot toimivat yleensä virhemerkeinä:
Käytäntö palauttaa virhemerkki virhekoodin sijaan, vaikka se säästää funktiolle välitettyjen argumenttien määrän, johtaa joissakin tapauksissa virheisiin inhimillisen tekijän seurauksena. On esimerkiksi yleistä, että ohjelmoijat jättävät huomioimatta tyypin tyypin tuloksen tarkistamisen ssize_t, ja itse tulosta käytetään edelleen laskelmissa, mikä johtaa hienovaraisiin virheisiin, jos -1[82] palautetaan .
Oikean arvon palauttaminen virhemerkkinä [82] edistää edelleen virheiden ilmaantumista , mikä myös pakottaa ohjelmoijan tekemään enemmän tarkistuksia ja vastaavasti kirjoittamaan enemmän samantyyppistä toistuvaa koodia. Tätä lähestymistapaa harjoitetaan stream-funktioissa, jotka toimivat tyyppisten objektien kanssa FILE *: virhemerkki on arvo EOF, joka on myös tiedoston lopun merkki. Siksi EOFjoskus sinun on tarkistettava merkkivirta sekä tiedoston lopun osalta funktiolla feof(), että virheen olemassaolosta käyttämällä ferror()[83] . Samanaikaisesti joitain toimintoja, jotka voivat palata EOFstandardin mukaan, ei tarvitse asettaa errno[79] .
Yhtenäisen virheenkäsittelykäytännön puute vakiokirjastossa johtaa mukautettujen virheenkäsittelymenetelmien ilmestymiseen ja yleisesti käytettyjen menetelmien yhdistelmään kolmansien osapuolien projekteissa. Esimerkiksi systemd -projektissa ideat virhekoodin ja numeron palauttamisesta -1merkkinä yhdistettiin - palautetaan negatiivinen virhekoodi [84] . Ja GLib - kirjasto esitteli käytännön palauttaa loogisen arvon virhemerkkinä , kun taas virheen yksityiskohdat sijoitetaan erityiseen rakenteeseen, johon osoitin palautetaan funktion viimeisen argumentin kautta [85] . Samanlaista ratkaisua käyttää Enlightenment -projekti , joka käyttää myös Boolen tyyppiä merkkinä, mutta palauttaa virhetiedot, jotka ovat samanlaisia kuin standardikirjasto - erillisen funktion [86] kautta , joka on tarkistettava, jos merkki palautettiin.
Virhekoodin palauttaminenVaihtoehto virhemarkkereille on palauttaa virhekoodi suoraan ja palauttaa funktion tulos osoitinargumenttien kautta. POSIX-standardin kehittäjät valitsivat tämän polun, jonka toiminnoissa on tapana palauttaa virhekoodi numerotyyppinä int. Tyyppiarvon palauttaminen intei kuitenkaan tee selväksi, että se on virhekoodi, joka palautetaan, eikä merkki, mikä voi johtaa virheisiin, jos tällaisten funktioiden tulos tarkistetaan arvoon -1. C11-standardin laajennus K esittelee erikoistyypin errno_tvirhekoodin tallentamiseen. On suositeltavaa käyttää tätä tyyppiä käyttäjäkoodissa virheiden palauttamiseksi, ja jos se ei ole vakiokirjastossa, ilmoita se itse [87] :
#ifndef __STDC_LIB_EXT1__ typedef int errno_t ; #loppu JosTämä lähestymistapa koodin laadun parantamisen lisäksi poistaa tarpeen käyttää errno, jonka avulla voit tehdä kirjastoja, joissa on palautuvia toimintoja ilman, että sinun tarvitsee sisällyttää lisäkirjastoja, kuten POSIX Threads , määrittääksesi oikein errno.
Virheet matemaattisissa funktioissaMonimutkaisempi on matemaattisten funktioiden virheiden käsittely otsikkotiedostosta math.h, jossa voi esiintyä kolmenlaisia virheitä [88] :
Kahden kolmesta virhetyypistä ehkäisy tarkoittaa, että syötetiedot tarkistetaan kelvollisten arvojen alueella. On kuitenkin erittäin vaikea ennustaa tuloksen tuottoa tyypin rajojen ulkopuolella. Siksi kielistandardi tarjoaa mahdollisuuden analysoida matemaattisia funktioita virheiden varalta. C99-standardista alkaen tämä analyysi on mahdollista kahdella tavalla, riippuen tiedostoon tallennetusta arvosta math_errhandling.
Tässä tapauksessa virheenkäsittelymenetelmä määräytyy vakiokirjaston tietyn toteutuksen mukaan, ja se voi puuttua kokonaan. Siksi alustariippumattomassa koodissa voi olla tarpeen tarkistaa tulos kahdella tavalla kerralla, riippuen math_errhandling[88] :n arvosta .
Resurssien vapauttaminenTyypillisesti virheen esiintyminen edellyttää, että toiminto poistuu ja palauttaa virheilmaisimen. Jos toiminnossa voi esiintyä virhe sen eri osissa, sen toiminnan aikana varatut resurssit on vapautettava vuotojen estämiseksi. On hyvä käytäntö vapauttaa resurssit käänteisessä järjestyksessä ennen paluuta funktiosta ja virheiden sattuessa käänteisessä järjestyksessä pääfunktion jälkeen return. Tällaisen julkaisun erillisissä osissa voit hypätä käyttämällä operaattoria goto[89] . Tämän lähestymistavan avulla voit siirtää koodin osia, jotka eivät liity toteutettavaan algoritmiin, itse algoritmin ulkopuolelle, mikä lisää koodin luettavuutta, ja se on samanlainen kuin deferGo- ohjelmointikielen operaattorin työ . Esimerkki resurssien vapauttamisesta on annettu alla esimerkkiosiossa .
Resurssien vapauttamiseksi ohjelman sisällä tarjotaan ohjelman poistumiskäsittelijämekanismi. Käsittelijät määritetään funktion avulla atexit()ja ne suoritetaan sekä funktion lopussa main()käskyn kautta returnettä funktion suorittamisen yhteydessä exit(). Tässä tapauksessa funktiot abort()ja _Exit()[90] eivät suorita käsittelijöitä .
Esimerkki resurssien vapauttamisesta ohjelman lopussa on globaaleille muuttujille varatun muistin vapauttaminen. Huolimatta siitä, että muisti vapautuu tavalla tai toisella sen jälkeen, kun käyttöjärjestelmä lopettaa ohjelman, ja koko ohjelman toiminnan aikana tarvittavaa muistia ei saa vapauttaa [91] , eksplisiittisen purkaminen on suositeltavaa, koska se tekee siitä helpompi löytää muistivuotoja kolmansien osapuolien työkaluilla ja vähentää virheestä johtuvien muistivuotojen mahdollisuutta:
Esimerkkiohjelmakoodi resurssien julkaisulla #include <stdio.h> #include <stdlib.h> int numeroiden_määrä ; int * numerot ; void free_numbers ( void ) { ilmainen ( numerot ); } int main ( int argc , char ** argv ) { if ( arg < 2 ) { exit ( EXIT_FAILURE ); } numeroiden_määrä = atoi ( argv [ 1 ]); if ( numeroiden_määrä <= 0 ) { exit ( EXIT_FAILURE ); } numerot = calloc ( numeroiden_määrä , koko ( * numerot )); if ( ! numerot ) { perror ( "Virhe varattaessa muistia taulukolle" ); exit ( EXIT_FAILURE ); } atexit ( free_numbers ); // ... työskennellä numerotaulukon kanssa // Free_numbers()-käsittelijä kutsutaan automaattisesti tänne paluu EXIT_SUCCESS ; }Tämän lähestymistavan haittana on, että määritettävien käsittelijöiden muoto ei salli mielivaltaisten tietojen välittämistä funktiolle, mikä mahdollistaa käsittelijöiden luomisen vain globaaleille muuttujille.
Minimaalinen C-ohjelma, joka ei vaadi argumenttien käsittelyä, on seuraava:
int main ( void ){}returnFunktiolle ei saa kirjoittaa operaattoria main(). Tässä tapauksessa funktio main()palauttaa standardin mukaan 0:n ja suorittaa kaikki funktiolle annetut käsittelijät exit(). Tämä olettaa, että ohjelma on suoritettu onnistuneesti [40] .
Hei maailma!Hei maailma! on Kernighanin ja Ritchien kirjan " The C Programming Language " ensimmäisessä painoksessa:
#include <stdio.h> int main ( void ) // Ei ota argumentteja { printf ( "Hei, maailma! \n " ); // '\n' - uusi rivi paluu 0 ; // Onnistunut ohjelman lopettaminen }Tämä ohjelma tulostaa viestin Hello, world! ' vakiolähdöllä .
Virheiden käsittely käyttämällä tiedoston lukua esimerkkinäMonet C-funktiot voivat palauttaa virheen tekemättä sitä, mitä niiden piti tehdä. Virheet on tarkistettava ja niihin on vastattava oikein, mukaan lukien usein tarve heittää virhe funktiosta korkeammalle tasolle analysointia varten. Samalla toiminto, jossa virhe tapahtui, voidaan tehdä reentrant , jolloin toiminto ei vahingossa saisi muuttaa tulo- tai lähtötietoja, jolloin voit käynnistää sen turvallisesti uudelleen virhetilanteen korjaamisen jälkeen.
Esimerkki toteuttaa funktion tiedoston lukemiseksi C-kielellä, mutta se edellyttää funktioiden fopen()ja POSIXfread() - standardin noudattamista , muuten ne eivät ehkä aseta muuttujaa , mikä vaikeuttaa suuresti sekä virheenkorjausta että yleisen ja turvallisen koodin kirjoittamista. Muilla kuin POSIX-alustoilla tämän ohjelman toiminta on määrittelemätön -virheen sattuessa . Virheiden resurssien jakaminen on pääalgoritmin takana luettavuuden parantamiseksi, ja siirto tapahtuu käyttämällä [89] . errnogoto
Tiedostonlukijan esimerkkikoodi virheenkäsittelyllä #include <errno.h> #include <stdio.h> #include <stdlib.h> // Määritä virhekoodin tallennustyyppi, jos sitä ei ole määritetty #ifndef __STDC_LIB_EXT1__ typedef int errno_t ; #loppu Jos enum { EOK = 0 , // errno_t:n arvo onnistumiselle }; // Toiminto tiedoston sisällön lukemiseen errno_t get_file_contents ( const char * tiedostonimi , void ** contents_ptr , koko_t * contents_size_ptr ) { TIEDOSTO * f ; f = fopen ( tiedostonimi , "rb" ); if ( ! f ) { // POSIXissa fopen() asettaa errno vahingossa palautusvirhe ; _ } // Hae tiedostokoko fseek ( f , 0 , SEEK_END ); pitkä sisällön_koko = ftell ( f ); if ( contents_size == 0 ) { * contents_ptr = NULL ; * contents_size_ptr = 0 ; goto cleaning_fopen ; } kelaa taaksepäin ( f ); // Muuttuja palautetun virhekoodin tallentamiseen errno_t saved_errno ; void * sisältö ; contents = malloc ( sisällön_koko ); if ( ! sisältö ) { saved_errno = errno ; goto aborting_fopen ; } // Lue koko tiedoston sisältö sisältöosoittimesta koko_t n ; n = fread ( sisältö , sisällön_koko , 1 , f ); if ( n == 0 ) { // Älä tarkista feof():ta, koska puskuroitu fseek() jälkeen // POSIX fread() asettaa errno vahingossa saved_errno = errno ; goto aborting_contents ; } // Palauta varattu muisti ja sen koko * contents_ptr = sisältö ; * contents_size_ptr = sisällön_koko ; // Resurssien julkaisu -osio menestyksestä puhdistus_fopen : fclose ( f ); paluu EOK ; // Erillinen osio resurssien vahingossa vapauttamiseen aborting_contents : ilmainen ( sisältö ); aborting_fopen : fclose ( f ); palauttaa tallennettu_errno ; } int main ( int argc , char ** argv ) { if ( arg < 2 ) { palauttaa EXIT_FAILURE ; } const char * tiedostonimi = argv [ 1 ]; errno_t errnum ; void * sisältö ; koko_t sisällön_koko ; errnum = get_file_contents ( tiedoston nimi , & sisältö , & sisällön_koko ); if ( errnum ) { charbuf [ 1024 ] ; const char * error_text = strerror_r ( errnum , buf , sizeof ( buf )); fprintf ( stderr , "%s \n " , error_text ); exit ( EXIT_FAILURE ); } printf ( "%.*s" , ( int ) sisällön_koko , sisältö ); ilmainen ( sisältö ); paluu EXIT_SUCCESS ; }Jotkut kääntäjät toimitetaan muiden ohjelmointikielien (mukaan lukien C++ ) kääntäjien kanssa tai ne ovat osa ohjelmistokehitysympäristöä .
|
|
Huolimatta siitä, että standardikirjasto on osa kielistandardia, sen toteutukset ovat erillisiä kääntäjistä. Siksi kääntäjän ja kirjaston tukemat kielistandardit voivat poiketa toisistaan.
Koska C-kieli ei tarjoa keinoa koodin kirjoittamiseen turvallisesti ja monet kielen elementit aiheuttavat virheitä, laadukkaan ja vikasietoisen koodin kirjoittaminen voidaan taata vain kirjoittamalla automaattisia testejä. Tällaisen testauksen helpottamiseksi on olemassa erilaisia toteutuksia kolmannen osapuolen yksikkötestikirjastoista .
On myös monia muita järjestelmiä C-koodin testaamiseen, kuten AceUnit, GNU Autounit, cUnit ja muut, mutta ne joko eivät testaa eristetyissä ympäristöissä, tarjoavat vain vähän ominaisuuksia [100] tai niitä ei enää kehitetä.
VirheenkorjaustyökalutVirheiden ilmentymien perusteella ei aina ole mahdollista tehdä yksiselitteistä johtopäätöstä koodin ongelma-alueesta, mutta erilaiset virheenkorjaustyökalut auttavat usein paikallistamaan ongelman.
Joskus tiettyjen C-kielellä kirjoitettujen kirjastojen, toimintojen ja työkalujen siirtämiseksi toiseen ympäristöön on tarpeen kääntää C-koodi ylemmän tason kielelle tai sellaiselle kielelle suunnitellun virtuaalikoneen koodiksi. Seuraavat projektit on suunniteltu tähän tarkoitukseen:
Myös C:lle on olemassa muita työkaluja, jotka helpottavat ja täydentävät kehitystä, mukaan lukien staattiset analysaattorit ja apuohjelmat koodin muotoiluun. Staattinen analyysi auttaa tunnistamaan mahdolliset virheet ja haavoittuvuudet. Ja automaattinen koodin muotoilu yksinkertaistaa yhteistyön järjestämistä versionhallintajärjestelmissä ja minimoi tyylimuutosten aiheuttamat ristiriidat.
Kieltä käytetään laajalti käyttöjärjestelmien kehittämisessä, käyttöjärjestelmän API-tasolla, sulautetuissa järjestelmissä sekä tehokkaan tai virhekriittisen koodin kirjoittamiseen. Yksi syistä matalan tason ohjelmoinnin laajaan käyttöön on kyky kirjoittaa cross-platform-koodia, jota voidaan käsitellä eri tavalla eri laitteissa ja käyttöjärjestelmissä.
Kyky kirjoittaa korkean suorituskyvyn koodia tapahtuu ohjelmoijan täydellisen toimintavapauden ja kääntäjän tiukan valvonnan puuttumisen kustannuksella. Esimerkiksi Java , Python , Perl ja PHP ensimmäiset toteutukset kirjoitettiin C -kielellä. Samaan aikaan monissa ohjelmissa eniten resursseja vaativat osat kirjoitetaan yleensä C-kielellä. Mathematican [109] ydin on kirjoitettu C-kielellä, kun taas alun perin Fortranilla kirjoitettu MATLAB kirjoitettiin uudelleen C-kielellä vuonna 1984 [110] .
C:tä käytetään joskus myös välikielenä korkeamman tason kieliä käännettäessä. Esimerkiksi C++- , Objective-C- ja Go - kielten ensimmäiset toteutukset toimivat tämän periaatteen mukaisesti - näillä kielillä kirjoitettu koodi käännettiin C-kielen väliesitykseen. Samalla periaatteella toimivat nykykielet ovat Vala ja Nim .
Toinen C-kielen sovellusalue on reaaliaikaiset sovellukset , jotka vaativat koodin reagointikykyä ja sen suoritusaikaa. Tällaisten sovellusten on aloitettava toimenpiteiden suorittaminen tiukasti rajoitetun ajan sisällä, ja itse toimien tulee mahtua tiettyyn ajanjaksoon. Erityisesti POSIX.1- standardi tarjoaa joukon toimintoja ja ominaisuuksia reaaliaikaisten sovellusten rakentamiseen [111] [112] [113] , mutta käyttöjärjestelmän on myös otettava käyttöön kova reaaliaikainen tuki [114] .
C-kieli on ollut ja on edelleen yksi yleisimmin käytetyistä ohjelmointikielistä yli neljänkymmenen vuoden ajan. Luonnollisesti sen vaikutus voidaan jäljittää jossain määrin monissa myöhemmissä kielissä. Siitä huolimatta tietyn levinneisyyden saavuttaneiden kielten joukossa on vain vähän C:n suoria jälkeläisiä.
Jotkut jälkeläiskielet rakentuvat C:lle lisätyökaluilla ja -mekanismeilla, jotka lisäävät tukea uusille ohjelmointiparadigmoille ( OOP , toiminnallinen ohjelmointi , yleinen ohjelmointi jne.). Näihin kieliin kuuluvat ensisijaisesti C++ ja Objective-C sekä välillisesti niiden jälkeläiset Swift ja D. Tunnetaan myös yrityksiä parantaa C:tä korjaamalla sen merkittävimmät puutteet, mutta säilyttämällä sen houkuttelevat ominaisuudet. Niistä voidaan mainita tutkimuskieli Cyclone (ja sen jälkeläinen Rust ). Joskus molemmat kehityssuunnat yhdistetään yhdellä kielellä, Go on esimerkki .
Erikseen on mainittava koko joukko kieliä, jotka enemmän tai vähemmän ovat perineet C:n perussyntaksin (kiharasulkujen käyttö koodilohkojen erottimina, muuttujien ilmoittaminen, operaattorien tyypilliset muodot for, while, if, switchparametrit suluissa, yhdistetyt toiminnot ++, --, +=, -=ja muut) , minkä vuoksi näiden kielten ohjelmilla on tyypillinen ulkoasu, joka liittyy nimenomaan C:hen. Nämä ovat kieliä, kuten Java , JavaScript , PHP , Perl , AWK , C# . Itse asiassa näiden kielten rakenne ja semantiikka ovat hyvin erilaisia kuin C, ja ne on yleensä tarkoitettu sovelluksiin, joissa alkuperäistä C:tä ei koskaan käytetty.
C++ - ohjelmointikieli luotiin C:stä ja peri sen syntaksin täydentäen sitä uusilla rakenteilla Simula-67:n, Smalltalkin, Modula-2:n, Adan, Mesan ja Clu:n hengessä [116] . Tärkeimmät lisäykset olivat tuki OOP :lle (luokkakuvaus, moniperintö, virtuaalisiin funktioihin perustuva polymorfismi) ja geneerinen ohjelmointi (mallimoottori). Mutta tämän lisäksi kieleen on tehty monia erilaisia lisäyksiä. Tällä hetkellä C++ on yksi laajimmin käytetyistä ohjelmointikielistä maailmassa ja se on sijoitettu yleiskäyttöiseksi kieleksi, joka painottaa järjestelmäohjelmointia [117] .
Aluksi C++ säilytti yhteensopivuuden C:n kanssa, mikä todettiin yhdeksi uuden kielen eduista. Ensimmäiset C++:n toteutukset yksinkertaisesti käänsivät uudet konstruktit puhtaaksi C:ksi, minkä jälkeen koodi käsiteltiin tavallisella C-kääntäjällä. Yhteensopivuuden säilyttämiseksi C++:n luojat kieltäytyivät jättämästä siitä pois joitakin C++:n usein kritisoiduista ominaisuuksista, vaan luovat sen sijaan uusia, "rinnakkaismekanismeja", joita suositellaan kehitettäessä uutta C++-koodia (mallit makrojen sijaan, eksplisiittinen tyyppisuora automaattisen sijaan , vakiokirjastosäilöjä manuaalisen dynaamisen muistin varauksen sijaan ja niin edelleen). Kielet ovat kuitenkin sittemmin kehittyneet itsenäisesti, ja nyt uusimpien standardien C ja C++ ovat vain osittain yhteensopivia: ei ole takeita siitä, että C++-kääntäjä onnistuu kääntämään C-ohjelman, ja jos onnistuu, ei ole takeita siitä, että käännetty ohjelma toimii oikein. Erityisen ärsyttäviä ovat jotkut hienovaraiset semanttiset erot, jotka voivat johtaa saman koodin erilaiseen käyttäytymiseen, joka on syntaktisesti oikein molemmilla kielillä. Esimerkiksi merkkivakioiden (merkkien sisällä lainausmerkeissä) tyyppi on intC ja tyyppi C++ , joten tällaisten vakioiden käyttämä muisti vaihtelee kielestä toiseen. [118] Jos ohjelma on herkkä merkkivakion koolle, se käyttäytyy eri tavalla, kun se käännetään C- ja C++-kääntäjillä. char
Tällaiset erot vaikeuttavat sellaisten ohjelmien ja kirjastojen kirjoittamista, jotka voivat kääntää ja toimia samalla tavalla sekä C:ssä että C++ :ssa , mikä tietysti hämmentää molemmilla kielillä ohjelmoivia. Sekä C- että C++-kehittäjien ja käyttäjien keskuudessa kannattavat kielten välisten erojen minimoimista, mikä toisi objektiivisesti konkreettisia etuja. On kuitenkin päinvastainen näkemys, jonka mukaan yhteensopivuus ei ole erityisen tärkeää, vaikka se on hyödyllistä, eikä yhteensopivuuden vähentäminen saa estää kunkin kielen kehittämistä erikseen.
Toinen vaihtoehto C:n laajentamiseen oliopohjaisilla työkaluilla on vuonna 1983 luotu Objective-C- kieli. Olioalijärjestelmä lainattiin Smalltalkilta ja kaikki tähän alijärjestelmään liittyvät elementit on toteutettu omassa syntaksissaan, joka eroaa melko jyrkästi C-syntaksista (johon asti luokkakuvauksissa kenttien ilmoittamisen syntaksi on päinvastainen kuin C:n muuttujien ilmoittamisen syntaksi: ensin kirjoitetaan kentän nimi, sitten sen tyyppi). Toisin kuin C++, Objective-C on klassisen C:n superjoukko, eli se säilyttää yhteensopivuuden lähdekielen kanssa; oikea C-ohjelma on oikea Objective-C-ohjelma. Toinen merkittävä ero C++-ideologiaan on se, että Objective-C toteuttaa objektien vuorovaikutusta vaihtamalla täysimittaisia viestejä, kun taas C++ toteuttaa käsitteen "viestin lähettäminen menetelmäkutsuna". Täysi viestien käsittely on paljon joustavampaa ja sopii luonnollisesti rinnakkaislaskentaan. Objective-C sekä sen suora jälkeläinen Swift ovat suosituimpia Applen tukemilla alustoilla .
C-kieli on ainutlaatuinen siinä mielessä, että se oli ensimmäinen korkean tason kieli , joka syrjäytti kokoajan järjestelmäohjelmistojen kehittämisessä . Se on edelleen kieli, jota käytetään eniten laitteistoalustoilla ja yksi suosituimmista ohjelmointikielistä , erityisesti vapaiden ohjelmistojen maailmassa [119] . Siitä huolimatta kielessä on monia puutteita, ja sen perustamisesta lähtien monet asiantuntijat ovat kritisoineet sitä.
Kieli on hyvin monimutkaista ja täynnä vaarallisia elementtejä, joita on erittäin helppo käyttää väärin. Rakenteellaan ja säännöillään se ei tue ohjelmointia, jonka tavoitteena on luoda luotettavaa ja ylläpidettävää ohjelmakoodia, vaan päinvastoin, eri prosessoreille suoritetun ohjelmoinnin aikakaudella syntynyt kieli myötävaikuttaa vaarallisen ja hämmentävän koodin kirjoittamiseen [119] . Monet ammattiohjelmoijat ajattelevat, että C-kieli on tehokas työkalu tyylikkäiden ohjelmien luomiseen, mutta samalla sillä voidaan luoda erittäin huonolaatuisia ratkaisuja [120] [121] .
Kielen erilaisten oletusten vuoksi ohjelmat voivat kääntää useita virheitä, mikä usein johtaa arvaamattomaan ohjelman toimintaan. Nykyaikaiset kääntäjät tarjoavat vaihtoehtoja staattiseen koodin analysointiin [122] [123] , mutta nekään eivät pysty havaitsemaan kaikkia mahdollisia virheitä. Lukutaidoton C-ohjelmointi voi aiheuttaa ohjelmiston haavoittuvuuksia , jotka voivat vaikuttaa sen käytön turvallisuuteen .
Xi:llä on korkea pääsykynnys [119] . Sen määrittely vie yli 500 sivua tekstiä, joka on tutkittava kokonaan, koska virheettömän ja laadukkaan koodin luomiseksi on otettava huomioon monet kielen epäselvät ominaisuudet. Esimerkiksi kokonaislukulausekkeiden operandien automaattinen syöttö kirjoitukseen intvoi antaa vaikeasti ennustettavia tuloksia käytettäessä binäärioperaattoreita [44] :
etumerkitön merkki x = 0xFF ; etumerkitön char y = ( ~ x | 0x1 ) >> 1 ; // Intuitiivisesti tässä odotetaan 0x00 printf ( "y = 0x%hhX \n " , y ); // Tulostaa 0x80, jos sizeof(int) > sizeof(char)Tällaisten vivahteiden ymmärtämisen puute voi johtaa lukuisiin virheisiin ja haavoittuvuuksiin. Toinen C:n hallitsemisen monimutkaisuutta lisäävä tekijä on kääntäjän palautteen puute: kieli antaa ohjelmoijalle täydellisen toimintavapauden ja mahdollistaa ohjelmien kääntämisen, joissa on ilmeisiä loogisia virheitä. Kaikki tämä vaikeuttaa C :n käyttöä opetuksessa ensimmäisenä ohjelmointikielenä [119]
Lopuksi, yli 40 vuoden olemassaolon aikana kieli on vanhentunut, ja on melko ongelmallista käyttää siinä monia moderneja ohjelmointitekniikoita ja paradigmoja .
C-syntaksissa ei ole moduuleja ja mekanismeja niiden vuorovaikutukseen. Lähdekooditiedostot kootaan erikseen, ja niiden tulee sisältää muista tiedostoista tuotujen muuttujien, funktioiden ja tietotyyppien prototyyppejä. Tämä tehdään sisällyttämällä otsikkotiedostot makrokorvauksen avulla . Jos kooditiedostojen ja otsikkotiedostojen välinen vastaavuus rikotaan, voi tapahtua sekä linkkiaikavirheitä että kaikenlaisia ajonaikaisia virheitä: pinon ja kasan korruptiosta segmentointivirheisiin . Koska direktiivi korvaa vain yhden tiedoston tekstin toisella, suuren määrän otsikkotiedostojen sisällyttäminen johtaa siihen, että käännettävän koodin todellinen määrä moninkertaistuu, mikä on syynä suhteellisen hitaalle suorituskyvylle. C-kääntäjät. Tarve koordinoida kuvaukset päämoduulissa ja otsikkotiedostoissa vaikeuttaa ohjelman ylläpitoa. #include#include
Varoitukset virheiden sijaanKielistandardi antaa ohjelmoijalle enemmän toimintavapautta ja siten suuren mahdollisuuden tehdä virheitä. Suurin osa siitä, mikä ei ole useimmiten sallittua, on kielen sallimaa, ja kääntäjä antaa parhaimmillaan varoituksia. Vaikka nykyaikaiset kääntäjät mahdollistavat kaikkien varoitusten muuntamisen virheiksi, tätä ominaisuutta käytetään harvoin, ja useimmiten varoitukset ohitetaan, jos ohjelma toimii tyydyttävästi.
Joten esimerkiksi ennen C99-standardia funktion kutsuminen mallocilman otsikkotiedostoa stdlib.hsaattoi johtaa pinon vioittumiseen, koska prototyypin puuttuessa funktiota kutsuttiin palauttavana tyypin int, kun taas itse asiassa se palautti tyypin void*( virhe tapahtui, kun kohdealustan tyyppien koot erosivat). Siitä huolimatta se oli vain varoitus.
Muuttujien alustuksen hallinnan puuteAutomaattisesti ja dynaamisesti luotuja objekteja ei alustata oletusarvoisesti, ja kun ne on luotu, ne sisältävät arvot, jotka ovat jääneet muistiin aiemmin siellä olleista objekteista. Tällainen arvo on täysin arvaamaton, se vaihtelee koneesta toiseen, ajosta suoritukseen, funktiokutsusta kutsuun. Jos ohjelma käyttää tällaista arvoa alustuksen vahingossa tekemättä jättämisen vuoksi, tulos on arvaamaton eikä välttämättä näy heti. Nykyaikaiset kääntäjät yrittävät diagnosoida tämän ongelman staattisella lähdekoodin analyysillä, vaikka yleensä ongelman ratkaiseminen staattisen analyysin avulla on erittäin vaikeaa. Lisätyökaluja voidaan käyttää näiden ongelmien tunnistamiseen testausvaiheessa ohjelman suorittamisen aikana: Valgrind ja MemorySanitizer [124] .
Osoitteiden aritmeettisen hallinnan puuteVaarallisten tilanteiden lähde on osoittimien yhteensopivuus numeeristen tyyppien kanssa ja mahdollisuus käyttää osoitearitmetiikkaa ilman tiukkaa valvontaa käännös- ja suoritusvaiheissa. Tämä mahdollistaa osoittimen saamisen mihin tahansa objektiin, myös suoritettavaan koodiin, ja viitata tähän osoittimeen, ellei järjestelmän muistin suojaus estä tätä.
Osoittimien virheellinen käyttö voi aiheuttaa ohjelman määrittelemätöntä toimintaa ja johtaa vakaviin seurauksiin. Osoitin voi esimerkiksi olla alustamatta tai virheellisten aritmeettisten operaatioiden seurauksena osoittaa mielivaltaiseen muistipaikkaan. Joillakin alustoilla tällaisen osoittimen käyttäminen saattaa pakottaa ohjelman pysähtymään, toisilla se voi vioittaa mielivaltaisia muistissa olevia tietoja. Viimeinen virhe on vaarallinen, koska sen seuraukset ovat arvaamattomia ja voivat ilmaantua milloin tahansa, myös paljon myöhemmin kuin todellisen virheellisen toiminnan hetki.
Pääsy taulukoihin C:ssä on myös toteutettu osoitearitmetiikalla, eikä se tarkoita keinoja tarkistaa taulukon elementtien oikeellisuuden indeksin perusteella. Esimerkiksi lausekkeet a[i]ja i[a]ovat identtisiä ja ne käännetään yksinkertaisesti muotoon *(a + i), eikä rajojen ulkopuolisten taulukoiden tarkistusta suoriteta. Pääsy indeksillä, joka on suurempi kuin taulukon yläraja, johtaa taulukon jälkeen olevassa muistissa olevaan dataan, jota kutsutaan puskurin ylivuodoksi . Kun tällainen kutsu on virheellinen, se voi johtaa arvaamattomaan ohjelman toimintaan [57] . Usein tätä ominaisuutta käytetään hyväksikäytöissä , joilla päästään laittomasti toisen sovelluksen muistiin tai käyttöjärjestelmän ytimen muistiin.
Virheille altis dynaaminen muistiJärjestelmätoiminnot dynaamisesti varatun muistin kanssa työskentelyyn eivät tarjoa hallintaa sen varauksen ja vapauttamisen oikeellisuudesta ja oikea-aikaisuudesta, vaan dynaamisen muistin kanssa työskentelyn oikean järjestyksen noudattaminen on täysin ohjelmoijan vastuulla. Sen virheet voivat johtaa vääriin osoitteisiin pääsyyn, ennenaikaiseen julkaisuun tai muistivuotoon (jälkimmäinen on mahdollista esimerkiksi, jos kehittäjä unohti soittaa free()tai kutsua kutsuvaan free()toimintoon, kun sitä vaadittiin) [125] .
Yksi yleisimmistä virheistä on, että muistinvaraustoimintojen ( malloc(), calloc()ja muut) tulosta ei tarkisteta -sovelluksessa NULL, kun taas muistia ei välttämättä varata, jos sitä ei ole tarpeeksi tai jos sitä pyydettiin liikaa, esim. -1virheellisten matemaattisten operaatioiden seurauksena saadun luvun vähentäminen etumerkittömään tyyppiin size_tja siihen liittyvät myöhemmät operaatiot . Toinen ongelma järjestelmän muistifunktioissa on määrittelemätön käyttäytyminen pyydettäessä nollakokoista lohkovarausta: funktiot voivat palauttaa joko tai todellisen osoitinarvon, riippuen tietystä toteutuksesta [126] . NULL
Jotkut erityiset toteutukset ja kolmannen osapuolen kirjastot tarjoavat ominaisuuksia, kuten viitteiden laskemisen ja heikkojen viittausten [127] , älykkäiden osoittimien [128] ja rajoitetun jätteenkeräyksen [129] , mutta kaikki nämä ominaisuudet eivät ole vakiomuotoisia, mikä luonnollisesti rajoittaa niiden käyttöä. .
Tehottomat ja vaaralliset merkkijonotKielelle nollapääteiset merkkijonot ovat vakiona, joten kaikki vakiofunktiot toimivat niiden kanssa. Tämä ratkaisu johtaa merkittävään tehokkuuden heikkenemiseen vähäisistä muistinsäästöistä (verrattuna koon nimenomaiseen tallentamiseen): merkkijonon pituuden (funktio ) laskeminen vaatii silmukan koko merkkijonon läpi alusta loppuun, myös merkkijonojen kopiointi on vaikeaa. optimoida päättävän nollan läsnäolon vuoksi [48] . Koska merkkijonotietoihin on lisättävä päättävä nolla, on mahdotonta saada tehokkaasti osamerkkijonoja viipaleina ja työskennellä niiden kanssa kuten tavallisten merkkijonojen kanssa; merkkijonojen osien varaaminen ja käsitteleminen vaatii yleensä manuaalista muistin varaamista ja purkamista, mikä lisää edelleen virheiden mahdollisuutta. strlen()
Nollapääteiset merkkijonot ovat yleinen virhelähde [130] . Edes standardifunktiot eivät yleensä tarkista kohdepuskurin kokoa [130] eivätkä välttämättä lisää tyhjää merkkiä [131] merkkijonon loppuun , puhumattakaan siitä, että sitä ei ehkä lisätä tai ylikirjoittaa ohjelmointivirheen vuoksi. [132] .
Variadic-funktioiden vaarallinen toteutusVaikka C tukee funktioita, joissa on vaihteleva määrä argumentteja , se ei tarjoa keinoa määrittää sellaiselle funktiolle siirrettyjen todellisten parametrien määrää ja tyyppejä, eikä mekanismia niiden turvalliseen käyttöön [133] . Ohjelmoijan tehtävänä on ilmoittaa funktiolle todellisten parametrien koostumuksesta, ja niiden arvoihin pääsemiseksi on tarpeen laskea oikea tavumäärä pinon viimeisen kiinteän parametrin osoitteesta joko manuaalisesti tai käyttämällä joukkoa makroja va_argotsikkotiedostosta stdarg.h. Samanaikaisesti on otettava huomioon automaattisen implisiittisen tyypin edistämisen mekanismin toiminta funktioita kutsuttaessa [134] , jonka mukaan kokonaislukutyyppiset argumentit, jotka ovat pienempiä kuin int, heitetään kohtaan int(tai unsigned int), mutta floatheitetään osoitteeseen double. Virhe kutsussa tai funktion sisällä olevien parametrien kanssa tulee näkyviin vain ohjelman suorituksen aikana, mikä johtaa arvaamattomiin seurauksiin virheellisten tietojen lukemisesta pinon korruptoitumiseen.
printf()Samaan aikaan funktiot, joissa on vaihteleva määrä parametreja ( ja scanf()muita), jotka eivät pysty tarkistamaan, vastaako argumenttiluettelo muotomerkkijonoa , ovat muotoillun I/O:n vakiokeino . Monet nykyaikaiset kääntäjät suorittavat tämän tarkistuksen jokaiselle kutsulle ja luovat varoituksia, jos he löytävät ristiriidan, mutta yleensä tämä tarkistus ei ole mahdollista, koska jokainen variadic-funktio käsittelee tätä luetteloa eri tavalla. Edes kaikkia funktiokutsuja on mahdotonta ohjata staattisesti, printf()koska muotomerkkijono voidaan luoda dynaamisesti ohjelmassa.
Virheenkäsittelyn yhtenäisyyden puuteC-syntaksi ei sisällä erityistä virheenkäsittelymekanismia. Vakiokirjasto tukee vain yksinkertaisimpia keinoja: otsikkotiedostosta tulevaa muuttujaa ( POSIX :n tapauksessa makroa) viimeisen virhekoodin asettamiseksi ja toimintoja, jotka saavat koodien mukaiset virheilmoitukset. Tämä lähestymistapa johtaa tarpeeseen kirjoittaa suuri määrä toistuvaa koodia sekoittaen pääalgoritmin virheenkäsittelyyn, ja lisäksi se ei ole säikeen turvallista. Lisäksi edes tässä mekanismissa ei ole yhtä järjestystä: errnoerrno.h
Vakiokirjastossa koodit errnomääritetään makromäärittelyillä ja niillä voi olla samat arvot, mikä tekee virhekoodien analysoinnin operaattorin kautta mahdottomaksi switch. Kielellä ei ole erityistä tietotyyppiä lipuille ja virhekoodeille, ne välitetään tyypin arvoina int. Erillinen tyyppi errno_tvirhekoodin tallentamiseen ilmestyi vain C11-standardin K-laajennuksessa, eikä kääntäjät välttämättä tue sitä [87] .
C:n puutteet ovat olleet hyvin tiedossa jo pitkään, ja kielen perustamisesta lähtien on yritetty parantaa C-koodin laatua ja turvallisuutta sen ominaisuuksista tinkimättä.
Keinot koodin oikeellisuuden analysointiinLähes kaikki nykyaikaiset C-kääntäjät mahdollistavat rajoitetun staattisen koodin analyysin ja varoittavat mahdollisista virheistä. Vaihtoehtoja tuetaan myös taulukon rajojen ulkopuolella, pinon tuhoutumisen, keon ulkopuolella olevien rajoitusten, alustamattomien muuttujien, määrittelemättömän toiminnan jne. tarkistusten upottamiseen koodiin. Lisätarkistukset voivat kuitenkin vaikuttaa lopullisen sovelluksen suorituskykyyn, joten ne ovat käytetään useimmiten vain virheenkorjausvaiheessa.
On olemassa erityisiä ohjelmistotyökaluja C-koodin staattiseen analysointiin ei-syntaksivirheiden havaitsemiseksi. Niiden käyttö ei takaa ohjelmien virheettömyyttä, mutta mahdollistaa merkittävän osan tyypillisistä virheistä ja mahdollisista haavoittuvuuksista tunnistamisen. Näiden työkalujen suurin vaikutus ei saavuteta satunnaisella käytöllä, vaan kun niitä käytetään osana vakiintunutta jatkuvan koodin laadunvalvontajärjestelmää, esimerkiksi jatkuvassa integraatio- ja käyttöönottojärjestelmissä. Koodi voi myös olla tarpeen merkitä erityisillä kommenteilla, jotta analysaattorin väärät hälytykset voidaan sulkea pois oikeista koodin osista, jotka muodollisesti kuuluvat virheellisten kriteerien piiriin.
Suojatut ohjelmointistandarditOikeasta C-ohjelmoinnista on julkaistu huomattava määrä tutkimusta pienistä artikkeleista pitkiin kirjoihin. C-koodin laadun ylläpitämiseksi otetaan käyttöön yritys- ja teollisuusstandardeja. Erityisesti:
POSIX -standardisarja kompensoi joitain kielen puutteita . Asennus on standardisoitu errnomonilla toiminnoilla, mikä mahdollistaa esimerkiksi tiedostotoimintojen virheiden käsittelyn ja joidenkin standardikirjaston toimintojen säikeen turvallisia analogeja esitellään, joiden turvalliset versiot ovat kielistandardissa vain K-laajennus [137] .
Sanakirjat ja tietosanakirjat | ||||
---|---|---|---|---|
|
Ohjelmointikielet | |
---|---|
|
C-ohjelmointikieli | |
---|---|
Kääntäjät |
|
Kirjastot | |
Erikoisuudet | |
Jotkut jälkeläiset | |
C ja muut kielet |
|
Luokka: C-ohjelmointikieli |