18.2 C23 könyvtár és preprocessor

A C23 könyvtári újdonságai több, rendszerprogramozásban visszatérő művelethez adnak szabványos eszközt: ellenőrzött egész aritmetikához, bitvizsgálathoz és érzékeny adatok törléséhez. Használatuk azonban nem következik pusztán a C23 nyelvi mód bekapcsolásából. A preprocessort, a fordítót, a headereket és a linkeléskor, illetve futáskor használt könyvtárat külön kell vizsgálni.

A C23 nyelvi újdonságok és inkompatibilis változások rész verziózási alapjaira építünk; a ritkább numerikus szolgáltatások előzménye a Numerikus és ritkább C könyvtári eszközök térképe. Itt a döntési szabály a fontos: képességet ellenőrizzünk, ne támogatást feltételezzünk egy verziószámból.

18.2.1 Nyelvi mód, header és könyvtári képesség

Az ISO C23 nyelvi verziót a __STDC_VERSION__ makró 202311L értéke jelzi. Ez nem szolgáltatásonkénti megfelelőségi teszt. Korábbi fordítókiadások részleges C23-támogatása más verzióértékkel vagy -std=c2x kapcsolóval is megjelenhet; ezeket külön profilként kell kezelni.

A képességvizsgálat (feature detection) három eltérő kérdést választ szét:

Vizsgálat Mit igazol? Mit nem igazol?
Nyelvi fordítási próba A fordító elfogadja a használt konstrukciót Egy könyvtári függvény elérhetőségét
Header és konkrét API fordítása A deklaráció vagy makró használható a megadott típusokkal Külső függvényszimbólum linkelhetőségét
Linkelési, szükség esetén célgépi próba A kiválasztott könyvtári környezetben használható a szolgáltatás Más telepítési környezet kompatibilitását

A headert akár a fordítócsomag is szállíthatja, nem feltétlenül a libc. Ez különösen fontos a fordítói beépített műveletekre épülő headereknél.

A példák választott laborprofilja Linux, x86-64 System V ABI, LP64, CHAR_BIT == 8, GCC 15.1.0 vagy Clang 20.1.0, glibc 2.41. Ez vizsgálati célprofil, nem közölt fordítási eredmény. A parancsokban szereplő gcc a rögzített kiadást jelentse; Clanggal ugyanazokat a próbákat külön kell elvégezni.

18.2.2 Ellenőrzött aritmetika és bitműveletek

Az ISO C23 <stdckdint.h> headerének ckd_add, ckd_sub és ckd_mul makrói ellenőrzött egész aritmetikát végeznek. A matematikai eredmény ábrázolhatóságát a célobjektum típusában vizsgálják; igaz visszatérési érték jelzi az ábrázolhatatlanságot. Ez nem egyszerűen az operandusok szokásos közös típusában végzett művelet utólagos ellenőrzése.

Az alábbi példa minden operandushoz és célobjektumhoz size_t típust használ. Hiba esetén a belső eredményt nem teszi közzé.

Az ISO C23 <stdbit.h> unsigned egész értékek bitmintázatának vizsgálatát támogatja. Például:

Ezek értékműveletek, nem memóriába írt bájtsorrendek vizsgálatai. Nem helyettesítik a hálózati szerializálást.

18.2.2.1 Kidolgozott példa C17 fallbackkal

Egy keret tárolóigénye legyen

B=nw+h, B=nw+h,

ahol nn az elemszám, ww az elemméret C bájtban, hh pedig a fejléc mérete. Mindhárom nemnegatív, size_t érték.

A következő teljes program alapértelmezésben ISO C17; a két konfigurációs kapcsoló külön engedélyezi a C23-szolgáltatásokat.

/* capacity.c */
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>

#ifndef HAVE_C23_CHECKED
#define HAVE_C23_CHECKED 0
#endif
#ifndef HAVE_C23_BITS
#define HAVE_C23_BITS 0
#endif

#if HAVE_C23_CHECKED
#include <stdckdint.h>
#endif
#if HAVE_C23_BITS
#include <stdbit.h>
#endif

static bool frame_size(size_t n, size_t w, size_t h, size_t *out)
{
    size_t product;
    size_t total;

#if HAVE_C23_CHECKED
    if (ckd_mul(&product, n, w) ||
        ckd_add(&total, product, h)) {
        return false;
    }
#else
    if (w != 0 && n > SIZE_MAX / w) {
        return false;
    }
    product = n * w;
    if (h > SIZE_MAX - product) {
        return false;
    }
    total = product + h;
#endif

    *out = total;
    return true;
}

static unsigned int ones(unsigned int value)
{
#if HAVE_C23_BITS
    return stdc_count_ones_ui(value);
#else
    unsigned int count = 0;
    while (value != 0u) {
        value &= value - 1u;
        ++count;
    }
    return count;
#endif
}

int main(void)
{
    size_t bytes = 0;

    if (!frame_size(4, 24, 16, &bytes) || bytes != 112)
        return EXIT_FAILURE;

    if (frame_size(SIZE_MAX, 2, 0, &bytes) || bytes != 112)
        return EXIT_FAILURE;

    if (ones(0xA4u) != 3u)
        return EXIT_FAILURE;

    return EXIT_SUCCESS;
}

Az out előfeltétele: nem null pointer, és élő, írható size_t objektumra mutat; az objektumhoz nincs szinkronizálatlan konkurens hozzáférés.

Hozzáférés Tulajdonos és élettartam Érvénytelenítés, hibakimenet
out A hívó birtokolja; a függvény csak a hívás alatt kölcsönzi Hiba esetén változatlan; a pointert a függvény nem őrzi meg
&product, &total A frame_size lokális objektumai Élettartamuk a függvényből kilépéskor véget ér

Bizonyításvázlat. Legyen M=𝚂𝙸𝚉𝙴_𝙼𝙰𝚇M=\texttt{SIZE\_MAX}. Ha w>0w>0, akkor

nw≤M⇔n≤⌊Mw⌋. nw\le M \iff n\le\left\lfloor\frac{M}{w}\right\rfloor.

Ezért a szorzás előtt osztással vizsgálható az ábrázolhatóság. Ha w=0w=0, a szorzat nulla. A már ellenőrzött p=nwp=nw után

p+h≤M⇔h≤M−p. p+h\le M \iff h\le M-p.

A kivonás biztonságos, mert p≤Mp\le M. A fallback tehát nem előbb számol túlcsorduló eredményt, hanem a művelet előtt ellenőriz.

A bitfallback minden iterációban törli a legalsó beállított bitet: a value - 1u az addigi legalsó egyest nullává, az alatta levő nullákat egyessé változtatja; az ÉS-művelet csak azt az egyest törli.

Fordítás és a két képesség külön próbája:

gcc -std=c17 -Wall -Wextra -Wpedantic capacity.c -o capacity-c17

gcc -std=c23 -Wall -Wextra -Wpedantic \
    -DHAVE_C23_CHECKED=1 capacity.c -o capacity-checked

gcc -std=c23 -Wall -Wextra -Wpedantic \
    -DHAVE_C23_BITS=1 capacity.c -o capacity-bits

A várt sikeres futási eredmény nulla kilépési státusz; ez nem mért eredmény. A konfiguráció csak sikeres próbák után állítsa egyre a megfelelő kapcsolókat. A két képességet ne egyetlen „van C23” kapcsoló helyettesítse.

18.2.3 Érzékeny adatok törlése és sztringmásolás

Az ISO C23 <string.h> headerében megjelenő memset_explicit rendeltetése, hogy a törlő írás ne maradjon el pusztán azért, mert az objektumot később már nem olvassák. A közönséges memset ilyen körülmények között eltávolítható optimalizáláskor.

A szerződés továbbra is érvényes, írható, megfelelő méretű tárolót követel. A titok tulajdonosa töröljön felszabadítás előtt, miután megszűnt minden párhuzamos hozzáférés. A művelet nem törli automatikusan a korábbi másolatokat, regiszterben maradt értékeket, core dumpot vagy swapot.

C17 fallback: nincs azonos garanciájú, általánosan rendelkezésre álló ISO C17-függvény. Használható dokumentált platformprimitív, külön képességpróbával. Ha az alkalmazás biztonsági szerződése megköveteli a törlést, megfelelő primitív hiányában a konfiguráció hibázzon. A sima memset és egy ellenőrizetlen „volatile trükk” nem tekinthető automatikusan egyenértékű helyettesítésnek.

A strdup és strndup szintén ISO C23 könyvtári függvény; korábban POSIX környezetben voltak szabványosak, ISO C17-ben nem.

A strdup nullterminált bemenetet vár. A strndup(s, n) legfeljebb az első nn karaktert másolja, korábbi nullkarakternél megáll, és siker esetén terminátort ad az eredményhez. A bemenetnek a vizsgált tartományban olvashatónak kell lennie; a korlát nem tesz érvényessé egy dangling pointert.

Mindkettő új allokációt ad vissza: siker esetén a hívó tulajdona, free érvényteleníti; allokációs hibánál null pointert kapunk. C17-ben saját nevű wrapper készíthető: korlátozott hosszkeresés, az len + 1 méret ellenőrzése, malloc, másolás, terminálás. Ne deklaráljuk kézzel a hiányzó szabványos nevet.

18.2.4 Preprocessor: jelenlét helyett használhatóság

Az ISO C23 __has_include azt vizsgálja, hogy egy header az adott keresési környezetben bevonható-e. Nem igazolja sem az API teljességét, sem a külső függvények linkelhetőségét. Régebbi preprocesszorokhoz először magának az operátornak az elérhetőségét is vizsgálni kell; a konfigurált fordítási próba gyakran egyszerűbb.

További C23-eszközök:

C23-as, önállóan nem teljes programrészlet:

#define VALUES(...) { 0 __VA_OPT__(,) __VA_ARGS__ }

static const int first[] = VALUES();
static const int second[] = VALUES(4, 9);

A két inicializáló rendre { 0 } és { 0, 4, 9 }. C17 fallback lehet két külön makrófelület. A #embed helyett előállított C-inicializáló használható, reprodukálható generálási szabállyal. A C17-es ágból a C23-direktívákat célszerű külön fájlba választani; a preprocesszor új képességeinek felismerését ne ugyanarra a képességre építsük.

18.2.5 Ritkább kiterjesztések és tipikus tévedések

A C23 könyvtári térképen külön vizsgálandók a következők:

Terület Példa Ellenőrzési szempont
Numerika roundeven, további matematikai műveletek Deklaráció, linkelés, szükség esetén libm
Feltételes lebegőpontos képességek Decimális lebegőpontos típusok és kapcsolódó támogatás Fordítói és könyvtári képesség külön
Karakterkonverzió <uchar.h>, mbrtoc8, c8rtomb Locale- és konverziósállapot-szerződés
Méretezett felszabadítás free_sized, free_aligned_sized A méret és igazítás pontos előfeltétele

A „külön vizsgálandó” nem jelenti, hogy minden felsorolt szolgáltatás szabvány szerint opcionális. A feltételes szabványos képességet és a hiányos implementációt meg kell különböztetni.

Gyakori hiba a header jelenlétét teljes támogatásnak tekinteni, a túlcsordulást a művelet után ellenőrizni, vagy biztonsági garanciát gyengébb fallbackkal észrevétlenül lecserélni. Ezeket külön konfigurációs kapcsoló, előzetes aritmetikai ellenőrzés és dokumentált hibapolitika javítja.

18.2.6 Feladatok

  1. Miért nem elegendő a -std=c23 és a <string.h> sikeres bevonása a memset_explicit használatához?
  2. Bővítsd a program ellenőrzéseit a frame_size(SIZE_MAX, 0, 7, ...) és a frame_size(1, 1, SIZE_MAX, ...) esetekkel.
  3. Egy migrációban a hiányzó memset_explicit helyére memset kerül, a strndup támogatását pedig csak headerellenőrzéssel döntik el. Diagnosztizáld és javítsd a konfigurációt.

18.2.7 Megoldások

18.2.7.1 A támogatási rétegek szétválasztása

A nyelvi mód nem biztosít libc-szimbólumot, a <string.h> pedig C17-ben is létezik. A konkrét függvény deklarációját fordítással, szimbólumát linkeléssel kell ellenőrizni; dinamikus linkelésnél a célkörnyezet is számít. Ellenőrzési feltétel: a próba valóban hivatkozzon a függvényre, és ezt az optimalizáló ne tüntesse el.

18.2.7.2 Határértékteszt kódolása

A következő C17-kompatibilis részlet a korábbi main függvénybe illeszthető, ugyanazokkal a buildparancsokkal:

if (!frame_size(SIZE_MAX, 0, 7, &bytes) || bytes != 7)
    return EXIT_FAILURE;

if (frame_size(1, 1, SIZE_MAX, &bytes) || bytes != 7)
    return EXIT_FAILURE;

Az első esetben a szorzat nulla, tehát az eredmény hét. A másodikban 1+𝚂𝙸𝚉𝙴_𝙼𝙰𝚇1+\texttt{SIZE\_MAX} nem ábrázolható. Az ellenőrzés a hibajelzés mellett a változatlan kimeneti objektumot is vizsgálja.

18.2.7.3 Biztonsági és konfigurációs hiba

A memset nem őrzi meg az explicit törlés garanciáját. Dokumentált platformfallback vagy konfigurációs hiba szükséges. A strndup számára konkrét fordítási és linkelési próba kell; C17/POSIX profilban az előírt feature-test makrókat a headerek előtt kell beállítani. Ellenőrzési feltétel: minden engedélyezett és fallback ág külön buildben, azonos alkalmazásszintű szerződéssel működjön.

18.2.8 Összefoglaló

A C23-eszközök választását műveleti szerződés vezesse: ellenőrzött aritmetikához stdckdint.h, bitvizsgálathoz stdbit.h, érzékeny törléshez megfelelő garanciájú primitív kell. A compiler, a headerek és a libc képessége külön bizonyítandó. A jó C17 fallback a viselkedést őrzi meg; ahol ez nem biztosítható, a hiányt látható hibává teszi.