Egy számláló, memóriaméret vagy protokollmező típusának kiválasztása
reprezentációs szerződés: milyen értékeket kell tárolni, és milyen
feltételezések maradhatnak igazak másik platformon is? C-ben az
int nem jelent automatikusan 32 bitet, a long
nem feltétlenül pointerméretű, és még a „bájt” sem szükségképpen nyolc
bit.
A Szabványok, dialektusok és hordozhatósági szerződés megkülönböztetését követve külön kezeljük az ISO C17 garanciáit, az ABI által rögzített tulajdonságokat és a saját programmal megfigyelt értékeket. A labor a main, startup, argumentumok és környezet részben megismert hosted programmodellre épül.
Az egész típus (integer type) diszkrét egész értékek tárolására szolgál. Egy típus értéktartománya nem azonos a számára fenntartott tároló bitmintáinak halmazával: a reprezentáció kitöltőbiteket (padding bits) is tartalmazhat.
ISO C17. A sizeof eredményének egysége
egy C bájt, amelyet a könyvben C_byte néven
különböztetünk meg a nyolcbites oktettől. Egy C bájt bitszámát a
<limits.h> fejléc CHAR_BIT makrója adja
meg:
Mindig teljesül:
Egy T típusú objektum tárolója tehát
bitből áll. Ez tárolási bitszám, nem feltétlenül az értéket hordozó bitek száma.
A char, signed char és
unsigned char három különböző típus. A sima
char a másik kettő valamelyikével azonos tartományú és
reprezentációjú; a választás implementáció által meghatározott.
Ellenőrzésére a CHAR_MIN alkalmas: ha nulla, a sima
char előjel nélküliként viselkedik.
A short, int, long és
long long önmagában előjeles típust jelöl. Például a
short, short int és
signed short int ugyanaz a típus. Az előjel nélküli
változatokat az unsigned jelöli; az önálló
unsigned jelentése unsigned int.
ISO C17 minimumkövetelmények: az alábbi tartományokat a típusoknak legalább le kell fedniük.
| Előjeles típus | Garantáltan lefedett tartomány | Előjel nélküli pár garantált tartománya |
|---|---|---|
signed char |
||
short |
||
int |
||
long |
||
long long |
A tényleges határokat a <limits.h> adja: például
INT_MIN, INT_MAX, UINT_MAX,
illetve LONG_MIN, LONG_MAX,
ULONG_MAX. A long long megfelelői
LLONG_MIN, LLONG_MAX és
ULLONG_MAX.
A méretekre:
Az egyenlőség megengedett. Az int és a long
attól még külön típus, hogy ugyanakkora.
Bizonyításvázlat az unsigned tartományhoz. Ha egy előjel nélküli típusnak értékbitje van, a legnagyobb érték minden értékbitet egyre állít:
Tartománya ezért . A lényeges feltétel az értékbitek száma: kitöltőbitek jelenlétében nem helyettesíthető automatikusan a teljes tárolási bitszámmal.
Az adatmodell (data model) néhány alapvető
típus szélességének együttese. Az ILP32 az int,
long és pointer 32 bites szélességére utal. LP64 esetén a
long és a pointer 64 bites, az int 32 bites.
LLP64 esetén a long long és a pointer 64 bites, de az
int és a long 32 bites.
Az alábbi ABI-profilokban
CHAR_BIT == 8; a méretek C bájtban szerepelnek. Ezek
profilhoz kötött értékek, nem ISO-garanciák és nem közölt mérési
eredmények.
| Típus | ILP32: Linux i386, System V | LP64: Linux x86-64, System V | LLP64: Windows x64 |
|---|---|---|---|
char |
1 | 1 | 1 |
short |
2 | 2 | 2 |
int |
4 | 4 | 4 |
long |
4 | 8 | 4 |
long long |
8 | 8 | 8 |
void * |
4 | 8 | 8 |
| Fordítási profil | GCC 14.2.0,
-std=c17 -m32 |
GCC 14.2.0,
-std=c17 -m64 |
MSVC 19.40, x64,
/std:c17 |
A modellnevek önmagukban nem határozzák meg például a
short méretét vagy a típusok igazítását; a táblázat ezért
konkrét ABI-példákat is megnevez.
Gyakorlati következmény: a „64 bites rendszer” nem elegendő egy
típusméret meghatározásához. Egy Linuxon pointerméretűnek bizonyuló
unsigned long Windows x64 alatt csak 32 bites lehet. A gépi
szóhossz, az egész típusok mérete és a pointerméret külön
tulajdonság.
A táblázat szerializálási formátumot sem definiál. Egy
long objektum tárolójának közvetlen kiírása nem eredményez
hordozható protokollmezőt; ehhez külön reprezentációs és
bájtsorrend-szerződés szükséges.
ISO C17. A <stdint.h> többféle
típuscsaládot kínál.
Az exact-width, vagyis pontos szélességű típusok
neve például int32_t és uint32_t. Ezek
pontosan a megadott bitszámúak, kitöltőbit nélkül. Az
int32_t tartománya
,
a uint32_t tartománya
.
Ezek a típusok opcionálisak: csak megfelelő
alaptípus rendelkezésre állásakor léteznek. Elérhetőségük a hozzájuk
tartozó határmakróval ellenőrizhető, például
#ifdef UINT32_MAX segítségével.
A pontos bitszám nem jelent rögzített C bájtszámot. Ha
uint32_t létezik:
Ez nyolcbites C bájttal négy, tizenhat bitessel két C bájt. Utóbbi
környezetben uint8_t nem létezhet.
A least-width család, például
uint_least32_t, legalább a megadott szélességhez szükséges
tartományt biztosítja, a követelményt kielégítő legkisebb méretű
megfelelő egész típussal. A fast-width család, például
uint_fast32_t, az implementáció által rendszerint
leggyorsabbnak kijelölt, legalább ilyen tartományú típust adja. A
fast nem egy konkrét alkalmazáson végzett teljesítménymérés
eredménye; egy uint_fast32_t lehet 64 bites is.
A 8, 16, 32 és 64 szélességű least és fast
családok kötelezők. A megfelelő határmakrók például
UINT_LEAST32_MAX és INT_FAST32_MIN.
A typedef-nevek nem hoznak létre új, elkülönült egész
típusokat. A uint32_t valamely létező egész típus
alternatív neve; nem szabad feltételezni, hogy ez mindenhol
unsigned int.
| Típus | Rendeltetés | Fontos korlát |
|---|---|---|
size_t |
A sizeof eredményének előjel nélküli típusa |
Nem szükségképpen unsigned long; felső határa
SIZE_MAX |
ptrdiff_t |
Pointerkivonás előjeles eredménytípusa | A különbségnek ábrázolhatónak kell lennie |
uintptr_t |
Opcionális előjel nélküli típus void * oda-vissza
konverziójához |
Nem hordozható hálózati címformátum |
A size_t és ptrdiff_t elérhető a
<stddef.h> fejlécből. Pointerkivonásnál az azonos
tömbhöz tartozás feltételeit is teljesíteni kell; ezt a Pointeraritmetika, objektumhatár és provenance
tárgyalja. A különbség elemekben, nem általában C bájtokban értendő.
A uintptr_t a <stdint.h> opcionális
típusa. Szerződése szerint egy érvényes void * érték ilyen
egésszé, majd vissza void * típussá alakítva az eredetivel
egyenlő pointert ad. Ez nem jogosít fel tetszőleges egész érték
pointerként történő használatára, és nem konzerválja a hivatkozott
objektum élettartamát.
ISO C17. Az általános előjeles egész típusokhoz három reprezentáció megengedett: előjel–abszolútérték (sign-and-magnitude), egyes komplemens (ones’ complement) és kettes komplemens (two’s complement).
Nyolc tárolási bit, kitöltőbit nélküli ábrázolás és minden bitminta értékként kezelése mellett az első kettő , a kettes komplemens tartományt ad. Az első kettő negatív zérót is kódol. Ez magyarázza a C17 minimumtáblázatának szimmetrikus negatív határait.
Fontos kivétel: az opcionális intN_t típusok már
C17-ben is kettes komplemensűek.
C23-eltérés. Az előjeles egész típusok
reprezentációja kettes komplemensre szűkül. Ettől azonban az
int nem lesz kötelezően 32 bites, a C bájt nem lesz
kötelezően oktett, és a típusméretek sem válnak platformfüggetlenné.
A reprezentációt a műveleti szabályoktól is el kell választani: az előjeles túlcsordulás C23-ban sem válik általánosan körbeforduló aritmetikává. A konverziókat és túlcsordulást a Promóciók, signedness és túlcsordulás rész tárgyalja.
A következő önálló program minimuma ISO C17, hosted környezet. Választott laborprofil: Linux x86-64 System V, LP64, glibc 2.40; fordítók GCC 14.2.0 és Clang 19.1.7. A helyi eszközverziókat külön ellenőrizni kell. A példához itt nem tartozik tényleges fordítási vagy futtatási eredmény.
/* integer_probe.c */
#include <inttypes.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
printf("CHAR_BIT=%d\n", CHAR_BIT);
printf("sizeof: char=%zu short=%zu int=%zu "
"long=%zu long long=%zu void*=%zu\n",
sizeof(char), sizeof(short), sizeof(int),
sizeof(long), sizeof(long long), sizeof(void *));
printf("char: %jd..%ju\n",
(intmax_t)CHAR_MIN, (uintmax_t)CHAR_MAX);
printf("int: %d..%d; unsigned int: 0..%u\n",
INT_MIN, INT_MAX, UINT_MAX);
printf("long: %ld..%ld; unsigned long: 0..%lu\n",
LONG_MIN, LONG_MAX, ULONG_MAX);
printf("long long: %lld..%lld\n", LLONG_MIN, LLONG_MAX);
printf("size_t: %zu C_byte; max=%zu\n",
sizeof(size_t), (size_t)SIZE_MAX);
printf("ptrdiff_t: %zu C_byte; %td..%td\n",
sizeof(ptrdiff_t),
(ptrdiff_t)PTRDIFF_MIN, (ptrdiff_t)PTRDIFF_MAX);
#ifdef UINT32_MAX
uint32_t field = 42;
printf("uint32_t: %zu C_byte; value=%" PRIu32 "\n",
sizeof(field), field);
#else
puts("uint32_t nem erheto el");
#endif
#ifdef UINTPTR_MAX
printf("uintptr_t: %zu C_byte\n", sizeof(uintptr_t));
#else
puts("uintptr_t nem erheto el");
#endif
return 0;
}Fordítás a megadott kiadásokra mutató gcc és
clang parancsokkal:
gcc -std=c17 -m64 -Wall -Wextra -Wpedantic -Wconversion -O0 -g integer_probe.c -o integer_probe-gcc
clang -std=c17 -m64 -Wall -Wextra -Wpedantic -Wconversion -O0 -g integer_probe.c -o integer_probe-clangA formátumok a típusok szerződését követik: size_t
esetén %zu, ptrdiff_t esetén %td,
uint32_t esetén a <inttypes.h> által
adott PRIu32 használható. A char határainak
kiírásánál az intmax_t és uintmax_t minden
megfelelő előjeles, illetve előjel nélküli egész érték befogadására
alkalmas típust biztosít.
A labor megfigyeléseit két lépésben értelmezzük. Először ellenőrizzük az ISO-minimumokat és méretrelációkat. Ezután összevetjük az adatokat a kiválasztott ABI-profillal. Egy LP64-nek megfelelő eredmény nem bizonyítja, hogy ugyanaz a program minden platformon ugyanilyen méreteket kap.
A sizeof(T) * 8 kifejezés rejtett oktettfeltételezés.
Tárolási bitszámhoz CHAR_BIT szükséges, értéktartományhoz
pedig a megfelelő határmakró.
A uint32_t feltétel nélküli használata elrejti az
opcionális típus követelményét. Ha pontos szélesség kell, ellenőrizzük
az elérhetőséget; ha csak legalább ekkora tartomány kell, válasszunk
uint_least32_t típust.
A méretérték unsigned long változóba másolása LP64-en
működőképesnek tűnhet, LLP64-en azonban értékvesztést okozhat.
Méretekhez alapértelmezésben size_t való.
A formátumspecifikátort sem a feltételezett mérethez, hanem a
tényleges típushoz kell választani. Az azonos bitszám önmagában nem tesz
helyessé egy variadikus printf hívást.
Magyarázó feladat. Mely állítások ISO
C17-garanciák: sizeof(char) == 1,
CHAR_BIT == 8, sizeof(int) == 4,
INT_MAX >= 32767? Következik-e nyolcbites C bájtból a
négybájtos int?
Kódolási feladat. Egészítsd ki a laborprogramot
a uint_least32_t és uint_fast32_t méretének és
felső határának kiírásával. Használj típushelyes formátumokat.
Diagnosztikai feladat. Az alábbi, már deklarált
payload objektumra vonatkozó C17-részlet miért
hordozhatósági hiba LLP64-en? Javítsd ki. Feltételezz olyan
implementációt, amelyen az objektum mérete meghaladhatja az
ULONG_MAX értékét.
/* Hibás hordozhatósági feltételezés; szemléltető részlet. */
unsigned long bytes = sizeof payload;
printf("%lu\n", bytes);Az első és a negyedik állítás garantált. A másik kettő platformfüggő.
Nyolcbites C bájttal az int legalább két C bájtot
igényel, mert legalább 65535 különböző értéket kell tudnia tárolni,
miközben egyetlen ilyen bájtnak csak 256 bitmintája van. Ebből nem
következik a négybájtos méret.
Ellenőrzési feltétel: az indoklás tartományból és szabványos garanciából induljon ki, ne egyetlen gép méréséből.
A következő C17-részlet a laborprogram main függvényébe
illeszthető; annak fejléceit és buildparancsait használja:
printf("uint_least32_t: %zu C_byte; max=%" PRIuLEAST32 "\n",
sizeof(uint_least32_t), UINT_LEAST32_MAX);
printf("uint_fast32_t: %zu C_byte; max=%" PRIuFAST32 "\n",
sizeof(uint_fast32_t), UINT_FAST32_MAX);Ellenőrzési feltétel: mindkét felső határ legalább . A két méret egyezése nem követelmény, és a nagyobb méret önmagában nem bizonyít nagyobb végrehajtási sebességet.
Az LLP64-profilban a size_t 64 bites, az
unsigned long 32 bites. A kiírás formátuma a hibás
változatban helyes a változóhoz, de a méret már az inicializáláskor
elveszhet.
Javított C17-részlet, <stddef.h> és
<stdio.h> mellett:
size_t bytes = sizeof payload;
printf("%zu\n", bytes);Ellenőrzési feltétel: a sizeof
eredménye ne kerüljön szűkebb típusba a tárolás vagy kiírás miatt. A
nagy objektum tényleges létrehozása nem szükséges a típushiba
felismeréséhez.
A típusnév, a tárolási méret és az értéktartomány három külön
információ. A sizeof C bájtban mér, a CHAR_BIT
a bájt bitszámát, a határmakrók az értéktartományt adják. Az ABI konkrét
méreteket rögzít, de ezek nem ISO-garanciák. Pontos szélességhez
ellenőrzött exact-width típus, minimális tartományhoz least
típus, objektummérethez size_t használható.