A standard fejléc kiválasztása nem pusztán névkeresés: egyben szerződésválasztás. Egy numerikus függvénynél számít az értelmezési tartomány és a hibajelzés módja; karakterfeldolgozásnál az aktuális locale és a konverziós állapot; diagnosztikánál pedig az, hogy a vizsgálat egyáltalán bekerül-e a programba.
Ez a térkép a lebegőpontos reprezentációra, a típusfüggő headeres eszközökre és a környezeti állapotra épít. Az alap ISO C17, hosted környezetben; az opcionális képességeket külön jelöljük. A fejléc megléte önmagában nem bizonyít például teljes IEC 60559 lebegőpontos működést vagy meghatározott Unicode-kódolást.
A <math.h> valós matematikai műveleteket,
osztályozó makrókat és hibajelzési eszközöket ad. A függvénycsaládokban
rendszerint a toldalék nélküli változat double, az
f végű float, az l végű
long double típussal dolgozik:
/* ISO C17; <math.h>; kifejezésrészletek. */
sqrt(9.0)
sqrtf(9.0f)
sqrtl(9.0L)A típust nem szabad összekeverni a pontossággal: a
long double nem minden implementációban ad több értékes
számjegyet a double típusnál.
A tartományhiba (domain error) azt jelenti,
hogy az argumentum kívül esik a függvény értelmezési tartományán; ilyen
a valós sqrt(-1.0). Az
eredménytartomány-hiba (range error) az
eredmény reprezentálhatóságával kapcsolatos, például túlcsorduláskor. A
math_errhandling jelzi, hogy az implementáció
errno-alapú jelzést (MATH_ERRNO), lebegőpontos
kivételjelzőket (MATH_ERREXCEPT), vagy mindkettőt
használja. Az errno == 0 ezért önmagában nem általános
sikerfeltétel.
A következő programban a matematikai előfeltételt futásidejű ellenőrzés biztosítja. Az assertion csak egy belső invariánst dokumentál.
/* numeric.c — ISO C17, hosted környezet */
#include <assert.h>
#include <float.h>
#include <inttypes.h>
#include <limits.h>
#include <math.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
const double squared = 9.0;
if (!isfinite(squared) || squared < 0.0) {
return EXIT_FAILURE;
}
const double root = sqrt(squared);
assert(root >= 0.0);
const uintmax_t samples = 42;
const size_t object_bytes = sizeof root;
if (printf("root=%.17g samples=%" PRIuMAX
" sizeof=%zu CHAR_BIT=%d DBL_MANT_DIG=%d\n",
root, samples, object_bytes,
CHAR_BIT, DBL_MANT_DIG) < 0) {
return EXIT_FAILURE;
}
return fflush(stdout) == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}A fordítási parancs választott laborprofilja: GCC 14.2.0, glibc 2.40, Linux x86-64, System V ABI, LP64. Ez nem futtatással ellenőrzött eredményközlés.
gcc -std=c17 -Wall -Wextra -Wpedantic numeric.c -lm -o numericA -lm e toolchain linkelési konvenciója, nem ISO
C-előírás. A matematikailag várt gyökérték
;
a kiírt típusjellemzőket a tényleges implementáció szolgáltatja. A
sizeof egysége C bájt, amelynek bitszámát
a CHAR_BIT adja meg.
Indoklás. A negatív, végtelen és NaN bemeneteket
kizártuk. Nemnegatív, véges double érték négyzetgyöke nem
okoz túlcsordulást; a példában a bemenet és az eredmény is pontosan
reprezentálható. Az általános numerikus pontosság kérdését ettől még nem
oldja meg egy assertion.
A <complex.h> komplex típusokhoz és műveletekhez
tartozik. C17-ben a komplex támogatás opcionális; hiányát a
__STDC_NO_COMPLEX__ makró jelezheti.
A <tgmath.h> típusgenerikus makrói az argumentum
típusa alapján választanak matematikai függvényt. Ez nem C++-féle
túlterhelés.
/* ISO C17; komplex támogatást igénylő részlet. */
#include <complex.h>
#include <tgmath.h>
/* Függvénytörzsben: */
double complex z = 1.0 + 2.0 * I;
double complex root = sqrt(z); /* A megfelelő csqrt-változat. */
double magnitude = cabs(root);Itt a sqrt komplex műveletet választ. Ha ugyanaz a makró
egész argumentumot kap, a választásnál az egész típus rendszerint
double-nak számít. Az „azonos függvénynév” tehát nem jelent
azonos eredménytípust vagy költséget.
<fenv.h> szerepeA lebegőpontos környezet a támogatott kerekítési módokat és a lebegőpontos kivételjelzőket foglalja magába. A jelző nem C++-kivétel, és nem szükségképpen operációsrendszer-szignál.
A jelzők tartósan beállítva maradhatnak, amíg nem töröljük őket. Ezért egy művelet vizsgálatának sorrendje:
feclearexcept eredményének
ellenőrzésével;fetestexcept segítségével.Például a fetestexcept(FE_INVALID) != 0 beállított
érvénytelenművelet-jelzőt keres, ha a FE_INVALID
támogatott és definiált. Törlés nélkül egy korábbi művelet
hibáját is láthatjuk.
Ha az aritmetika környezetfüggő működésére támaszkodunk, a
#pragma STDC FENV_ACCESS ON fordítói támogatását külön
ellenőrizni kell. A fejléc jelenléte ezt nem bizonyítja; a környezetet
figyelmen kívül hagyó optimalizálási módok, például bizonyos
-ffast-math beállítások, alkalmatlanok ilyen
vizsgálathoz.
A <assert.h> assert makrója belső
programinvariánsok diagnosztikájára szolgál. Aktív állapotban a hamis
feltétel diagnosztikát és rendellenes programleállást vált ki.
Ha az NDEBUG definiált a fejléc feldolgozásakor, az
assertion feltétele nem értékelődik ki. Az
NDEBUG=0 is definiált makró, tehát szintén kikapcsolja az
assertionöket.
/* Hibás tervezési példa; a művelet eltűnhet a release buildből. */
assert(++count < limit);A javítás nem pusztán az inkrementálás áthelyezése. Előbb a művelet saját előfeltételeit — például a túlcsordulás kizárását — kell biztosítani, utána külön végrehajtani, végül mellékhatásmentesen ellenőrizni az invariánst.
Felhasználói bemenetet, allokációs hibát vagy fájlmegnyitási hibát nem kezelünk kizárólag assertionnel. Ezek a program üzemi hibaszerződésének részei, release buildben is.
A „széles karakter” (wide character) nem azonos azzal, hogy
„Unicode-kódpont”. A wchar_t szélessége és a hozzá
kapcsolódó karakterkódolás implementációfüggő.
| Fejléc | Feladat és minimális példa | Fontos feltétel |
|---|---|---|
<locale.h> |
setlocale(LC_CTYPE, "") |
Környezetből választ locale-t; sikertelenségkor null pointert ad |
<wchar.h> |
mbrtowc(&wc, "A", 1, &state) |
Állapottartó többbájtos dekódolás |
<wctype.h> |
iswalpha((wint_t)wc) |
Locale-függő karakterosztályozás |
<uchar.h> |
mbrtoc16(&unit, "A", 1, &state16) |
char16_t kódegységet állít elő |
A táblázat kifejezésrészleteiben wc egy
wchar_t, unit egy char16_t, a két
állapot pedig külön, nullával inicializált mbstate_t
objektum. A szükséges deklaráció például:
/* ISO C17; <wchar.h>; függvénytörzs-részlet. */
mbstate_t state = {0};
wchar_t wc;
size_t consumed = mbrtowc(&wc, "A", 1, &state);A hívó tulajdonolja a kimeneti és állapotobjektumot; a függvény csak a hívás idejére kölcsönzi a címüket. Az állapotot ugyanazon konverziós folyamat folytatásához meg kell őrizni. Az objektum élettartamának vége érvényteleníti a rá mutató pointert; az állapot felülírása pedig elveszítheti a dekódolás folytatásához szükséges információt.
A mbrtowc eredménye nem egyszerű siker/hiba érték:
(size_t)-1 érvénytelen sorozatot, (size_t)-2
hiányos sorozatot, nulla nullkaraktert jelez. A mbrtoc16
ezenfelül (size_t)-3 eredménnyel is visszatérhet, amikor
korábban feldolgozott bemenetből további kódegységet ad ki.
A __STDC_UTF_16__ és __STDC_UTF_32__ makrók
a megfelelő kódolási garanciákat jelzik. A típusnevekből önmagukban ne
következtess teljes Unicode-feldolgozásra.
A setlocale visszatérési sztringjét az implementáció
kezeli: nem szabad felszabadítani vagy módosítani, és későbbi
setlocale hívás felülírhatja. A locale váltása megosztott
környezeti állapotot érint; részletes szövegkezelési hátterét a kódolásról szóló rész tárgyalja.
A <signal.h> az ISO C szignálmodelljét adja.
Minimális jelzőobjektum például:
/* ISO C17; fájlszintű deklaráció. */
#include <signal.h>
static volatile sig_atomic_t stop_requested = 0;Ez nem általános szálközi szinkronizációs eszköz. Egy aszinkron
szignálkezelőből megengedett műveletek köre erősen korlátozott; a
printf vagy a tetszőleges cleanup meghívását nem teszi
biztonságossá ez a deklaráció. Az ISO C és a POSIX szignálszerződéseit a
dedikált szignálfejezet választja szét.
A <setjmp.h>
setjmp/longjmp párosa mentett végrehajtási
környezethez ugrik vissza:
/* ISO C17; <setjmp.h>; függvénytörzs-részlet. */
jmp_buf env;
if (setjmp(env) == 0) {
longjmp(env, 1);
}Az első visszatérés nulla; az ugrás utáni visszatérés itt egy. A
setjmp hívást tartalmazó függvénynek még aktívnak kell
lennie. A setjmp után módosított, nem
volatile, automatikus lokális változók értéke az ugrás után
indeterminate lehet.
A vezérlésugrás nem futtat automatikus erőforrás-cleanupot. Ezért nem egyszerű helyettesítője a hibavisszatérésnek; lásd a nem lokális vezérlés életciklus-korlátait.
| Fejléc | Mire használjuk? | Gyakori tévedés |
|---|---|---|
<limits.h> |
CHAR_BIT, INT_MAX,
LONG_MAX |
A long nem mindenhol 64 bites |
<float.h> |
FLT_RADIX, DBL_MANT_DIG,
DBL_MAX |
A DBL_MIN a legkisebb pozitív normál érték, nem a
legnegatívabb |
<stddef.h> |
size_t, ptrdiff_t, offsetof,
max_align_t |
A sizeof nem szükségképpen oktettekben mér |
<inttypes.h> |
PRIuMAX, PRId64,
strtoimax |
A %ld nem általános helyettesítője a típusspecifikus
formátumnak |
A DBL_EPSILON az
és a következő nagyobb reprezentálható double érték
különbségét jellemzi; nem univerzális numerikus tolerancia.
Az exact-width típusok, például az int64_t,
opcionálisak. A hozzájuk tartozó formázó makrókra csak a megfelelő típus
rendelkezésre állásakor támaszkodhatunk. A fenti program ezért használja
a hordozható uintmax_t–PRIuMAX párost. Az
offsetof(T, member) eredménye C bájtban értendő;
bit-fieldre nem alkalmazható.
A C17 29 standard fejlécnevének teljes térképe:
| Terület | Fejlécek |
|---|---|
| Numerika és környezet | <math.h>, <complex.h>,
<tgmath.h>, <fenv.h> |
| Típusjellemzők | <limits.h>, <float.h>,
<stddef.h>, <stdint.h>,
<inttypes.h> |
| Karakterek és locale | <ctype.h>, <locale.h>,
<wchar.h>, <wctype.h>,
<uchar.h> |
| I/O, memória, idő, hibák | <stdio.h>, <stdlib.h>,
<string.h>, <time.h>,
<errno.h> |
| Diagnosztika és vezérlés | <assert.h>, <signal.h>,
<setjmp.h> |
| Nyelvi segédeszközök | <stdarg.h>, <stdbool.h>,
<stdalign.h>, <stdnoreturn.h>,
<iso646.h> |
| Konkurencia | <stdatomic.h>,
<threads.h> |
A táblázat leltár, nem minden környezetre érvényes támogatási
garancia. A komplex támogatás mellett az atomikus és szálkönyvtári
támogatás is lehet opcionális; a kapcsolódó jelzések
__STDC_NO_ATOMICS__ és __STDC_NO_THREADS__.
Freestanding implementációban a könyvtári minimum szűkebb.
Az Annex K opcionális, bounds-checking interfészeket ad, például
memcpy_s és strcpy_s nevekkel. Nem önálló
fejléc.
Támogatását a __STDC_LIB_EXT1__ jelzi; használati
igényét a releváns fejlécek előtt definiált, 1 értékű
__STDC_WANT_LIB_EXT1__ makróval kell megadni. A definíció
önmagában nem teremti meg a hiányzó libc-támogatást.
Ezek az interfészek sem tudják általában ellenőrizni, hogy a hívó
által közölt pufferméret megfelel-e a tényleges objektumnak. Futásidejű
szerződéssértéskor constraint handler is szerepet kap, így a hibakezelés
nem feltétlenül egyszerű visszatérési kód. Egy gyártói _s
nevű függvény nem szükségképpen Annex K-kompatibilis. Hordozható API-t
ezért ne építs ellenőrizetlen elérhetőségükre.
Magyarázat. Miért hibás általános állítás, hogy
a DBL_EPSILON minden összehasonlításhoz megfelelő
tolerancia, illetve hogy a wchar_t Unicode-kódpontot
tárol?
Kódolás. A numerikus program
samples változója legyen módosítható. Növeld eggyel
túlcsordulás nélkül, majd assertionnel ellenőrizd, hogy pozitív. Az
üzemi működés -DNDEBUG mellett se változzon.
Diagnosztika. Egy C17 build feltétel nélkül
használ komplex számokat és memcpy_s-t. Numerikus hibát
kizárólag errno alapján keres, miközben
-ffast-math kapcsolóval fordít. Milyen különálló
ellenőrzések hiányoznak?
A DBL_EPSILON az egység körüli reprezentációt jellemzi.
A megfelelő tolerancia függ a nagyságrendtől, a számítás hibájától és az
alkalmazás követelményeitől. A wchar_t jelentése
implementáció- és locale-függő; egy kódegység ráadásul nem szükségképpen
teljes kódpont.
Ellenőrzési feltétel: a válasz különítse el a reprezentációt az alkalmazási pontosságtól, illetve a típust a kódolástól.
Az eredeti deklaráció legyen uintmax_t samples = 42;,
majd következzen:
/* A numeric.c main függvényébe illesztendő C17-részlet. */
if (samples == UINTMAX_MAX) {
return EXIT_FAILURE;
}
++samples;
assert(samples > 0);Az unsigned aritmetika körbefordulása definiált lenne, de megsértené a számláló szerződését. A guard ezt előzi meg.
gcc -std=c17 -Wall -Wextra -Wpedantic -DNDEBUG \
numeric.c -lm -o numeric-releaseEllenőrzési feltétel: mindkét buildben a számláló
várt értéke 43; UINTMAX_MAX kezdőértéknél mindkettő hibával
térjen vissza.
Külön kell vizsgálni a komplex támogatást, az Annex K implementációs
és deklarációs feltételeit, valamint a math_errhandling
jelzését. A lebegőpontos környezetre támaszkodó kódhoz a fordító
támogatását és optimalizálási beállításait is ellenőrizni kell.
Ellenőrzési feltétel: a konfiguráció vagy igazolja a szükséges képességeket, vagy világosan elutasítja az adott buildet; nem helyettesíti a hiányzó szerződést puszta függvénynév-egyezéssel.
Elsődleges továbbtanulási kiindulópont a WG14 dokumentumgyűjteménye és a GNU C Library kézikönyve. Ezek itt kutatási kiindulópontok, nem konkrét C17-szakaszok ellenőrzött bizonyítékai. A szabványverziót és a használt libc dokumentációjának verzióját külön kell azonosítani.
A fejlécválasztás helyes sorrendje: feladat, nyelvi
szerződés, opcionális képesség, implementációs támogatás,
hibakezelés. Az assertion nem üzemi validáció; a széles
karakter nem automatikusan Unicode; a numerikus hibajelzés nem kizárólag
errno; a standard név pedig nem jelenti minden környezetben
ugyanazt a rendelkezésre állást.