Egy hálózati hosszmező, egy előjeles hibajelzés és egy memóriaméret gyakran ugyanabban a kifejezésben találkozik. Matematikailag mindegyik egész szám, C-ben azonban a típusuk dönti el, hogyan történik az összehasonlítás, a kivonás vagy a szorzás. A hibás konverzió következménye lehet elutasított érvényes index, óriásira változó negatív hossz vagy túl kicsire számított allokáció.
Az Egész típusok és adatmodellek alapján már különválasztjuk a típus nevét, tartományát és reprezentációját. Most a kifejezés típusát követjük: ezt általában nem az eredményt fogadó változó, hanem az operátor és az operandusok típusa határozza meg.
Az alábbi nyelvi szabályok az ISO C17-re vonatkoznak. A konkrét
számpéldák laborprofilja Linux, x86-64 System V ABI, LP64,
CHAR_BIT == 8; itt az int 32 bites, a
long 64 bites, a size_t pedig
unsigned long. A használt exact-width típusok rendelkezésre
állását külön feltételezzük. A példák eredményei levezetett elvárások,
nem dokumentált futtatási eredmények.
Az egész promóciók (integer promotions) a
kis rangú egész típusokat emelik int vagy
unsigned int típusra. A rang (integer conversion
rank) a nyelv által meghatározott típusviszony; nem egyszerűen a
tárolási méret másik neve.
A szokásos kis egész típusokra, például _Bool,
char, signed char, unsigned char,
short és unsigned short esetén:
int az eredeti típus minden
értékét képes ábrázolni, a cél int;unsigned int.Nem az aktuális érték számít. Egy unsigned short akkor
is ugyanarra a típusra promótálódik, ha éppen nulla van benne. Az
enumerációk és bitmezők is érintettek lehetnek, saját típusszabályaik
szerint.
C17-részlet a megadott laborprofilra:
uint16_t received = 4;
uint16_t header = 8;
int payload = received - header;A levezetés:
uint16_t értéktartománya
.int típusa ezt teljes egészében
ábrázolja.int típusra promótálódik.int aritmetikával történik: az eredmény
-4.Az unsigned változók tehát nem garantálják, hogy az aritmetikai művelet is unsigned lesz.
Az olyan bináris operátoroknál, mint a +,
-, *, / és az összehasonlítások,
az egész promóciókat a szokásos aritmetikai konverziók
(usual arithmetic conversions) követik.
Két egész operandusra, a promóciók után:
A laborprofilon:
| Operandustípusok | Közös típus | Indok |
|---|---|---|
int, unsigned int |
unsigned int |
Az unsigned rang nem kisebb |
long, unsigned int |
long |
A 64 bites long minden 32 bites unsigned értéket
ábrázol |
int, size_t |
unsigned long |
A size_t unsigned, rangja nagyobb |
Összehasonlításnál ez a vizsgált operandusok közös
típusa; az összehasonlítás eredménye C-ben int, értéke
nulla vagy egy.
Ezek a szabályok nem minden operátorra alkalmazhatók változtatás nélkül. Az értékadás például a kiszámított értéket a bal oldal típusára konvertálja; a léptetőoperátorok külön szabályait a Kifejezések kiértékelése, sequencing és bitműveletek tárgyalja.
C17-részlet a laborprofilra:
int index = -1;
size_t length = 100;
if (index < length) {
/* Nem ez az ág következik a típuskonverziókból. */
}A közös típus size_t, vagyis unsigned long.
Az egész érték unsigned típusra konvertálásakor az eredmény a cél
értéktartományához tartozó moduló szerinti érték.
Ha a cél maximuma , akkor:
Az összehasonlítás így a profilon SIZE_MAX < 100,
tehát hamis. A matematikailag kisebb negatív index a konverzió miatt
nagy pozitív értékként szerepel.
A helyes ellenőrzés előbb a jelentést, utána a reprezentációt kezeli.
Hordozható C17-részlet, <stdint.h> használatával:
if (index >= 0 &&
(uintmax_t)index < (uintmax_t)length) {
/* Az index a [0, length) tartományban van. */
}A && rövidzáras: negatív indexnél a jobb oldal
nem értékelődik ki. A uintmax_t minden unsigned egész típus
értékeit képes ábrázolni; a nemnegatív int érték is
belefér.
A megszokott (size_t)index < length változat
ugyancsak megfelelő, ha előtte ellenőrizzük a nemnegativitást, és az
alkalmazott profilban minden nemnegatív int belefér a
size_t típusba. Ez a választott LP64 profilban teljesül, de
nem önálló, minden C-implementációra érvényes axióma.
Ha a művelet eredménytípusa értékbites unsigned egész típus, az összeadás, kivonás és szorzás eredménye modulo értendő:
Ez definiált körbefordulás (wraparound), de méretszámításban rendszerint programhiba. Egy körbefordult kéréshez túl kevés memória foglalható.
Ezzel szemben, ha egy előjeles aritmetikai eredmény nem ábrázolható
az eredménytípusban, az nem definiált viselkedés, UB.
Az INT_MAX + 1 nem hordozható módja az INT_MIN
előállításának. Az optimalizáló nem köteles a processzor esetleges
körbefordulását megőrizni.
Fontos különbség az aritmetika és az utólagos konverzió között:
/* C17-részlet; uint8_t létezik. */
uint8_t a = 250;
uint8_t b = 10;
uint8_t result = a + b;Itt az operandusok int típusra promótálódnak. A
260 eredmény ábrázolható int-ben, tehát nincs
aritmetikai túlcsordulás. Ezután az értékadás uint8_t
típusra konvertál:
.
Szűkítésnek (narrowing conversion) itt az olyan konverziót nevezzük, amelynél a cél nem képes a forrástípus minden értékét ábrázolni. C17-ben:
A signed célra történő konverzió tehát nem ugyanaz a szabálykategória, mint a signed aritmetikai túlcsordulás.
Az explicit cast konverziós kérés, nem tartományellenőrzés. C17-részlet:
if (wire_length <= UINT16_MAX) {
uint16_t short_length = (uint16_t)wire_length;
/* short_length itt veszteségmentesen előállított érték. */
}Feltétel: wire_length típusa uint32_t, és
mindkét exact-width típus elérhető. Signed forrásnál az alsó határt is
ellenőrizni kell.
Különösen veszélyes minta:
/* Hibás lehet: a cast túl későn történik. */
size_t bytes = (size_t)(count * stride);Ha count és stride típusa int,
előbb int szorzás történik. A későbbi cast nem javítja meg
a korábbi UB-t.
Legyen
két size_t érték,
.
Az összeadás pontosan akkor fér el, ha:
A szorzás pedig akkor fér el, ha:
Bizonyításvázlat. Mivel , az kivonás biztonságos. Pozitív mellett az egyenlőtlenség osztható -vel; egész esetén a lefelé kerekített hányados adja a pontos korlátot. A nulla szorzót külön kezeljük, így nincs nullával osztás.
A következő teljes C17-program egy már dekódolt hálózati elemszámból számít méretet. Nem végez allokációt: annak külön hibaszerződése a malloc, calloc, free és allokációs szerződés témája. A 24 és 16 itt alkalmazásszerződésből származó, C bájtban megadott méret, nem struktúralayout.
#include <stdint.h>
#include <inttypes.h>
#include <stdio.h>
#ifndef UINT32_MAX
#error "This example requires uint32_t."
#endif
int main(void)
{
uint32_t wire_count = 0;
/* Kizárólag ismert, ábrázolható tesztérték beolvasása. */
if (sscanf("42", "%" SCNu32, &wire_count) != 1) {
fputs("conversion failed\n", stderr);
return 1;
}
if ((uintmax_t)wire_count > (uintmax_t)SIZE_MAX) {
fputs("count does not fit size_t\n", stderr);
return 1;
}
const size_t count = (size_t)wire_count;
const size_t stride = 24;
const size_t header = 16;
if (stride != 0 && count > SIZE_MAX / stride) {
fputs("payload size overflow\n", stderr);
return 1;
}
const size_t payload = count * stride;
if (payload > SIZE_MAX - header) {
fputs("total size overflow\n", stderr);
return 1;
}
const size_t total = header + payload;
printf("count=%" PRIu32 " total=%zu\n", wire_count, total);
return 0;
}Az sscanf nem tulajdonló hozzáférést kap a
wire_count helyi objektumhoz, és a címet nem tárolja el. Az
objektum tárolója és élettartama a main blokkjához kötött;
a hívás alatt nem érvénytelenedik. A bemeneti sztringliterál statikus
tárolási idejű. A program saját felismert hibakimenete üzenet és nem
nulla kilépési státusz.
Választott, itt nem ellenőrzött labor-eszközlánc: GCC 14.2.0 vagy Clang 19.1.0, glibc 2.40, a fent megadott Linux-targettel. Fordítás külön laborlépésként:
gcc -std=c17 -O2 -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion sizes.c -o sizes-gcc
clang -std=c17 -O2 -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion sizes.c -o sizes-clangA számtani levezetés szerint . Ez várt eredmény, nem mérés. Az aritmetikai ellenőrzés mellett valódi szolgáltatásban külön erőforráskorlát is szükséges: a reprezentálható méret nem feltétlenül elfogadható méret.
A variadikus printf nem következteti ki az argumentum
típusát a formátumszövegből. A formátum és az átadott típus össze nem
illése UB-t okozhat.
| Értéktípus | Kiírás | Beolvasás |
|---|---|---|
size_t |
%zu |
%zu, size_t * argumentummal |
ptrdiff_t |
%td |
%td, ptrdiff_t * argumentummal |
uint32_t |
"%" PRIu32 |
"%" SCNu32, uint32_t * argumentummal |
int64_t |
"%" PRId64 |
"%" SCNd64, int64_t * argumentummal |
A PRI és SCN makrókat az
<inttypes.h> biztosítja; az exact-width változatok az
adott típus elérhetőségéhez kötődnek. A kiírási és beolvasási makrók nem
felcserélhetők.
A SCN makró csak a típushelyes
formátumot adja meg. C17-ben a célobjektumban nem ábrázolható
numerikus scanf-eredmény UB lehet; a visszatérési érték
ellenőrzése önmagában nem teszi biztonságossá a tetszőleges bemenetet.
Ezért használ a példa ismert literált. Külső numerikus szöveghez
célszerű például strtoumax-alapú feldolgozás, külön
előjel-, tartomány-, végpozíció- és hibavizsgálattal; ennek részleteit a
Formázás és megbízható szövegfeldolgozás
tárgyalja.
uint16_t szorzása a profilon
int szorzás; nagy operandusokkal túlcsordulhat.A laborprofilon a és b típusa
uint16_t, értékük 3 és 5. Mi a különbség az
int x = a - b; és a size_t y = a - b; között?
Nevezd meg a kivonás és az értékadás konverzióit.
Írj frame_fits(count, stride, header, limit) függvényt
size_t paraméterekkel, amely túlcsordulás nélkül
eldönti:
A nulla stride is legyen megengedett.
A laborprofilon elemezd ezt az elkülönített, hibás C17-részletet:
int count = 100000000;
int stride = 24;
size_t bytes = (size_t)(count * stride);
printf("%u\n", bytes);Keress két egymástól független hibát. UB esetére ne jósolj kimenetet.
Mindkét operandus int típusra promótálódik, ezért a
kivonás eredménye int típusú -2. Az
x értéke -2; a y
inicializálásakor külön unsigned konverzió történik:
Ellenőrzési feltétel: a levezetés külön kezelje az operátor és a célváltozó típusát.
C17-függvényrészlet; önálló programhoz hívó és main
szükséges:
#include <stdbool.h>
#include <stddef.h>
bool frame_fits(size_t count, size_t stride,
size_t header, size_t limit)
{
if (header > limit) {
return false;
}
return stride == 0 ||
count <= (limit - header) / stride;
}A kivonást az első ellenőrzés védi, az osztást a rövidzáras
||. Szorzást egyáltalán nem végzünk.
Ellenőrzési esetek: (42,24,16,1024) igaz,
(43,24,16,1024) hamis; nulla stride mellett
kizárólag a fejléc mérete számít.
A szorzás int típusban történik. A matematikai eredmény
,
amely a profil INT_MAX értékénél nagyobb: ez UB, még a cast
előtt.
Emellett %u helyett %zu szükséges a
size_t kiírásához. A javítás sorrendje: előjel- és
tartományvizsgálat, konverzió size_t típusra, szorzási
korlátvizsgálat, majd szorzás és típushelyes kiírás. A warning hasznos
bizonyíték lehet egy hibára, de hiánya nem bizonyítja a helyességet.
Egész kifejezéseknél először a promóciókat, majd a közös típust, végül az értékadás konverzióját vizsgáld. Az unsigned körbefordulás definiált, de méretszámításban veszélyes; a signed aritmetikai túlcsordulás UB. A cast nem ellenőrzés. Negatív értékeket unsigned konverzió előtt, összeadást és szorzást pedig a művelet végrehajtása előtt kell tartományvizsgálattal védeni.