A C programozási nyelv szintaxisa a szoftver C nyelven történő írására vonatkozó szabályok összessége. Úgy tervezték, hogy lehetővé tegye a tömör kódot, az eredményül kapott tárgykóddal szoros kapcsolatban álló, ugyanakkor viszonylag magas szintű adatabsztrakciót biztosító programokat. A C volt az első széles körben sikeres magas szintű nyelv a hordozható operációs rendszerek fejlesztéséhez.
A C-szintaxis a maximális rágcsálás elvét használja. Szabad formátumú nyelvként a C kód különböző módon formázható anélkül, hogy befolyásolná szintaktikai jellegét. A C szintaxis befolyásolta a későbbi nyelvek, köztük a C++, a Java és a C# szintaxisát.
A C kód előfeldolgozói direktívákból, valamint a magnyelvi típusokból, változókból és függvényekből áll, amelyek egy vagy több forrásfájlba vannak rendszerezve. A kód felépítése jellemzően előfeldolgozást, majd az egyes forrásfájlok objektumfájlba fordítását foglalja magában. Ezután az objektumfájlokat összekapcsolják egy futtatható kép létrehozása érdekében.
A változók és függvények deklarálhatók a definíciójuktól függetlenül. A deklaráció azonosítja a felhasználó által definiált elem nevét, valamint néhány, ha nem az összes információt arról, hogyan használható az elem futásidejűleg. A definíció egy elem teljes leírása, amely magában foglalja a deklarációs aspektust, valamint további információkat, amelyek kiegészítik az elemet. Például egy függvénydeklaráció jelzi a nevet, és opcionálisan az általa elfogadott argumentumok típusát és számát. A függvénydefiníció ugyanazokat az információkat tartalmazza (az argumentuminformáció nem opcionális), plusz a függvénylogikát megvalósító kódot.
Egy hosztolt környezetben a program egy main nevű belépési pont-függvénynél indul. A függvény két argumentumot kap, bár a függvény egy implementációja figyelmen kívül hagyhatja ezeket. A függvényt a következő prototípusok egyike szerint kell deklarálni (a bemutatott paraméternevek tipikusak, de bármi lehet):
int main();
int main(void);
int main(int argc, char* argv[]);
int main(int argc, char** argv);
int main(void);
int main(int argc, char* argv[], char* envp[]);
int main(int argc, char** argv, char** envp);
int main(int argc, char* argv[], char** envp);
int main(int argc, char** argv, char* envp[]);
Az első két definíció ekvivalens, ami azt jelenti, hogy a függvény nem használja a két argumentumot. A második kettő szintén ekvivalens, lehetővé téve a függvény számára a parancssori argumentumok olvasásához szükséges két argumentum elérését. Az utolsó négy szintén ekvivalens, lehetővé téve a környezeti változók és a parancssori argumentumok elérését.
Az int típusú visszatérési érték állapotjelzőként szolgál a gazdagép számára. Az <stdlib.h> fájlban definiált standard könyvtár makrókat biztosít a szabványos állapotértékekhez: EXIT_SUCCESS és EXIT_FAILURE. Ettől függetlenül egy program bármilyen értékkel jelezheti az állapotot. Például a kill parancs a plusz 128 numerikus signal értékét adja vissza.
Egy minimális program egy üres main függvényből áll, például:
int main() {}
Más függvényekkel ellentétben a nyelv megköveteli, hogy a program úgy viselkedjen, mintha 0-t adna vissza, még akkor is, ha nem return utasítással végződik.
Egy free-standing (nem hosztolt) környezetben, például egy operációs rendszer nélküli rendszerben a szabvány eltérő indítási kezelést tesz lehetővé. Nem kell main függvényt igényelnie.
A program indításához a parancssorban megadott paraméterek két értékként — előre definiált változóval — kerülnek átadásra a programnak – az argumentumok száma (argc) és egy nullával lezárt karakterláncok tömbje (argv), amelynek első eleme a program neve. Tehát a parancs:
myFilt p1 p2 p3
valami ilyesmit eredményez:
| m | y | F | i | l | t | \0 | p | 1 | \0 | p | 2 | \0 | p | 3 | \0 |
| argv[0] | argv[1] | argv[2] | argv[3] | ||||||||||||
Míg az egyes karakterláncok összefüggő karakterek tömbjei, nincs garancia arra, hogy a karakterláncok összefüggő csoportként kerülnek tárolásra.
A program neve, argv[0], hasznos lehet diagnosztikai üzenetek kinyomtatása során, vagy ha egy bináris program több célt is szolgálhat. A paraméterek egyes értékeit az argv[1], argv[2] és argv[3] paraméterekkel érhetjük el, ahogy az alábbi programban látható, a kód kinyomtatja a parancssori paraméterek értékét:
#include <stdio.h>
int main(int argc, char* argv[]) {
printf("argc\t= %d\n", argc);
for (int i = 0; i < argc; ++i) {
printf("argv[%i]\t= %s\n", i, argv[i]);
}
}
$ ./a.out abc def
argc = 3
argv[0] = ./a.out
argv[1] = abc
argv[2] = def
A következő kulcsszavak fenntartottak, és nem használhatók azonosítóként:
alignasalignofautoboolbreakcasecharconstconstexprcontinuedefaultdodoubleelseenumexternfalsefloatforgotoifinlineintlongnullptrregisterrestrictreturnshortsignedsizeofstaticstatic_assertstructswitchthread_localtruetypedeftypeoftypeof_unqualunionunsignedvoidvolatilewhileA következő kulcsszavakat gyakran egy makró vagy a fenti listából származó megfelelő kulcsszó helyettesíti. Az alábbi kulcsszavak némelyike a C23 óta elavult.
_Alignas_Alignof_Atomic_BitInt_Bool_Complex_Decimal32_Decimal64_Decimal128_Generic_Imaginary_Noreturn_Static_assert_Thread_localAz implementációk más kulcsszavakat is lefoglalhatnak, bár általában nem szabványos kulcsszavakat biztosítanak, amelyek egy vagy két aláhúzásjellel kezdődnek. A következő kulcsszavak kiterjesztésekként vannak besorolva, és feltételesen támogatottak:
asmfortranA következő tokeneket ismeri fel az előfeldolgozó a preprocesszor direktívák kontextusában.
#if#elif#else#endif#ifdef#ifndef#elifdef#elifndef#define#undef#include#embed#line#error#warning#pragmadefined (feltételes utasítást követ)#__has_include#__has_embed#__has_c_attributeA _Pragma tokent a preprocesszor az előfeldolgozói direktívák kontextusán kívül is felismeri.
A következő makrókat gyakran használják mindenütt a C nyelvben:
NULL (gyakran (void*)0-ként definiálják, a nullptr bevezetése előtt használták null pointer ábrázolására)NDEBUG (egy makró, aminek jelentése no-debug – „nincs hibakeresés”, elsősorban az assert() hívások figyelmen kívül hagyásának szabályozására szolgál)assert() (egy argumentumokkal rendelkező makró, amely leállítja a programot, ha az argumentum false, 0 vagy nullptr értéket ad ki, és az NDEBUG segítségével letiltható)A megjegyzés – egy programozó számára informatív szöveg, amelyet a nyelvi fordító figyelmen kívül hagy – sor- vagy blokkmegjegyzés-szintaxisként is beilleszthető a kódba. A sormegjegyzés *// karakterrel kezdődik és ugyanazon sor végén végződik. A blokkmegjegyzés /* karakterrel kezdődik és */ karakterrel végződik – tetszőleges számú sort (vagy csak egyet) felölelve.
Bizonyos esetekben a megjegyzésjelzőket a rendszer figyelmen kívül hagyja. A karakterlánc literál szövege mentesül a megjegyzés kezdetének tekintésétől. Ezenkívül a megjegyzések nem ágyazhatók be egymásba. Például a /* egy soros megjegyzésben nem úgy tekintendő, mint egy blokk megjegyzés kezdete. És a *// egy blokk megjegyzésben nem tekintendő egy soros megjegyzés kezdetének.
A soros megjegyzések (a // karakterrel kezdődnek, és a sor végéig terjednek) szintaxisa, amelyet néha C++ stílusnak is neveznek, a BCPL-ből származik, és a C99-ben vált érvényes szintaxissá. Nem érhető el az eredeti K&R C verzióban, sem az ANSI C-ben.
A következő kód a megjegyzéseket mutatja be. Az 1. sor egy soros megjegyzést, a 3-4. sor pedig egy blokkos megjegyzést tartalmaz. A 4. sor azt mutatja be, hogy egy blokkos megjegyzés beágyazható egy sorba, amelynek előtte és utána is van kód.
int i; // line comment
/* block
comment */
int ii = /* always zero */ 0;
A következő példa egy lehetséges problémát mutat be a megjegyzés szintaxissal. Aminek az osztási operátornak /, majd a hivatkozás eltávolítására szolgáló operátornak * kellene lennie, az egy blokk megjegyzés kezdeteként lesz kiértékelve.
x = *p/*q;
A következő szöveg nem érvényes C szintaxisú, mivel a megjegyzések nem ágyazódnak be. Úgy tűnik, hogy a 3-5. sorok egy beágyazott megjegyzést jelentenek az 1-7. sorokat átívelő megjegyzésblokkba. Valójában azonban az 5. sor az 1. sorban kezdődő megjegyzést zárja le. Ez azt jelenti, hogy a 6. sort kódként kell értelmezni, ami egyértelműen nem érvényes C szintaxisú.
/*
First of comment block
/*
First line of what is intended to be an inner block
*/
Compiler treats this line as code but it's not valid!
*/
A szintaxis támogatja a felhasználó által definiált azonosítókat. Egy azonosítónak betűvel (A-Z, a-z) vagy aláhúzásjellel (_) kell kezdődnie, a további karakterek lehetnek betűk, számok (0-9) vagy aláhúzásjelek, és nem lehet foglalt szó. Az azonosítók megkülönböztetik a kis- és nagybetűket, így a foo, FOO, és Foo megkülönböztethetőek.
Bármilyen ésszerűen összetett kifejezésben lehetőség van arra, hogy a kifejezés részeit milyen sorrendben értékeljük ki: az (1+1)+(3+3) értékelhető az (1+1)+(3+3), (2)+(3+3), (2)+(6), (8), vagy az (1+1)+(3+3), (1+1)+(6), (2)+(6), (8) sorrendben. Formálisan egy megfelelő C fordító bármilyen sorrendben kiértékelheti a kifejezéseket a szekvenciapontok között (ez lehetővé teszi a fordító számára, hogy optimalizálást végezzen). A szekvenciapontokat a következők határozzák meg:
&&) és logikai vagy (||).? :): Ez az operátor először az első részkifejezését értékeli ki, majd a másodikat vagy harmadikat (soha mindkettőt) az első értéke alapján.A sorozatpont előtti kifejezések mindig a sorozatpont utáni kifejezések előtt kerülnek kiértékelésre. Rövidzárlati kiértékelés esetén előfordulhat, hogy a második kifejezés nem kerül kiértékelésre az első kifejezés eredményétől függően. Például az (a() || b()) kifejezésben, ha az első argumentum értéke nem nulla (igaz), a teljes kifejezés eredménye nem lehet más, mint igaz, így a b() nem kerül kiértékelésre. Hasonlóképpen, az (a() && b()) kifejezésben, ha az első argumentum nullára (false) értékelődik ki, a teljes kifejezés eredménye nem lehet más, mint hamis, így a b() nem kerül kiértékelésre.
A függvényhívás argumentumai tetszőleges sorrendben kiértékelhetők, feltéve, hogy a függvénybe való belépéskor már mind kiértékelésre kerültek. A következő kifejezés például meghatározatlan viselkedést mutat:
printf("%s %s\n", argv[i = 0], argv[++i]);
A kód más fájlokból az #include direktíva használatával illeszthető be, amely szövegesen másolja a fájl tartalmát a helyére. Hagyományosan a C kód egy header fájlra (.h kiterjesztéssel) és egy forrásfájlra (.c kiterjesztéssel) oszlik. A header tartalmazza a szimbólumok deklarációit, míg a forrásfájl a teljes implementációt. Ez az elválasztás azért van érvényben, hogy megakadályozza, hogy a fordító minden alkalommal újrafordítsa ugyanazt a forráskódot, amikor egy könyvtárat beilleszt, ami a fordítási idő megnövekedését okozza. A fejlécek és a különálló fordítási egységek nem ismerik a névtereket, ami azt jelenti, hogy minden szimbólumnév globális, és ütközni fog, ha ugyanaz a név több objektumhoz is létezik ugyanabban a fordítási egységben.
Annak megakadályozására, hogy egy fejléc többször is bekerüljön egy fájlba, az #include guard vagy a #pragma once használható.
A C fejlécek előre lefordított fejlécekké fordíthatók, amelyeket a fordító gyorsabban feldolgoz. A ritkán változó könyvtárakat (például a C szabványos könyvtár-fejléceket) jellemzően előre le lehet fordítani a projektben történő gyorsabb fordítás érdekében.
A beillesztési könyvtárban és a relatív elérési úton történő keresés megkülönböztetéséhez használjon szögletes zárójelet a beillesztési könyvtárakhoz, és idézőjeleket a relatív elérési utakhoz.
#pragma once
// standard library includes
#include <stdio.h>
#include <stdlib.h>
// local
#include "Personalities.h"
#include "Math/SpecialFunctions.h"
// external libraries
#include <sqlite3.h>
(angolul)
A Clang egy nem szabványos funkciót kínál, a modulokat, amelyek hasonlóak a C++ modulokhoz, de szemantikailag eltérőek. Mindkettő a fordítási idő növelésére szolgál azáltal, hogy egy fordítási egységet egyszer fordít le.
Az #embed direktíva segítségével bináris tartalmat lehet beágyazni egy fájlba, még akkor is, ha az nem érvényes C kód.
constexpr char ICON_DISPLAY_DATA[] = {
#embed "art.png"
};
// specify any type which can be initialized form integer constant expressions will do
constexpr char RESET_BLOB[] = {
#embed "data.bin"
};
// attributes work just as well
alignas(8) constexpr char ALIGNED_DATA_STRING[] = {
#embed "attributes.xml"
};
int main() {
return
#embed </dev/urandom> limit(1)
;
}
A C programozási nyelv a számokat három formában ábrázolja: egész, valós és komplex. Ez a megkülönböztetés hasonló különbségeket tükröz a legtöbb központi feldolgozó egység utasításkészlet-architektúrájában. Az egész (integer) adattípusok a számokat egész számok halmazában tárolják, míg a valós és komplex számok számokat (vagy számpárokat) képviselnek a valós számok halmazában lebegőpontos formában.
Minden C egész típusnak vannak előjeles (signed) és előjel nélküli (unsigned) változatai. Ha a signed vagy unsigned nincs kifejezetten megadva, a legtöbb esetben a rendszer signed-nek feltételezi. Történelmi okokból azonban a sima char az előjeles és az előjel nélküli karakterektől eltérő típus. Lehet előjeles (signed char) vagy előjel nélküli (unsigned char) típus, a fordítótól és a karakterkészlettől függően (A C garantálja, hogy a C alapkarakterkészlet tagjai pozitív értékkel rendelkeznek). Ezenkívül a sima int-ként megadott bitmező típusok a fordítótól függően lehetnek előjelesek vagy előjel nélküliek.
A C egész számtípusai különböző rögzített méretűek, és különböző számtartományokat képesek reprezentálni. A char típus pontosan egy bájtot foglal el (a legkisebb címezhető tárolóegység), ami jellemzően 8 bit széles. (Bár a char képviselheti a C bármely "alap" karakterét, a nemzetközi karakterkészletekhez szélesebb típusra lehet szükség.) A legtöbb egész típusnak van előjeles és előjel nélküli változata is, amelyeket az signed és unsigned kulcsszavak jelölnek. Az elõjeles egész típusok mindig a kettes komplemens reprezentációt használják, a C23 óta (és a gyakorlatban korábban is; a régebbi C verziókban a C23 elõtt lehet, hogy az ábrázolás egyes komplemens, vagy előjel-nagyság volt, de a gyakorlatban ez a modern hardvereken évtizedek óta nem volt így). Sok esetben több egyenértékű mód létezik a típus megjelölésére; például a signed short int és short szinonimák.
Egyes típusok ábrázolása tartalmazhat fel nem használt "padding" biteket, amelyek tárhelyet foglalnak el, de nem szerepelnek a szélességben. Az alábbi táblázat a szabványos egész típusok teljes listáját tartalmazza és minimális megengedett szélességüket (beleértve az előjelbiteket is).
| A legrövidebb forma | Minimális szélesség (bit) |
|---|---|
_Bool |
1 |
char |
8 |
signed char |
8 |
unsigned char |
8 |
short |
16 |
unsigned short |
16 |
int |
16 |
unsigned int |
16 |
long |
32 |
unsigned long |
32 |
long long |
64 |
unsigned long long |
64 |
A char típus különbözik mind a signed char, mind az unsigned char karakterektől, de garantáltan megegyezik az egyikükkel. A _Bool és long long típusok 1999 óta szabványosak, és előfordulhat, hogy a régebbi C fordítók nem támogatják őket. A _Bool típus általában az stdbool.h szabványos fejléc-fájl által meghatározott bool typedef néven keresztül érhető el, azonban a C23 óta a _Bool típust átnevezték boolra, a <stdbool.h> pedig elavult.
Általánosságban elmondható, hogy az adott platformon megvalósított adatszélességek és ábrázolási sémák a gép architektúrája alapján kerülnek kiválasztásra, figyelembe véve a más platformokhoz kifejlesztett forráskódok importálásának egyszerűségét. Az int típus szélessége különösen nagymértékben változik a C implementációk között; gyakran megfelel az adott platform legtermészetesebb szóméretének. A limits.h szabványos fejléc makrókat definiál a szabványos egész típusok minimális és maximális reprezentálható értékéhez bármely adott platformon megvalósítva.
A szabványos egész típusokon kívül létezhetnek más "bővített" (extended) egész típusok is, amelyek a szabványos fejlécekben lévő typedef meghatározásokhoz használhatók. A szélesség pontosabb meghatározásához a programozók használhatják és kell is használni a typedefeket a szabványos stdint.h fejlécből.
Az egész konstansok többféleképpen is megadhatók a forráskódban. A numerikus értékek megadhatók decimális (példa: 1022), oktális nulla (0) előtaggal (01776), vagy hexadecimális 0x (nulla x) előtaggal (0x3FE). Egy idézőjelben lévő karakter (például: 'R'), amelyet "karakter konstansnak" neveznek, az adott karakter értékét jelenti a végrehajtási karakterkészletben, int típussal. A karakterkonstansok kivételével az egész konstans típusát a megadott érték megjelenítéséhez szükséges szélesség határozza meg, de az mindig legalább olyan széles, mint az int. Ez felülírható egy explicit hossz- és/vagy előjel-módosító hozzáadásával; például a 12lu unsigned long típusú. Nincsenek negatív egész állandók, de gyakran ugyanaz a hatás érhető el, ha egy "-" unáris negációs operátort használunk.
A felsorolt típus C-ben, amelyet az enum kulcsszó határoz meg, és gyakran csak "enum"-nak nevezik, egy olyan típus, amelyet úgy terveztek, hogy az értékeket nevesített konstansok sorozatán keresztül reprezentálja. A felsorolt konstansok mindegyike int típusú. Mindegyik enum-típus önmagában kompatibilis a char-ral vagy egy előjeles vagy előjel nélküli egész típussal, de minden megvalósítás meghatározza a saját szabályait a típus kiválasztásához.
Egyes fordítók figyelmeztetnek, ha egy felsorolt típusú objektumhoz olyan értéket rendelnek, amely nem tartozik az állandók közé. Azonban egy ilyen objektumhoz bármilyen érték hozzárendelhető a kompatibilis típusuk tartományában, és az enum-konstansok bárhol használhatók, ahol egész szám várható. Emiatt gyakran enum értékeket használnak az előfeldolgozó #define direktívák helyett a nevesített konstansok létrehozásához. Az ilyen konstansok használata általában biztonságosabb, mint a makrók, mivel egy adott azonosító névtérben találhatók.
A felsorolt típust az enum megadásával és az enum opcionális nevével (vagy címkéjével) deklarálják, ezt követi a kapcsos zárójelben lévő és vesszővel elválasztott egy vagy több állandó listája, valamint a változónevek opcionális listája. Egy adott felsorolt típusra történő későbbi hivatkozások az enum kulcsszót és az enum nevét használják. Alapértelmezés szerint a felsorolás első konstansához nulla érték rendelődik, és minden további érték eggyel nő az előző állandóhoz képest. A deklarációban szereplő konstansokhoz specifikus értékek is hozzárendelhetők, és minden további, konkrét érték nélküli konstans ettől a ponttól kezdve növelt értékeket kap. Vegyük például a következő deklarációt:
enum colors { RED, GREEN, BLUE = 5, YELLOW } paint_color;
avagy:
enum Color {
RED,
GREEN,
BLUE = 5,
YELLOW
} paint_color;
Ez deklarálja az enum colors típusát; az int konstansok RED (amelynek értéke 0), GREEN (amelynek értéke eggyel nagyobb, mint a RED, 1), BLUE (amelynek értéke a megadott érték, 5) és YELLOW (amelynek értéke eggyel nagyobb, mint a BLUE, 6); és az enum colors paint_color változója. A konstansok az enum kontextusán kívül is használhatók (ahol bármely egész érték megengedett), és a konstansoktól eltérő értékeket is hozzá lehet rendelni a paint_color-hoz vagy bármely más enum colors típusú változóhoz.
A C++-tól eltérően a C enumok nem rendelkeznek hatókörrel, mivel a C nem használja a névtereket. A C-ben az enumok implicit módon numerikus típussá alakíthatók, ami típusbiztonságot nem biztosít.
typedef enum Color {
RED,
ORANGE,
YELLOW,
GREEN,
BLUE,
INDIGO,
VIOLET
} Color;
Color c = RED; // in C
Color d = Color::RED; // in C++, but not in C
A C23 óta lehetőség van manuálisan megadni az enum mögöttes típusát, hasonlóan a C++-hoz.
enum CardSuit: char {
HEARTS,
CLUBS,
SPADES,
DIAMONDS
};
A lebegőpontos forma törtkomponensű számok ábrázolására szolgál. Ezek azonban nem reprezentálják pontosan a legtöbb racionális számot; ehelyett szoros közelítések. A valós értékeknek három szabványos típusa van, amelyeket specifikációik jelölnek (és a C23 szabvány óta még három decimális típus): egyszeres pontosság (float), kettős pontosság (double) és dupla kiterjesztett pontosság (long double). Ezek mindegyike más-más formában képviselheti az értékeket, gyakran az IEEE lebegőpontos formátumok egyikét.
| Típusmeghatározók | Pontosság (tizedes számjegyek) | Kitevő tartomány | ||
|---|---|---|---|---|
| Minimum | IEEE 754 | Minimum | IEEE 754 | |
float |
6 | 7.2 (24 bit) | ±37 | ±38 (8 bit) |
double |
10 | 15.9 (53 bit) | ±37 | ±307 (11 bit) |
long double |
10 | 34.0 (113 bit) | ±37 | ±4931 (15 bit) |
A lebegőpontos állandók decimális jelöléssel írhatók fel, pl. 1.23. A tizedes tudományos jelölés használható e vagy E hozzáadásával, majd egy decimális kitevővel, más néven E-jelöléssel, pl. 1.23e2 (amelynek értéke 1,23 × 102 = 123,0). Vagy egy tizedesvesszőre, vagy egy kitevőre van szükség (ellenkező esetben a szám egész konstansként értelmeződik). A hexadecimális lebegőpontos konstansok hasonló szabályokat követnek, azzal a különbséggel, hogy 0x előtaggal kell rendelkezniük, és p vagy P-t kell használni egy bináris kitevő megadására, pl. 0xAp-2 (amelynek értéke 2,5, mivel Ah × 2−2 = 10 × 2−2 = 10 ÷ 4). Mind a decimális, mind a hexadecimális lebegőpontos konstansok utótagja lehet f vagy F a float típusú konstans jelzésére, l (l betű) vagy L a long double típus jelzésére, vagy utótag nélkül hagyható double konstans esetén.
A float.h szabványos fejlécfájl határozza meg a megvalósítás float, double, és long double lebegőpontos típusainak minimális és maximális értékét. Más határértékeket is meghatároz, amelyek a lebegőpontos számok feldolgozása szempontjából relevánsak.
A C23 három további decimális (a binárissal szemben) valós lebegőpontos típust vezet be: _Decimal32, _Decimal64 és _Decimal128.
float, double, és long double értékekhez. Egy implementáció kiválaszthatja a float, double, és long double ábrázolását úgy, hogy megegyezzen a decimális lebegőpontos típusokkal.Ennek ellenére a radix történelmileg bináris volt (2-es alapú), ami azt jelenti, hogy az olyan számok, mint az 1/2 vagy 1/4 pontosak, de az 1/10, 1/100 vagy 1/3 már nem. A decimális lebegőpontos számok mindegyike pontos pozitív szám, például 1/10 és 1/100, de pl. az 1/3 mégsem. Egyetlen ismert megvalósítás sem választja a decimális radixot a bináris típusként ismert típusok esetében. Mivel a legtöbb számítógépen nincs is hardvere a decimális típusokhoz, és azok a kevesek, amelyek igen (pl. IBM nagyszámítógépek az IBM System z10 óta), használhatják a kifejezetten decimális típusokat.
Minden objektumnak van tárolási osztálya. Ez alapvetően a tárolási időtartamot határozza meg, amely lehet statikus (alapértelmezett a globális), automatikus (alapértelmezett lokális) vagy dinamikus (allokált).
| Azonosítók | Élettartam | Hatály | Alapértelmezett inicializátor |
|---|---|---|---|
auto |
Block (stack) | Block | Uninitialized |
register |
Block (stack or CPU register) | Block | Uninitialized |
static |
Program | Block or compilation unit | Zero |
extern |
Program | Global (entire program) | Zero |
_Thread_local |
Thread | ||
| (none)1 | Dynamic (heap) | Uninitialized (initialized to 0 if using calloc()) |
malloc() és free() könyvtári függvények használatával.Az alapértelmezés szerint egy blokkon belül deklarált változók automatikus tárolással rendelkeznek, csakúgy, mint az auto vagy register tárolási osztály specifikációival kifejezetten deklarált változók. Az auto és a register specifikátorok csak függvényekben és függvényargumentum-deklarációkban használhatók; mint ilyen, az auto specifikáció mindig redundáns. Az összes blokkon kívül deklarált objektumok és a static tárolási osztály-meghatározóval kifejezetten deklarált objektumok statikus tárolási időtartammal rendelkeznek. A statikus változókat alapértelmezés szerint nullára inicializálja a fordító.
Az automatikus tárolással rendelkező objektumok abban a blokkban lokálisak, amelyben deklarálták őket, és a blokkból való kilépéskor megszűnnek. Ezenkívül a register tárolási osztályával deklarált objektumoknak a fordító magasabb prioritást adhat a regiszterekhez való hozzáféréshez; bár a fordító dönthet úgy, hogy valójában egyiket sem tárolja regiszterben. Az ilyen tárolási osztályú objektumok nem használhatók az (&) unáris operátor címével. A statikus tárolással rendelkező objektumok a program teljes időtartama alatt megmaradnak. Ily módon ugyanazt az objektumot egy függvény több híváson keresztül is elérheti. A hozzárendelt tárolási időtartamú objektumok kifejezetten malloc, free és kapcsolódó funkciókkal jönnek létre és semmisülnek meg.
Az extern tárolási osztály-meghatározó azt jelzi, hogy egy objektum tárolója máshol van meghatározva. Ha egy blokkon belül használják, akkor azt jelzi, hogy a tárolót a blokkon kívüli deklaráció határozta meg. Ha az összes blokkon kívül használjuk, akkor azt jelzi, hogy a tároló a fordítási egységen kívül lett meghatározva. Az extern tárolási osztály-meghatározó redundáns, ha függvénydeklarációban használják. Azt jelzi, hogy a deklarált függvény a fordítási egységen kívül lett meghatározva.
A C11-ben bevezetett _Thread_local (thread_local a C++-ban, és a C-ben C23 óta, valamint a C korábbi verzióiban, ha a <threads.h> fejléc is benne van) tárolási osztály-specifikáció egy thread-local változó deklarálására szolgál. Kombinálható static vagy extern-nel a kapcsolat meghatározásához.
Vegye figyelembe, hogy a tárolási specifikációk csak a függvényekre és objektumokra vonatkoznak; egyéb dolgok, például a típus- és enum-deklarációk annak a fordítási egységnek a sajátja, amelyben megjelennek. A típusoknak viszont vannak minősítői (lásd alább).
A C23 óta a C nyelvben az auto használható típuskövetkeztetett változók deklarálására.
(angolul)
A típusok minősíthetők adataik speciális tulajdonságainak jelzésére. A const típusminősítő azt jelzi, hogy az érték nem változik az inicializálás után. A const minősítésű érték módosításának kísérlete definiálatlan viselkedést eredményez, ezért egyes C fordítók rodata-ban vagy (beágyazott rendszerek esetén) csak olvasható memóriában (ROM) tárolják azokat. Hasonlóképpen, a constexpr a const egy "erősebb" formájának tekinthető, ahol az értéknek fordítási időben ismertnek kell lennie (így típusbiztosan helyettesítheti a makrókonstansokat). Egy constexpr függvénynek hasonlóképpen szintén ki kell tudnia értékelni fordítási időben. A volatile típusminősítő azt jelzi az optimalizáló fordítónak, hogy nem távolíthatja el a látszólag redundáns olvasásokat vagy írásokat, mivel az érték akkor is változhat, ha nem módosították semmilyen kifejezéssel vagy utasítással, vagy többszöri írásra lehet szükség, például memórialeképezett I/O esetén.
A hiányos típus (incomplete type) olyan struktúra vagy unió típus, amelynek tagjai még nincsenek megadva, olyan tömbtípus, amelynek dimenziója még nincs megadva, vagy void (üres) típus (a void típus nem fejezhető be). Egy ilyen típust nem lehet példányosítani (a mérete nem ismert), a tagjait sem lehet elérni (ezek is ismeretlenek); azonban a származtatott mutatótípus használható (de nem hivatkozva).
Gyakran használják mutatókkal, akár forward, akár external deklarációként. Például a kód deklarálhat egy hiányos típust:
struct Integer* pt;
Ez deklarálja a pt-t mutatóként deklarálja az struct Integer típusra (valamint a hiányos struktúra típusra). Mivel minden mutató azonos méretű (függetlenül attól, hogy mire mutat), a kód használhatja a pt struktúrát mutatóként, bár nem férhet hozzá a struct Integer mezőihez.
Egy hiányos típus később, ugyanabban a hatókörben kiegészíthető újradeklarálással. Például:
struct Integer {
int num;
};
A hiányos típusokat rekurzív struktúrák megvalósítására használjuk; a típus-deklaráció törzse későbbre halasztható a fordítási egységben:
typedef struct Bert Bert;
typedef struct Wilma Wilma;
struct Bert {
Wilma* wilma;
};
struct Wilma {
Bert* bert;
};
A hiányos típusokat az adatok elrejtésére is használják; a hiányos típus egy fejlécfájlban van megadva, a törzs pedig csak a megfelelő forrásfájlban.
(angolul)
Egy változódeklarációban a csillag (*) jelentése „mutató erre” lehet. Például az int x egy int típusú változót deklarál, az int* px pedig egy (px változót, amely egy egész számra mutató mutató. Egyesek azt állítják, hogy a nyelvi definíció alapján a * szorosabban kapcsolódik a változóhoz, mint a típushoz, ezért a kódot int *px vagy akár int * px formátumban formázzák. Ez azért van, mert a csillag (*) a deklaráció része,[5] és nem a típusleíró; ennek egy másik következménye, hogy az int* a,b; két változót fog deklarálni úgy, hogy a egy egész számra mutató mutató, b pedig maga is egy egész szám.
A mutatóértékek két információt társítanak: egy memóriacímet és egy adattípust.
Amikor egy nem statikus mutatót deklarálnak, egy meghatározatlan érték van hozzá társítva. Az ilyen mutatóhoz társított címet hozzárendeléssel meg kell változtatni a használat előtt.
Az & operátor az utána következő adatobjektum címét adja meg. A következő példában a ptr operátorhoz az a címe lesz hozzárendelve:
int a = 0;
int *ptr = &a;
Ennek eléréséhez az "address-of" (unáris &) operátort használjuk.
Egy változó neve előtti csillag (ha nem deklarációban vagy matematikai kifejezésben szerepel) dereferenciál egy mutatót, hogy hozzáférést biztosítson a mutató által mutatott értékhez. A következő példában a b egész változó az a egész változó értékére van beállítva, ami 10:
int a = 10;
int* p;
p = &a;
int b = *p;
A tömbök a C nyelvben azonos típusú egymást követő elemeket tárolnak. A következő kód egy 100 elemből álló tömböt deklarál, amelynek a neve a, és amelynek a típusa int:
int array[100];
Ha egy függvényen kívül (globálisan) deklaráljuk, a méretnek konstans értéknek kell lennie. Ha egy függvényben deklaráljuk, a tömb mérete lehet nem konstans kifejezés.
Az elemek száma elérhető sizeof(a) / sizeof(int) formában, de ha az értéket egy másik függvénynek adjuk át, az elemek száma nem érhető el a formális paraméterváltozón keresztül.
A legtöbb esetben a későbbi használat során az array változó említése a tömb első elemére mutató mutatóvá alakul. A sizeof operátor kivétel: a sizeof array a teljes tömb méretét adja (vagyis egy int méretének 100-szorosa, és a sizeof(array) / sizeof(int) értéke 100). Egy másik kivétel az & (address-of) operátor, amely például egy mutatót ad a teljes tömbre:
int (*ptr_to_array)[100] = &array;
A tömb elemei értékeinek az eléréséhez elsődleges eszköz a tömb index-operátora. Például az a[i] az a tömb i indexű elemét éri el. A tömb indexének számozása 0-val kezdődik (nulla alapú indexelés). A tömb utolsó elemének indexe tehát egyenlő a tömb elemeinek számával mínusz 1. Mivel a szabvány nem biztosítja a tömbindexelési határok ellenőrzését, a tartományon kívüli index megadása nem definiált viselkedést eredményez.
Mivel a tömbök és a mutatók felcserélhetők, az egyes elemek címe mutatóaritmetikában kifejezhető. Az alábbi táblázat mindkét módszert bemutatja ugyanazon tömb meglévő esetére:
Mivel a tömbök és mutatók felcserélhetők, az egyes tömbelemek címei ekvivalens mutató aritmetikával fejezhetők ki. Az alábbi táblázat mindkét módszert szemlélteti ugyanazon meglévő tömb esetében:
| Elem | Első | Második | Harmadik | n-edik |
|---|---|---|---|---|
| Tömb indexe | array[0]
|
array[1]
|
array[2]
|
array[n - 1]
|
| Hivatkozott mutató | *array
|
*(array + 1)
|
*(array + 2)
|
*(array + n - 1)
|
Mivel az a[i] kifejezés szemantikailag ekvivalens a *(a + i)-vel, ami viszont egyenértékű a *(i + a)-val, a kifejezés i[a]-ként is felírható, bár ezt az alakot ritkán használják.
A C99 szabványosította a változó hosszúságú tömböt (variable-length array) a blokk hatókörében, amely egy futásidejű információk (nem állandó érték) által méretezett, de a blokk végéig rögzített méretű tömböt hozott létre. Az ilyen tömbváltozók a blokkba való belépéskor futásidejű integer érték alapján kerülnek kiosztásra, és a blokk végén feloldódnak. A C11-től kezdve ezt a szolgáltatást már nem kell megvalósítania a fordítónak.
int n = 20; // n can be any arbitrary value
int a[n];
a[3] = 10;
A C standard könyvtár segítségével dinamikusan átméretezhető tömbök állíthatók elő. A malloc függvény egyszerű módszert biztosít a memória lefoglalására. Egy paraméterre van szükség: a lefoglalandó memória mennyiségére bájtokban. Sikeres kiosztás esetén a malloc egy általános (void) mutatóértéket ad vissza, amely a lefoglalt terület elejére mutat. A visszaadott mutatóértéket hozzárendeléssel implicit módon a megfelelő típusra konvertálja. Ha az elosztás nem fejezhető be, a malloc null mutatót ad vissza. A következő részlet tehát funkciójában hasonló a fenti kívánt deklarációhoz:
#include <stdlib.h> /* declares malloc */
...
int *a = malloc(n * sizeof *a);
a[3] = 10;
Az eredmény egy "pointer to int" változó (a), amely az n összefüggő int objektum közül az elsőre mutat; a tömb-mutató ekvivalenciája miatt ez használható a tényleges tömbnév helyett, amint az az utolsó sorban látható. Ennek a dinamikus allokációnak az az előnye, hogy a hozzá lefoglalt memória mennyisége a futási időben a ténylegesen szükségesre korlátozható, és ez szükség szerint módosítható (a szabványos realloc könyvtári függvény segítségével).
Ha a dinamikusan lefoglalt memóriára már nincs szükség, azt vissza kell adni a futásidejű rendszernek. Ez a free funkció meghívásával történik. Egyetlen paraméter szükséges: egy mutató a korábban lefoglalt memóriára. Ez az az érték, amelyet a malloc korábbi hívása adott vissza.
Biztonsági intézkedésként néhány programozó ezután NULL-ra állítja a mutatóváltozót:
free(a);
a = NULL;
Ez biztosítja, hogy a legtöbb rendszeren a mutató hivatkozásának megszüntetésére irányuló további kísérletek a program összeomlásához vezetnek. Ha ez nem történik meg, a változó lógó mutatóvá válik, ami utólagos használat során hibához vezethet. Ha azonban a mutató egy helyi változó, akkor a NULL értéke nem akadályozza meg, hogy a program a mutató más másolatait használja. A statikus analizátorok általában könnyen felismerik a helyi, utánhasználat nélküli hibákat. Ezért ez a megközelítés kevésbé hasznos a helyi mutatók esetében, és gyakrabban használják a hosszú élettartamú struktúrákban tárolt mutatók esetében. Általában azonban a mutatók NULL-ra állítása jó gyakorlat, mivel lehetővé teszi a programozó számára, hogy NULL-ellenőrizze a mutatókat a hivatkozás megszüntetése előtt, így segít megelőzni az összeomlásokat.
Ha felidézzük a tömbpéldát, dinamikus allokációval fix méretű tömböt is létrehozhatunk:
int (*a)[100] = malloc(sizeof *a);
...Ez egy pointer-to-tömböt eredményez.
A pointer-to-tömbhöz való hozzáférés kétféleképpen történhet:
(*a)[index];
index[*a];
Az iteráció kétféleképpen is elvégezhető:
for (int i = 0; i < 100; i++)
(*a)[i];
for (int *i = a[0]; i < a[1]; i++)
*i;
A második példa használatának az az előnye, hogy az első példa numerikus korlátja nem kötelező, ami azt jelenti, hogy a mutató-tömb bármilyen méretű lehet, és a második példa változtatás nélkül végrehajtható.
A nyelv támogatja a többdimenziós tömböket – sorok szerint tárolva, ami lényegében egy egydimenziós tömb, amelynek elemei tömbök. Tekintettel arra, hogy a ROWS és COLUMNS konstansok, a következő deklaráció egy ROWS hosszúságú kétdimenziós tömböt deklarál, amelynek minden eleme COLUMNS egész számokból álló tömb.
int array2d[ROWS][COLUMNS];
A következő példa egy egész elem elérésére:
array2d[4][3]
Ismét balról jobbra olvasva ez eléri az 5. sort, és a sorban a 4. elemet. A array2d[4] kifejezés egy tömb, amelyet ezután a [3]-mal indexelünk, hogy elérjük a negyedik egész számot.
| Elem | Első elem | Második sor, második oszlop | i-edik sor, j-edik oszlop |
|---|---|---|---|
| Array subscript | array[0][0]
|
array[1][1]
|
array[i - 1][j - 1]
|
| Dereferenced pointer | *(*(array + 0) + 0)
|
*(*(array + 1) + 1)
|
*(*(array + i - 1) + j - 1)
|
A több-dimenziós tömbök is hasonló módon deklarálhatók.
A többdimenziós tömböt nem szabad összetéveszteni a tömbökre mutató mutatók tömbjével (más néven iliffe-vektor vagy néha tömbök tömbje). Az előbbi mindig négyszögletes alakú (minden altömbnek azonos méretűnek kell lennie), és egy összefüggő memóriaterületet foglal el. Ez utóbbi egy egydimenziós mutatók tömbje, amelyek mindegyike egy altömb első elemére mutathat a memória különböző helyén, és az altömböknek nem kell azonos méretűnek lenniük. Ez utóbbit a malloc többféle használatával is létrehozhatjuk.
Bár a nyelv típusokat biztosít szöveges karakteradatokhoz, sem a nyelv, sem a standard könyvtár nem definiál karakterlánctípust, de a nullával végződő karakterláncot gyakran használják. A karakterláncérték egy összefüggő karaktersorozat, amelynek végét nulla érték jelöli. A standard könyvtár számos karakterlánc-kezelő függvényt tartalmaz nullával végződő karakterláncokhoz, de a karakterláncok manipulálása egyéni kóddal kezelhető, és gyakran így is történik.
A C nyelvben a karakterlánc-literálokat idézőjelek (") veszik körül (pl. "Hello world!"), és a megadott char értékekből álló tömbbe vannak fordítva egy további null karakteres (0 értékű vezérlőkarakter) kóddal, amely a karakterlánc végét jelzi.
A karakterlánc-literálok nem tartalmazhatnak beágyazott újsorokat; ez a tiltás némileg leegyszerűsíti a nyelv elemzését. Ha újsort szeretne beilleszteni egy karakterláncba, a fordított perjel \n escape használható az alábbiak szerint.
A nyelv támogatja a karakterlánc literálok összefűzését – a szomszédos karakterlánc literálokat fordítási időben összekapcsoltként kezeli a rendszer. Ez lehetővé teszi a hosszú karakterláncok több sorra való felosztását, valamint azt is, hogy a preprocesszori makrókból származó karakterlánc literálokat fordítási időben hozzáfűzzünk a karakterláncokhoz. Például a forráskód:
printf(__FILE__ ": %d: Hello "
"world\n");
a következővé válik, miután az előfeldolgozó kibontja a __FILE__ fájlt:
printf("helloworld.c" ": %d: Hello "
"world\n");
ami egyenértékű a következővel:
printf("helloworld.c: %d: Hello world\n");
Számos szabványos függvénytári függvény létezik a karakterlánc-adatokkal való (nem feltétlenül állandó) műveletekhez, amelyek karaktertömbként (array of char) vannak megszervezve ezzel a null-végződésű formátummal; lásd alább.
A C karakterlánc-literális szintaxisa nagyon befolyásos volt, és számos más nyelvben is elterjedt, mint például a C++, az Objective-C, a Perl, a Python, a PHP, a Java, a JavaScript, a C# és a Ruby. Manapság szinte minden új nyelv C-stílusú karakterlánc-szintaxist alkalmaz vagy arra épít. Azok a nyelvek, amelyekből hiányzik ez a szintaxis, általában a C előttiek.
A karakterliterál, más néven karakterkonstans, aposztrófok között áll, pl. 'A', és int típusú. A karakterláncliterál és a karakterkonstans közötti különbség szemléltetésére vegyük figyelembe, hogy az "A" két karakter, az 'A' és '\0', mivel az 'A' egyetlen karaktert jelöl (ASCII-ben 65).
Egy karakterkonstans nem lehet üres (azaz a '' érvénytelen szintaxis). A többkarakteres konstansok (pl. 'xy') érvényesek, bár ritkán hasznosak – lehetővé teszik több karakter tárolását egy egész számban (pl. 4 ASCII karakter fér el egy 32 bites egész számban, 8 egy 64 bitesben). Mivel a karakterek int-be csomagolásának sorrendje nincs meghatározva (ezt a megvalósításra van bízva), a több karakteres konstansok hordozható használata nehézkes.
Mindazonáltal egy adott platformra és a fordító implementációjára korlátozott helyzetekben a többkarakteres konstansok felhasználásra kerülnek az aláírások megadásában. Egy gyakori használati eset az OSType, ahol a klasszikus Mac OS fordítók és a velük járó big endianitás kombinációja azt jelenti, hogy az egész számban a bájtok a literálban definiált karakterek pontos sorrendjében jelennek meg. A népszerű "implementációk" definíciója valójában következetes: a GCC-ben, a Clangban és a Visual C++-ban az '1234' 0x31323334-et eredményez ASCII alatt.
A karakterlánc literálokhoz hasonlóan a karakterkonstansok is módosíthatók előtagokkal, például az L'A' típusa wchar_t, és az "A" karakterértékét jelöli a széles karakterkódolásban.
Mivel bizonyos karakterek nem lehetnek közvetlenül részei egy literális karakterlánc-kifejezésnek, ehelyett egy visszaperjellel (\) kezdődő escape szekvencia azonosítja őket. Például a fordított perjelek (backslash) a "Ez a karakterlánc \"dupla idézőjeleket tartalmaz\" szövegben. jelzik (a fordítónak), hogy a belső idézőjelpár a karakterlánc tényleges része, nem pedig magának a karakterláncnak az alapértelmezett olvasása határolóként (végpontként).
A fordított perjelek különféle vezérlőkarakterek stb. bevitelére használhatók egy karakterláncban:
| Escape | Jelentése |
|---|---|
\\ | Literal backslash |
\" | Double quote |
\' | Single quote |
\n | Newline (line feed) |
\r | Carriage return |
\b | Backspace |
\t | Horizontal tab |
\f | Form feed |
\a | Alert (bell) |
\v | Vertical tab |
\? | Question mark (A trigráfok elkerülésére használják, a C23-ban kiesett elavult funkció) |
\% | Percentage mark, csak printf formátumú karakterláncok (Megjegyzés: \% nem szabványos, és nem mindig ismerhető fel) |
\OOO | OOO oktális értékű karakter (ahol a OOO 1-3 oktális számjegy, '0'-'7' tartományban) |
\xhh | hh hexadecimális értékű karakter (ahol hh 1 vagy több hexadecimális számjegy, '0'-'9', 'A'-'F', 'a'-'f') |
\uhhhh | Unicode kódpont 10000 hexadecimális alatt (a C99-hez adva) |
\Uhhhhhhhh | Unicode kódpont, ahol hhhhhhhh nyolc hexadecimális számjegy (a C99-hez adva) |
A C-szabvány nem határozza meg az egyéb backslash escape-kódok használatát, bár a fordítóprogram-gyártók gyakran további escape kódokat adnak meg nyelvi kiterjesztésként. Ezek egyike a \e escape szekvencia az 1B ASCII hexadecimális értékű escape karakterhez, amely nem került be a C szabványba, mivel más karakterkészletekben (például EBCDIC) hiányzik a reprezentáció. Elérhető GCC, Clang és tcc változatban.
Ne feledje, hogy a printf formátumú karakterláncok %%-ot használnak a literális % karakter megjelenítésére; a standard C-ben nincs \% escape szekvencia.
Mivel a char típus 1 bájt széles, egyetlen char érték általában legfeljebb 255 különböző karakterkódot jelenthet, ami közel sem elegendő a világszerte használt összes karakterhez. A nemzetközi karakterek jobb támogatása érdekében az első C szabvány (C89) széles karaktereket (wchar_t kódolású) és széles karakterláncokat vezetett be, amelyek L"Hello world!"-ként íródnak.
A széles karakterek leggyakrabban 2 bájtosak (2 byte-os kódolás, például UTF-16) vagy 4 bájtosak (általában UTF-32), de a C szabvány nem határozza meg a wchar_t szélességét, így a választást a megvalósítóra bízza. A Microsoft Windows általában UTF-16-ot használ, így a fenti karakterlánc 26 bájt hosszú lenne egy Microsoft fordítónál; a Unix világ az UTF-32-t részesíti előnyben, így az olyan fordítók, mint a GCC, 52 bájtos karakterláncot generálnak. A 2 bájt széles wchar_t ugyanazt a korlátozást érinti, mint a char, mivel bizonyos karakterek (a BMP-n kívüliek) nem ábrázolhatók egyetlen wchar_t-ben; hanem helyettesítő párokkal (surrogate pair) kell ábrázolni.
Az eredeti C szabvány csak minimális függvényeket írt elő a széles karakterláncokkal való működéshez; 1995-ben a szabványt módosították, hogy sokkal kiterjedtebb támogatást tartalmazzon a char karakterláncokhoz. A releváns függvényeket többnyire char megfelelőikről nevezték el, "w" hozzáadásával vagy az "str" helyett "wcs"-vel; a <wchar.h>-ban vannak megadva, ahol a <wctype.h> széles karakteres osztályozási és leképezési funkciókat tartalmaz.
A nemzetközi karakterek támogatásának jelenleg általánosan ajánlott módszere az UTF-8, amely char tömbökben van tárolva, és UTF-8 szerkesztő használata esetén közvetlenül a forráskódba írható, mivel az UTF-8 közvetlen ASCII-kiterjesztés.
A wchar_t általános alternatívája a változó szélességű kódolás használata, ahol a logikai karakter a karakterlánc több pozícióján is átnyúlhat. A változó szélességű karakterláncokat szó szerint literálokká lehet kódolni, azzal a kockázattal, hogy megzavarják a fordítót, vagy numerikus fordított perjelek használatával (pl. "\xc3\xa9" az "é" helyett UTF-8-ban). Az UTF-8 kódolást kifejezetten a szabványos könyvtári karakterlánc-funkciókkal való kompatibilitásra tervezték (Plan 9 alatt); a kódolás támogató jellemzői közé tartozik a beágyazott nullák hiánya, az alsorozatok érvényes értelmezése és a triviális újraszinkronizálás. Az ilyen tulajdonságokat nem tartalmazó kódolások valószínűleg nem kompatibilisek a szabványos könyvtári funkciókkal; ilyen esetekben gyakran használnak kódolás-tudatos karakterlánc-függvényeket.
Az állandó és változó karakterláncok a standard függvénykönyvtár használata nélkül is manipulálhatók. A könyvtár azonban számos hasznos funkciót tartalmaz a null-végződésű karakterláncok kezeléséhez.
(angolul)
A struktúra (structure vagy struct) olyan adattárolóként van definiálva, amelyek különböző típusú elnevezett tagok sorozatából áll, hasonlóan más nyelvek rekordjaihoz. Az első mező a struktúra címével kezdődik, és a tagok egymást követő helyeken tárolódnak a memóriában, de a fordító a hatékonyság érdekében, vagy a célarchitektúra általi megfelelő igazításhoz szükséges kitöltésként beszúrhat kitöltést a tagok közé vagy után. A struktúra mérete magában foglalja a kitöltést is.
Egy struktúrát a struct kulcsszóval deklarálunk, amelyet egy opcionális azonosító (name) követ, amely a struktúra formájának azonosítására szolgál. Az azonosítót a struktúra törzsének meződeklarációi követik, amelyek mindegyike egy típusnévből, egy mezőnévből áll, és pontosvesszővel zárul. A tagok deklarációinak listája kapcsos zárójelben található, és minden deklarációt pontosvessző zár le. Végül a deklarációt az azonosító nevek opcionális listája zárja, amelyek a struktúra vagy az unió példányaiként vannak deklarálva.
A következő deklarál egy MyStruct nevű struktúrát, amely három tagot tartalmaz. Emellett deklarál egy tee nevű példányt is:
struct MyStruct {
int x;
float y;
char* z;
} tee;
A struktúra tagjai nem lehetnek hiányosak vagy függvény típusúak. Így a tagok nem lehetnek a deklarált struktúra példányai (mivel az adott ponton hiányos), de egy mező lehet egy mutató a deklarált típusra.
A deklarálás után egy változó deklarálható a struktúra típusával. A következő deklarálja a MyStruct struktúra egy új, r nevű példányát:
struct MyStruct r;
Bár egyesek a struct kulcsszó használatával deklarálnak egy struktúraváltozót, mások a typedef kulcsszót használják a struktúratípus álnevének megadására a fő típusnévtérben. A következő példa Integer-ként deklarál egy típust, amely ezután az Integer n-hez hasonlóan használható.
typedef struct {
int i;
} Integer;
Egy tagot pontjelöléssel lehet elérni. Például, ha a fenti tee deklarációja adott, az y tag tee.y formátumban érhető el.
Egy struktúrához általában mutatón keresztül lehet hozzáférni. Vegyük például a struct MyStruct* ptee = &tee struktúrát, amely egy ptee nevű mutatót definiál a tee struktúrára. A tee-nek az y tagja a ptee struktúrára való hivatkozás megszüntetésével és az eredmény bal oldali operandusként (*ptee).y formájában történő használatával érhető el. Mivel ez a művelet gyakori, a nyelv egy rövidített szintaxist biztosít egy tagok közvetlen mutatón keresztüli eléréséhez, -> (például ptee->y).
Egy tagnak értéket rendelni olyan, mint egy változónak értéket adni. Az egyetlen különbség az, hogy a szintaxis szerint a hozzárendelés balértéke (lvalue) a tag neve.
Egy struktúra egészében is hozzárendelhető egy másik, azonos típusú struktúrához, amelyet a copy metódus ad át függvényargumentumként vagy visszatérési értékként. Például a tee.x = 74 a 74 értéket rendeli hozzá a tee struktúra x nevű tagjához, és a ptee->x = 74 ugyanezt teszi a ptee esetében.
A struktúrákhoz támogatott műveletek a következők: inicializálás, másolás, cím lekérése és mező elérése. Megjegyzendő, hogy a nyelv nem támogatja két struktúra értékének összehasonlítását, kivéve az egyéni kódon keresztüli összehasonlítást az egyes mezők között.
A nyelv egy speciális tagtípust, úgynevezett bitmezőt biztosít, amely egy egész szám, amelynek meghatározott a mérete bitben. Egy bitmezőt int, bool vagy _BitInt(N) típusú (signed/unsigned) tagként deklarálunk., plusz egy kettőspontból és bizonyos számú bitből álló utótagot adunk hozzá a tag neve után. Egyetlen bitmezőben lévő bitek teljes száma nem haladhatja meg az alaptípus bitjeinek teljes számát. A szokásos C szintaxisszabályokkal ellentétben, ha nincs explicit módon megadva, akkor implementációfüggő, hogy egy bitmező előjeles vagy előjel nélküli. Ezért a legjobb gyakorlat az előjeles (signed) vagy előjel nélküli (unsigned) megadása.
A névtelen mezők kitöltést jelölnek, és csak egy kettőspontból, majd egy bizonyos számú bitből állnak. A névtelen mezők nulla szélességének megadása az új szóhoz való igazítás kikényszerítésére szolgál. Mivel egy unió összes tagja ugyanazt a memóriát foglalja el, a nulla szélességű névtelen bitmezők semmit sem csinálnak az uniókban, azonban a nem nulla szélességű névtelen bitmezők megváltoztathatják az unió méretét, mivel bele kell férniük.
A bitmezők tagjai nem rendelkeznek címmel, ezért nem használhatók a cím (&) unáris operátorral. A sizeof operátor nem alkalmazható bitmezőkre.
A következő példa deklarál egy FlagStatus nevű struktúratípust és annak egy g nevű példányát. Az első mező, a flag, egy egybites jelző, amely csak 1 vagy 0 lehet. A második mező, a num, egy előjeles 4 bites mező; tartománya -7...7 vagy -8...7. Az utolsó mező 3 bitnyi kitöltést ad hozzá, hogy a struktúra 8 bitre kerekedjen.
struct FlagStatus {
unsigned int flag : 1;
signed int num : 4;
signed int : 3;
} g;
A C nyelvnek nincs natív támogatása a névterekhez, ellentétben a C++-val és a Javával. Emiatt a C szimbólumnevek névütközésekre hajlamosak. Azonban lehetséges anonim struktúrák használatával emulálni a névtereket.
Math.h:
#pragma once
const struct {
double PI;
double (*sin)(double);
} Math;
Math.c:
#include <math.h>
static double _sin(double arg) {
return sin(arg);
}
const struct {
double PI;
double (*sin)(double);
} Math = { M_PI, _sin };
Main.c:
#include <stdio.h>
#include "Math.h"
int main() {
printf("sin(0) = %d\n", Math.sin(0));
printf("pi is %f\n", Math.PI);
}
Az uniók többnyire olyanok, mint egy struktúrák, azzal a különbséggel, hogy a mezők átfedésben vannak a memóriában, lehetővé téve a különböző típusú értékek tárolását, bár nem egyszerre. Az uniók hasonlóak más nyelvek variánsrekordjaihoz. Minden mező ugyanarra a helyre hivatkozik a memóriában. Az uniók mérete megegyezik a legnagyobb komponenstípusának méretével plusz az esetleges kitöltés mértékével.
Egy uniót a union kulcsszóval deklarálunk. A következő deklarál egy MyUnion nevű uniót és annak egy n nevű példányát:
union MyUnion {
int x;
float y;
char* z;
} n;
Egy változó inicializálása a deklarálásával együtt egyenlőségjel hozzáfűzését, majd az adattípussal kompatibilis konstrukció létrehozását jelenti. A következő inicializál egy int típust:
int x = 12;
A nyelv nyelvtanából adódóan egy skaláris inicializáló tetszőleges számú kapcsos zárójelpár közé kerülhet. A legtöbb fordítóprogram figyelmeztetést ad, ha egynél több ilyen pár van. Az alábbiak legálisak, bár vitathatatlanul szokatlanok:
int y = { 23 };
int z = { { 34 } };
A struktúrák, uniók és tömbök deklaráció után inicializálhatók egy inicializáló listával.
Mivel a nem párosított elemek 0-ra vannak állítva, egy üres lista az összes elemet 0-ra állítja. Például a következő az a tömb összes elemét és az s tömb összes mezőjét 0-ra állítja:
int a[10] = {};
struct MyStruct s = {};
Ha egy tömböt explicit méret nélkül deklarálunk, akkor a tömb hiányos típus. Az inicializálók száma határozza meg a tömb méretét és teszi teljessé a típust. Például:
int x[] = { 0, 1, 2 };
Alapértelmezés szerint egy inicializáló lista elemei a definiálásuk sorrendjében felelnek meg az elemeknek. Túl sok érték megadása hibát eredményez. A következő utasítás inicializálja a MyStruct struktúra egy pi nevű példányát:
struct MyStruct {
int x;
float y;
char* z;
};
struct MyStruct pi = { 3, 3.1415, "Pi" };
A kijelölt inicializálók lehetővé teszik a tagok név szerinti inicializálását, tetszőleges sorrendben, anélkül, hogy explicit módon megadnánk az előző értékeket. A következő inicializálás funkcionálisan egyenértékű az előzővel:
struct MyStruct pi = { .z = "Pi", .x = 3, .y = 3.1415 };
Egy inicializálóban egy jelölő használata mozgatja az inicializáló "kurzort". Az alábbi példában, ha a MAX nagyobb, mint 10, akkor az a közepén néhány nulla értékű elem lesz; ha kisebb, mint 10, akkor az első öt inicializáló által megadott értékek közül néhányat a második öt felülír. Ha a MAX kisebb, mint 5, akkor fordítási hiba keletkezik:
int a[MAX] = { 1, 3, 5, 7, 9, [MAX - 5] = 8, 6, 4, 2, 0 };
A C89-ben egy uniót egyetlen értékkel inicializáltak, amelyet az első tagjára adtak. Vagyis a fent definiált MyUnion uniónak csak az x tagja lehetett inicializálva:
union MyUnion value = { 3 };
Egy kijelölt inicializáló használatával az inicializálandó tagnak nem kell az első tagnak lennie:
union MyUnion value = { .y = 3.1415 };
Az összetett jelölők használhatók explicit inicializálás biztosítására, amikor a dísztelen inicializáló listák félreérthetők lennének. Az alábbi példában a w struktúrák tömbjeként van deklarálva, ahol minden struktúra egy a tagból (3 int-ből álló tömb) és egy b tagból (egy int-ből) áll. Az inicializáló a w méretét 2-re állítja, és beállítja minden a első elemének értékeit:
struct { int a[3], b; } w[] = { [0].a = {1}, [1].a[0] = 2 };
Ez egyenértékű a következővel:
struct { int a[3], b; } w[] =
{
{ { 1, 0, 0 }, 0 },
{ { 2, 0, 0 }, 0 }
};
Lehetőség van az inicializálási módszertan kölcsönzésére összetett struktúra és tömbliterálok generálásához:
// pointer created from array literal.
int* ptr = (int[]){ 10, 20, 30, 40 };
// pointer to array.
float (*foo)[3] = &(float[]){ 0.5f, 1.f, -0.5f };
struct MyStruct pi = (struct MyStruct){ 3, 3.1415, "Pi" };
Az összetett literálokat gyakran kijelölt inicializálókkal kombinálják, hogy a deklaráció olvashatóbb legyen:
pi = (struct MyStruct){ .z = "Pi", .x = 3, .y = 3.1415 };
(angolul)
Egy függvényre mutató mutatót a következőképpen deklarálhatunk:
type-name (*function-name)(parameter-list);
A következő programkód egy függvénymutató használatát mutatja be az összeadás és a kivonás közötti választáshoz. A 12. sor egy operation nevű függvénymutató változót definiál, amely ugyanazt a felületet támogatja, mint az add és a subtrect függvények. A feltételes feltétel (argc) alapján a művelet az összeadás vagy a kivonás címéhez van rendelve. A 14. sorban meghívódik az a függvény, amelyre az operation mutat.
Ez a C és C++ programozási nyelvek operátorainak listája.
Az összes felsorolt operátor megtalálható C++-ban, és ha nincs másképp jelezve, C-ben is. Néhány táblázat tartalmaz egy "C-ben" oszlopot, amely jelzi, hogy egy operátor C-ben is megtalálható-e. Fontos megjegyezni, hogy a C nem támogatja az operátorok túlterhelését.
Ha nincsenek túlterhelve, az &&, || és , (vessző operátor) operátorok esetében az első operandus kiértékelése után egy szekvenciapont található.
A C és C++ nyelvekben elérhető operátorok többsége más C-családbeli nyelvekben, például C#-ban, D-ben, Java-ban, Perlben és PHP-ben is elérhető, ugyanazzal a precedenciával, asszociativitással és szemantikával.
Sok, szimbólumsorozattal meghatározott operátorra általában egy olyan névvel hivatkoznak, amely az egyes szimbólumok nevéből áll. Például a += és -= karaktereket gyakran „plusz egyenlő” és „mínusz egyenlő” néven emlegetik, a részletesebb „hozzáadás” és „kivonás” helyett.
A C egy szabad formájú nyelv.
A zárójelek stílusa programozóról programozóra változik, és vita tárgyát képezheti. További részletekért lásd a Behúzási stílus című fejezetet.
Az összetett utasítás, más néven utasításblokk, egy párosított kapcsos zárójelpár, amely között tetszőleges számú utasítás helyezhető el, például:
{
{statement}
}
Mivel az összetett utasítás egy utasítástípus, amikor utasítást használunk, az lehet egyetlen utasítás (zárójelek nélkül) vagy összetett utasítás (zárójelekkel). Összetett utasításra van szükség egy függvény törzséhez és egy olyan vezérlőszerkezeti ághoz, amely nem egyetlen utasítás.
Egy blokkban deklarált változóra a blokkban (és a belső blokkokban) található kód hivatkozhat a deklaráció alatt. A blokkban deklarált változóhoz használt memóriához való hozzáférés a blokk bezárása után (azaz egy mutatón keresztül) definiálatlan viselkedést eredményez.
Az if feltételes utasítás így néz ki:
if (expression) {
statement
} else {
statement
}
Ha a kifejezés nem nulla, a vezérlés az első utasításra kerül. Ellenkező esetben a vezérlés a második utasításra kerül. Ha az else rész hiányzik, akkor amikor a kifejezés nullára értékelődik, az első utasítás egyszerűen kimarad. Az else mindig a legközelebbi előző, nem egyező if utasításra illeszkedik. Kapcsos zárójelek használhatók ennek felülbírálására, ha szükséges, vagy az érthetőség kedvéért.
Figyelemre méltó, hogy a második utasítás lehet egy másik if utasítás. Például:
if (i == 1) {
printf("it's 1");
} else if (i == 2) {
printf("it's 2");
} else {
printf("it's something else");
}
A switch a vezérlést arra az esetcímkére adja át, amelynek értéke megegyezik az egész típusú kifejezéssel, vagy egyébként a default címkére (ha van ilyen). A vezérlés a case címkét követő utasításokon folytatódik egy break utasításig vagy a switch utasítás végéig. A szintaxis a következő:
switch (expression) {
case label-name:
statement
case label-name:
statement
.
.
.
default:
statement
}
Minden egyes case értéknek egyedinek kell lennie az utasításon belül. Legfeljebb egy default címke lehet.
A végrehajtás az egyik case címkétől a következőig folytatódik, ha nem találkozik break utasítással. Ezt átesésnek nevezik, ami bizonyos körülmények között hasznos, de gyakran nem kívánatos.
Bár szokatlan, de lehetséges case címkéket elhelyezni a belső vezérlőstruktúrák alblokkjaiban. Ilyen például Duff eszköze és Simon Tatham coroutine implementációja a PuTTY-ban.
A következő példa egy switch használatára egy int felett:
#include <stdio.h>
// ...
int num = 2;
switch (num) {
case 1:
printf("Number is 1\n");
break
case 2:
printf("Number is 2\n");
break;
case 3:
printf("Number is 3\n");
break;
default:
printf("Number is not 1, 2, or 3\n");
}
A C2Y-tól kezdve két egész szám konstans között "case tartomány" használható ... (három pont) használatával. Az érték és a három pont között szóköznek kell lennie. Ez a tartomány befogadó. Például:
#include <stdio.h>
// ...
int num = 2;
// new-style
switch (num) {
case 1 ... 3:
printf("Number is 1, 2, or 3\n");
break
default:
printf("Number is not 1, 2, or 3\n");
}
// old-style, using case fallthrough
switch (num) {
case 1:
case 2:
case 3:
printf("Number is 1, 2, or 3\n");
break
default:
printf("Number is not 1, 2, or 3\n");
}
Az iteráció utasításnak három formája létezik:
while (expression) {
statement
}
do {
statement
} while (expression)
for (init; test; next) {
statement
}
A while és do utasítások esetében a részutasítás ismételten végrehajtódik, amíg a kifejezés (expression) értéke nem nulla (igaznak felel meg). A while esetében a tesztelés, beleértve az esetleges mellékhatásokat is, minden iteráció előtt megtörténik. A do esetében a teszt minden iteráció után megtörténik. Így egy do utasítás mindig legalább egyszer végrehajtja a rész-utasítást, míg a while egyáltalán nem hajtja végre a rész-utasítást.
A for logikája a while függvény segítségével a következőképpen írható le:
for (e1; e2; e3) {
s;
}
egyenértékű a következővel:
e1;
while (e2) {
s;
cont:
e3;
}
Kivéve a continue utasítás viselkedését (amely a for ciklusban az e3-ra ugrik az e2 helyett). Ha az e2 üres, akkor 1-gyel kell helyettesíteni.
A for ciklusban a három kifejezés bármelyike elhagyható. Egy hiányzó második kifejezés a while tesztet mindig nem nullává teszi; ez egy végtelen ciklust ír le.
A C99 óta az első kifejezés deklarációs formát ölthet, amelynek hatóköre az részutasításra korlátozódik. Például:
for (int i = 0; i < limit; ++i) {
// ...
}
A foreach ciklus nem létezik C-ben, mint a Java-ban és a C++-ban. Makrók segítségével azonban emulálható.
Négy ugró utasítás létezik (feltétel nélkül átadják a vezérlést): goto, continue, break és return.
A goto utasítás a programvezérlést egy címkézett utasításnak adja át. Szintaxisa a következő:
goto label-name
Egy continue utasítás, ami egyszerűen a continue szó, a vezérlést a legbelső, azt bezáró iterációs utasítás ciklusfolytatási pontjára helyezi át. Az utasításnak egy iterációs utasításon belül kell lennie. Például:
while (true) {
// ...
continue;
}
do {
// ...
continue;
} while (true);
for (;;) {
// ...
continue;
}
A break utasítás, ami egyszerűen a break szó, egy for, while, do vagy switch utasítást zár le. A vezérlés a bezáró vezérlő utasítást követő utasításra kerül át.
A return utasítás átadja a vezérlést a függvény hívójának. Amikor a return utasítást egy kifejezés követi, az érték visszakerül a hívóhoz. A függvény végével való találkozás egyenértékű a kifejezés nélküli visszatéréssel. Ebben az esetben, ha a függvényt úgy deklaráltuk, hogy értéket ad vissza, és a hívó megpróbálja használni a visszaadott értéket, a viselkedés definiálatlan.
A címke a kód azon pontját jelöli, ahová a vezérlés átvihető. A címke egy azonosító, amelyet egy kettőspont követ. Például:
if (i == 1) {
goto END;
}
// other code
END:
A szabvány nem definiál metódust egy címke címének lekérésére, de a GCC kiterjeszti a nyelvet egy unáris && operátorral, amely visszaadja a címke címét. A cím tárolható egy void* változóban, és később felhasználható egy goto paranccsal. Ez a funkció ugrótábla megvalósítására használható.
Például a következő parancs ismételten kiírja a "hi" értéket:
void* ptr = &&J1;
J1: printf("hi");
goto *ptr;
A C2Y óta a C-ben címkézett ciklusok vannak, hasonlóan a Java-hoz. Ez lehetővé teszi a címkék hozzárendelését a for utasításokhoz, és a vezérlés átadását a break és a continue címkéken keresztül (multi-level breaks).
// using break:
outer:
for (int i = 0; i < n; ++i) {
switch (i) {
case 1:
break; // jumps to 1
case 2:
break outer; // jumps to 2
default:
continue;
}
// 1
}
// 2
// using continue:
outer:
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
continue; // jumps to 1
continue outer; // jumps to 2
// 1
}
// 2
}
Egy értéket visszaadó függvény definíciója a visszatérési típus nevéből, a kódbázisban egyedi függvénynévből, zárójelben lévő paraméterlistából és egy utasításblokkból áll, amely egy return utasítással zárul. A blokk tartalmazhat egy return utasítást a függvényből a blokk vége előtt való kilépéshez. A szintaxis a következő:
type-name function-name(parameter-list)
{
statement-list
return value;
}
Egy olyan függvényt, amely nem ad vissza értéket, típusnév helyett void-dal deklarálunk, például:
void function-name(parameter-list)
{
statement-list
}
A szabvány nem tartalmaz lambda függvényeket, de néhány fordító igen.
A paraméterlista formális paraméterdeklarációk vesszővel elválasztott listája; minden elemnek van egy típusneve, amelyet egy változó neve követ:
type-name variable-name{, type-name variable-name}
A visszatérési típus nem lehet tömb vagy függvény. Például:
int f()[3]; // Error: function returning an array
int (*g())[3]; // OK: function returning a pointer to an array
void h()(); // Error: function returning a function
void (*k())(); // OK: function returning a function pointer
Ha a függvény nem fogad el paramétereket, a paraméterlista lehet a void vagy üres, de ezeknek eltérő következményei vannak. Egy függvény argumentumokkal való meghívása, ha a paraméterlistában void van deklarálva, érvénytelen szintaxis. Egy függvény argumentumokkal való meghívása, ha üres paraméterlistával van deklarálva, nem érvénytelen szintaxis, de definiálatlan viselkedést eredményezhet. A void használata ezért a legjobb gyakorlat.
Egy függvény változó számú argumentumot fogadhat el, ha az argumentumlista végére ... karaktereket illeszt be. Egy gyakran használt függvény ezzel a deklarációval a standard printf könyvtárfüggvény, amelynek prototípusa:
int printf(const char*, ...);
A változó hosszúságú argumentumok felhasználása a <stdarg.h> fájlban deklarált szabványos könyvtári függvényekkel valósítható meg.
A kód akkor férhet hozzá egy könyvtár függvényéhez, ha az deklarálva és definiálva is van. A könyvtárfüggvény deklarációja gyakran egy headerfájlon keresztül történik, amelyet a felhasználó kód a #include direktíván keresztül használ. Alternatív megoldásként a felhasználó kód a saját fájljában is deklarálhatja a függvényt. A függvénydefiníció a felhasználó kódhoz linkeléskor kapcsolódik. A standard könyvtár általában alapértelmezés szerint linkelt, míg más könyvtárak linkelési idejű konfigurációt igényelnek.
Egy másik fájlban lévő felhasználó által definiált függvény elérése hasonló egy könyvtárfüggvény használatához. A felhasználó kód vagy egy fejlécfájl beillesztésével, vagy közvetlenül a saját fájljában deklarálja a függvényt. A másik fájlban lévő definícióra való hivatkozás akkor történik meg, amikor az objektumfájlok összekapcsolódnak.
Egy ugyanabban a fájlban definiált függvény meghívása viszonylag egyszerű. A definíciónak vagy annak deklarációjának a hívás felett kell lennie.
Egy függvény egy argumentumot érték alapján ad át, ami azt jelenti, hogy a meghívott függvény az argumentum másolatát kapja meg, és nem módosíthatja az argumentumváltozót. Ahhoz, hogy egy függvény megváltoztassa egy változó értékét, a hívó átadja a változó címét (egy mutatót), amely szimulálja azt, amit más nyelvek referenciaként biztosítanak. A meghívott függvény módosíthatja a változót az átadott cím dereferenciájával.
A következő kódban az x címe az &x hívásban történő megadásával kerül átadásra. A meghívott függvény a címet y-ként kapja, és az x-et *y-ként éri el.
void incInt(int* y) {
(*y)++;
}
int main(void) {
int x = 7;
incInt(&x);
return 0;
}
A következő kód a mutatók egy fejlettebb használatát mutatja be – mutató átadását mutatónak. A 9. sorban definiálunk egy a nevű egész mutatót, amelynek címe átadásra kerül a 10. sorban található függvénynek. A függvény egy mutatót kap az a_p nevű egész mutatóra. Hozzárendeli az a címet (*a_p-ként). A hívás után a 11. sorban felszabadul az a címhez lefoglalt és hozzárendelt memória.
#include <stdio.h>
#include <stdlib.h>
void allocate_array(int** const p, const int count) {
*p = malloc(sizeof(int) * count);
}
int main(void) {
int* a;
allocate_array(&a, 42);
free(a);
return 0;
}
A tömb típusú függvényparaméterek első pillantásra kivételnek tűnhetnek az érték-átadás szabálya alól, amint azt a következő program is mutatja, amely 123-at, és nem 1-et ír ki:
#include <stdio.h>
void setArray(int a[], int index) {
array[index] = 123;
}
int main(void) {
int a[1] = {1};
setArray(a, 0);
printf("a[0]=%d\n", a[0]);
return 0;
}
Ennek a viselkedésnek azonban más oka is van. A tömbparamétert mutatóként kezeli a rendszer. A következő prototípus egyenértékű a fenti függvényprototípussal:
void setArray(int* a, int index);
Ugyanakkor a tömbök kifejezésekben való használatára vonatkozó szabályok azt eredményezik, hogy az a értékét az első elemre mutató mutatóként kezeli a rendszer. Így ez továbbra is érték szerinti átadást jelent, azzal a kikötéssel, hogy az érték által átadott tömb első elemének címe, nem pedig a tömb tartalma.
A C99 óta a programozó a static kulcsszó használatával megadhatja, hogy egy függvény egy adott méretű tömböt fogadjon el. A következő függvényben
void setArray(int array[static 4], int index)
az első paraméternek egy legalább 4 elem hosszú tömb első elemére mutató mutatónak kell lennie. Lehetőség van minősítők (const, volatile és restrict) használatára is a tömb konvertálandó mutatótípusához.
A C23-ban hozzáadott és a C++11-ből származó C támogatja az attribútumspecifikáló szekvenciákat. Az attribútumok bármely szimbólumra alkalmazhatók, amely támogatja őket, beleértve a függvényeket és a változókat is, és a fordítóprogram szükség szerint kezeli az attribútummal megjelölt szimbólumokat. Ezek hasonlóak a Java annotációkhoz, amelyek további információkat nyújtanak a fordítóprogramnak, azonban abban különböznek, hogy a C-ben az attribútumok nem metaadatok, amelyekhez reflexióval lehet hozzáférni. Továbbá a C-ben nem lehet egyéni attribútumokat létrehozni, ellentétben a Javával, ahol egyéni annotációk definiálhatók a szabványos annotációk mellett. A C-nek azonban vannak implementáció-/gyártóspecifikus attribútumai, amelyek nem szabványosak. Ezekhez jellemzően egy névtér tartozik. Például a GCC és a Clang attribútumai a gnu:: névtér alatt találhatók, és minden ilyen attribútum [[gnu::*]] formátumú, bár a C nyelv nem támogatja a névtereket a nyelvben.
Egy attribútum függvényen való használatának szintaxisa a következő:
[[nodiscard]]
bool satisfiesProperty(const struct MyStruct* s);
A szabvány a következő attribútumokat határozza meg:
| Név | Leírás |
|---|---|
[[noreturn]] |
Azt jelzi, hogy a megadott függvény nem tér vissza a hívójához. |
[[deprecated]][[deprecated("reason")]] |
Azt jelzi, hogy a megjelölt szimbólum használata megengedett, de a megadott okból (ha meg van adva) nem javasolt/nem ajánlott. |
[[fallthrough]] |
Azt jelzi, hogy az előző case címkéről való átlépés szándékos. |
[[maybe_unused]] |
Letiltja a fordító figyelmeztetéseit egy nem használt entitáson. |
[[nodiscard]][[nodiscard("reason")]] |
Fordítói figyelmeztetést ad, ha a megjelölt szimbólum visszatérési értékét a megadott ok (ha meg van adva) miatt elvetik vagy figyelmen kívül hagyják. |
[[unsequenced]] |
Azt jelzi, hogy egy függvény állapot nélküli, hatástalan, idempotens és független. |
[[reproducible]] |
Azt jelzi, hogy egy függvény hatástalan és idempotens. |
(angolul)
A C dinamikus memóriafoglalás a C programozási nyelvben a dinamikus memóriafoglalás manuális memóriakezelését jelenti a szabványos C-könyvtárban található függvények, főként a malloc, realloc, calloc, aligned_alloc és free segítségével.
A C++ programozási nyelv tartalmazza ezeket a függvényeket; azonban a new és delete operátorok hasonló funkciókat biztosítanak, és a nyelv szerzői is ezeket ajánlják. Ennek ellenére számos olyan helyzet van, amikor a new/delete használata nem alkalmazható, például szemétgyűjtési kód vagy teljesítményérzékeny kód esetén, és a magasabb szintű new operátor helyett a malloc és a new kombinációjára lehet szükség.
A malloc által használt tényleges memóriafoglalási mechanizmusnak számos különböző implementációja létezik. Teljesítményük mind a végrehajtási időben, mind a szükséges memóriában változik.
Az anonim funkciót a szabványos C programozási nyelv nem támogatja, de néhány C dialektus, például a GCC és a Clang támogatja.
A GNU Compiler Collection (GCC) támogatja az anonim függvényeket, vegyesen a beágyazott függvényekkel és és utasításkifejezésekkel. Ennek a következő formája van:
( { return_type anonymous_functions_name (parameters) { function_body } anonymous_functions_name; } )
A következő példa csak a GCC-vel működik. A makrók kibontásának módja miatt az l_body nem tartalmazhat vesszőt a zárójelen kívül; A GCC a vesszőt a makróargumentumok határolójaként kezeli. Az l_ret_type argumentum eltávolítható, ha a __typeof__ elérhető; az alábbi példában a __typeof__ használata a tömbön a testtype *-ot adná vissza, amelyre szükség esetén a tényleges értékre hivatkozhatunk.
#include <stdio.h>
//* this is the definition of the anonymous function */
#define lambda(l_ret_type, l_arguments, l_body) \
({ \
l_ret_type l_anonymous_functions_name l_arguments \
l_body \
&l_anonymous_functions_name; \
})
#define forEachInArray(fe_arrType, fe_arr, fe_fn_body) \
{ \
int i=0; \
for(;i<sizeof(fe_arr)/sizeof(fe_arrType);i++) { fe_arr[i] = fe_fn_body(&fe_arr[i]); } \
}
typedef struct
{
int a;
int b;
} testtype;
void printout(const testtype * array)
{
int i;
for ( i = 0; i < 3; ++ i )
printf("%d %d\n", array[i].a, array[i].b);
printf("\n");
}
int main(void)
{
testtype array[] = { {0,1}, {2,3}, {4,5} };
printout(array);
/* the anonymous function is given as function for the foreach */
forEachInArray(testtype, array,
lambda (testtype, (void *item),
{
int temp = (*( testtype *) item).a;
(*( testtype *) item).a = (*( testtype *) item).b;
(*( testtype *) item).b = temp;
return (*( testtype *) item);
}));
printout(array);
return 0;
}
A Clang támogatja az anonim függvényeket, úgynevezett blokkokat, amelyek a következő formájúak:
^return_type ( parameters ) { function_body }
A fenti blokkok típusa return_type (^)(parameters).
A fent említett blokk bővítmény és a Grand Central Dispatch (libdispatch) használatával a kód egyszerűbbnek tűnhet:
#include <stdio.h>
#include <dispatch/dispatch.h>
int main(void) {
void (^count_loop)() = ^{
for (int i = 0; i < 100; i++)
printf("%d\n", i);
printf("ah ah ah\n");
};
/* Pass as a parameter to another function */
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), count_loop);
/* Invoke directly */
count_loop();
return 0;
}
A blokkokat tartalmazó kódot az -fblocks opcióval kell lefordítani, és a -lBlocksRuntime használatával kell linkelni.
A C szabvány egyik aspektusa (nem egyedi a C-re), hogy bizonyos kódok viselkedését "undefined"-nek mondják. A gyakorlatban ez azt jelenti, hogy az ebből a kódból előállított program bármire képes, a programozó szándéka szerinti működéstől egészen a minden futáskor összeomlásig.
Például a következő kód definiálatlan viselkedést produkál, mivel a b változó többször módosul anélkül, hogy közbeiktatott sorrendi pont lenne:
#include <stdio.h>
int main(void)
{
int b = 1;
int a = b++ + b++;
printf("%d\n", a);
}
Mivel a "b++ + b++"-ban a b módosításai között nincs szekvenciapont, lehetséges, hogy a kiértékelési lépéseket több sorrendben is végrehajthatjuk, ami kétértelmű állítást eredményez. Ez javítható például a kód átírásával, hogy a sorrendi pont beillesztésével kikényszerítsük az egyértelmű viselkedést:
a = b++;
a += b++;
*ptee.y nem helyes; *(ptee.y)-ként értelmeződik, így a zárójelek szükségesek.long long módosítót a C99 szabványban vezették be.#include direktíva arra utasítja a szövegfájl-feldolgozót, hogy a direktíva szövegét egy megadott fájl (például stdbool.h) tartalmára cserélje.<limits.h> header (climits.h a C++-ban) az egész típusú makrókat, a <float.h> fejléc (C++-ban cfloat) pedig a lebegőpontos típusokhoz definiálja a makrókat. A tényleges értékek a megvalósítástól függenek.<inttypes.h> fejlécben van definiálva (C++-ban cinttypes), és elérhető a <stdint.h> headerben is (cstdint fejléc C++-ban).