Ez a fejezet nem új mechanizmust vezet be, hanem a korábbi részek újraelérésének eszközeit rendezi össze: hibából szabályhoz, döntési helyzetből szerződéshez, tanulási célból ellenőrizhető bizonyítékhoz vezető útvonalakat ad. A cél az, hogy a kurzus anyaga ne lineáris olvasmányként, hanem kereshető referenciaként is használható legyen. Ezért itt nem ismételjük meg a definíciókat és a levezetéseket; minden sor egy korábbi részre mutat, ahol a feltételek és az indoklás megtalálhatók. A táblázatok használatának szabálya: előbb a megfigyelt tünetet vagy a meghozandó döntést azonosítod, aztán kikeresed a sort, végül visszalépve elolvasod az adott rész előfeltételeit. Egy táblázatsor önmagában nem bizonyíték, csak mutató.
A visszatérő hibaforrás nem a szintaxis, hanem hogy a felek nem ugyanazt értik tulajdonláson. Az alábbi tábla a kérdést fordítja döntéssé; a részletes szerződéseket a Tulajdonlási szerződés és cleanup ágak, az Owner, view és erőforrásszerződés C++ API-ban, a Rule of zero, three és five és a Move constructor, moved-from állapot és std::move tartalmazza.
| Helyzet | Választás | Következmény | Tipikus hiba |
|---|---|---|---|
| A függvény új erőforrást ad | visszatérési érték owner (unique_ptr, dokumentált
free) |
a hívó felel a lezárásért | nyers pointer ownerként, dokumentálatlanul |
| A függvény csak olvas | const T*, std::string_view,
std::span |
nincs másolás, nincs tulajdonátadás | a view túléli az ownert |
| A függvény átveszi a tulajdont | érték szerinti owner paraméter vagy
unique_ptr&& |
a hívó tudja, hogy átadta | const& átvétel, majd belső free |
| Az erőforrás lehet „nincs” | unique_ptr, std::optional owner |
a hiány típusban látszik | nullable nyers pointer ownerként |
| A paramétert eltároljuk | érték vagy owner-másolat | az élettartam független lesz | string_view tagként tárolva |
| Nagy objektumot adunk át | const& vagy span |
nincs másolás | temporary const&, amely a kifejezés végén
elhal |
A döntés lényege mindig ugyanaz a kérdés: ki zárja le az erőforrást, és meddig él a hozzáférés? Ha a válasz nem fér bele egy mondatba, az API szerződése hiányos.
A következő C példa a „tulajdonos visszatérési érték” sort mutatja; a fordításhoz szükséges include-ok és a parancs is szerepelnek. A futtatás és a viselkedés ellenőrzése a saját laborprofilodban történik, ezt a fejezet nem állítja mérésnek.
/* c17: gcc -std=c17 -Wall -Wextra -Wpedantic -O2 -o dup dup.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Szerződés: a visszatérési érték owner, a hívó free()-eli.
Bemenet: borrowed, csak a hívás idejére kell érvényesnek lennie. */
char *dup_upper(const char *borrowed)
{
if (borrowed == NULL) return NULL;
size_t n = strlen(borrowed);
char *owner = malloc(n + 1);
if (owner == NULL) return NULL;
for (size_t i = 0; i <= n; ++i) {
char c = borrowed[i];
owner[i] = (c >= 'a' && c <= 'z') ? (char)(c - 'a' + 'A') : c;
}
return owner;
}
int main(void)
{
char *s = dup_upper("devops");
if (s == NULL) { fputs("alloc failure\n", stderr); return 1; }
puts(s);
free(s); /* owner lezárása */
s = NULL; /* a dangling újrahasználat megelőzése */
return 0;
}A C++ változat ugyanezt a döntést mutatja be a nem tulajdonló
string_view és a tulajdonló std::string
párral; a lényeg, hogy a view-t nem tároljuk a hívás után.
// c++20: g++ -std=c++20 -Wall -Wextra -Wpedantic -fsanitize=address -o count count.cpp
#include <cstdio>
#include <string>
#include <string_view>
// Szerződés: s csak a hívás idejére kell érvényesnek lennie (borrowed).
std::size_t count_upper(std::string_view s)
{
std::size_t n = 0;
for (char c : s)
if (c >= 'A' && c <= 'Z') ++n;
return n;
}
int main()
{
std::string owner{"DevOps"}; // owner: élettartama main vége
std::printf("%zu\n", count_upper(owner)); // a view az ownerre mutat
return 0;
}A tiltott minta ugyanennek a tükörképe:
std::string_view v = std::string{"tmp"}; után a temporary
elhal, a view dangling lesz. A helyes változat vagy owner stringet tart,
vagy a view élettartamát a hívásra korlátozza; erről a Temporary, value category és dangling és a Borrowed range, dangling és iterator lifetime
szól.
Az alábbi index nem típuslexikon, hanem invalidációs térkép: azt mutatja, melyik művelet teszi érvénytelenné a hozzáférést. A részletek a Pointeraritmetika, objektumhatár és provenance, a Tárolóválasztási és invalidációs labor és a span, byte és bounds-szerződések részekben találhatók.
| Hozzáférés | Érvénytelenné teszi | Ki az owner |
|---|---|---|
T* nyers pointer |
free, realloc, a mögöttes objektum
élettartamának vége |
a szerződés mondja meg, nem a típus |
T[N] tömb |
a tömb hatókörének és élettartamának vége | a tömböt tartalmazó objektum |
std::span<T> |
a mögöttes tároló átméretezése vagy elhalása | nem tulajdonol |
std::string_view |
az owner string módosítása, elhalása | nem tulajdonol |
std::string::data() |
insert, erase, újraallokálás |
a string objektum |
vector::data(), vector iterator |
push_back, reserve,
insert |
a vector objektum |
| asszociatív tároló iteratora | erase az adott elemen |
a tároló objektum |
Formálisan: ha egy hozzáférés az owner
tartományára mutat, akkor érvényes mindaddig, amíg az owner objektum
élettartama tart és a tartomány nem invalidálódott. Az end
és az one-past érték nem dereferálható; ez a fél-nyílt tartomány szerződése. Az invalidáció nem
mindig jár együtt memóriahibával: a string kis méretű
optimalizálása miatt a buffer helyben is átíródhat, ezért a régi
data() pointer csendesen más tartalmat mutathat.
Ez a táblázat kizárólag a két nyelv eltérésének gyors felidézésére való; a C oldalt a Scope, linkage, storage duration és lifetime és az Effective type, aliasing és objektumreprezentáció, a C++ oldalt az Az inicializálás különböző alakjai és a Tároló újrafelhasználása és C++ objektumszabályok tárgyalja.
| Kérdés | C (C17/C23) | C++ (C++20) |
|---|---|---|
| Élettartam kezdete | tárolási idő szerint: statikus a program indulásakor, automatikus a blokkba lépéskor, allokált a foglaláskor | konstrukció befejezésekor; implicit-lifetime típusnál implicit létrehozással is |
| Nyers tárolóban objektum | nincs „objektum-létrehozás”; az allokált tároló effective type-ját
az első nem karaktertípusú írás vagy a memcpy forrása
adja |
implicit-lifetime típusnál a malloc,
operator new, std::allocator::allocate,
memcpy/memmove,
unsigned char/std::byte tömb implicit
létrehozza az objektumot; más típushoz konstrukció kell (placement new,
construct_at) |
| Alias-szabály | effective type és strict aliasing; karaktertípuson át minden olvasható | dinamikus típus és strict aliasing ([basic.lval]);
char, unsigned char, std::byte
kivétel; átlátszó helyettesítés híján std::launder |
| Nem inicializált érték | indeterminate; olvasása UB, ha csapdareprezentáció lehet, vagy ha a
címét soha nem vett automatikus változót olvassuk;
unsigned char-ként olvasható |
default inicializált automatikus változó értéke indeterminate;
olvasása UB, kivéve az unsigned char/std::byte
egyes másolási eseteit (C++26: erroneous behaviour) |
| Kijelölt inicializálás | C99 óta: tetszőleges sorrend, beágyazott (.a.b) és
tömbdesignator ([3] =), keverhető pozícionálissal,
felülírás megengedett |
C++20 óta: csak aggregate-nél, csak közvetlen tagra, deklarációs sorrendben, nem keverhető pozícionálissal, nincs tömb- és beágyazott designator |
| Lista-inicializálás | {} csak aggregate-re és skalárra; szűkítés
megengedett |
{} minden típusra; szűkítés tiltott;
std::initializer_list konstruktor elsőbbsége |
A gyakorlati következtetés: a C++ kódban a „nyers memória + cast”
minta nem átvihető gondolkodás nélkül C-ből, mert ott az objektum
élettartama nem a byte-ok létezéséhez, hanem a konstrukcióhoz kötődik —
kivéve az implicit-lifetime típusokat, amelyeket a
malloc/memcpy jellegű műveletek C++20-ban
implicit módon létrehozhatnak (lásd Tároló
újrafelhasználása és C++ objektumszabályok (20.6)). Erről bővebben a
C és C++: azonos szintaxis, eltérő szemantika
szól.
A könyv következetesen megkülönbözteti az állítás forrásának típusát. A jelölés a szövegben az állítás mellett áll, nem helyettesíti a hivatkozást, és nem garantálja, hogy az adott verzió a te laborodban elérhető.
| Címke | Mit jelent | Hol ellenőrizd |
|---|---|---|
| ISO-C | kiadott C szabvány nyelvi szabálya | a rögzített szabványverzió szövege |
| ISO-C++ | kiadott C++ szabvány nyelvi szabálya | a rögzített szabványverzió szövege |
| DRAFT | nyilvános munkatervezet, nem kiadott szabvány | a tervezet státusza és dátuma |
| POSIX | POSIX specifikációs követelmény | a vonatkozó specifikációs fejezet |
| LINUX | Linux-specifikus viselkedés | kernel/libc dokumentáció |
| ABI | bináris felület, layout, hívási konvenció | a cél ABI specifikációja |
| COMPILER | fordító vagy könyvtár dokumentált viselkedése | a rögzített kiadás dokumentációja |
| GUIDELINE | ajánlás, nem normatív szabály | az ajánlás szövege |
| MEASUREMENT | konkrét mérés eredménye | a mérés leírása és környezete |
| ILLUSTRATION | szemléltető érték, például cím | nem állítás, nem bizonyíték |
A C++23 és C++26 állapotára vonatkozó kijelentések ezért mindig
DRAFT vagy COMPILER címkét kapnak, és a
támogatás verziőfüggő; erről a C++23 összefoglaló és
migrációs táblázat és a C++26 és későbbi irányok
ellenőrzött státusszal ad eligazítást. Ez a fejezet nem garantál
semmilyen jövőbeli támogatást.
A hiba helyét a tünet alapján érdemes szűkíteni, mert a rossz szinten keresett hiba időt rabol. Az útvonal sorrendje nem esztétikai kérdés: a korábbi fázis hibája elfedi a későbbit.
undefined reference esetén
deklaráció/definíció, extern "C", szimbólumláthatóság. Lásd
Szimbólumok, linkage és relocation és extern C és két nyelvű header.atexit, nem lokális vezérlés. Lásd Statikus és thread-local objektumok
életciklusa.Az útvonal ellenőrzési szabálya: minden lépésnél írd le, mit vársz és mit figyeltél meg, és a kettő eltérését ne ugorásként kezeld. A „buildel, de összeomlik” tünet például nem a 3. lépésnél kezdődik, hanem a 6.-nál, viszont a 3. lépés figyelmeztetései gyakran a 6. okát jelzik előre.
A mátrix nem haladási napló, hanem annak ellenőrzése, hogy egy témacsoportot valóban lefedtél-e: van-e hozzá megoldott feladat és olyan bizonyíték, amelyet más is reprodukálhat. A lefedettség aránya: Az arány önmagában félrevezető: a nevezőt és a bizonyíték típusát is rögzíteni kell, különben a mutató tartalom nélkül telítődik.
| Témacsoport | Cél | Feladat típusa | Bizonyíték |
|---|---|---|---|
| Tulajdonlás és élettartam | owner/borrower API-t indokolni | kódolási | szerződéslista + sanitizer futás naplója |
| Invalidáció | view érvényességét megőrizni | diagnosztikai | reprodukált hiba és javított változat |
| C/C++ szemantika | eltérést megnevezni | magyarázó | kétnyelvű példa és indoklás |
| Build és link | hibát fázishoz kötni | diagnosztikai | parancs, hibaüzenet, ok |
| Teljesítmény | költséget mérni | mérési | mérési környezet és nyers adat |
A mátrix használatakor a bizonyíték oszlopba ne „megértettem” kerüljön, hanem visszakereshető nyom: parancs, kimenet, hibaüzenet vagy indoklás. A Reprodukálható fordítási és diagnosztikai labor és a Release, debug symbol és reprodukálhatóság adja az ehhez szükséges környezeti fegyelmet.
Segmentation fault-tal áll le, a
-Wall -Wextra nem jelez semmit, a linker hibátlan. Írd le,
melyik diagnosztikai lépésekből melyiket választod, milyen sorrendben,
és melyik megfigyelés zárná ki az adott szintet. Add meg a használt
kapcsolókat is.const char *name paramétert kap és a mutatót egy hosszú
élettartamú struktúrában eltárolja. Írd át a szerződést és a kódot úgy,
hogy az owner/borrower döntési tábla szerinti legyen, és adj ellenőrzési
tervet arra az esetre, amikor a hívó ideiglenes buffert ad át.ISO-C, ISO-C++, ABI
vagy MEASUREMENT címkét kapna.1. megoldás. A tünet alapján a futásidejű szint a
valószínű, de a sorrendet nem szabad megfordítani. Először ellenőrizd,
hogy a lefordított program a várt forrásból származik-e (friss build,
nem elavult bináris), majd futtasd címzett diagnosztikával:
-fsanitize=address,undefined és debug információ. Ha az
ASan pontos helyet ad, a hiba a 6. szintre tartozik; ha nem szólal meg,
de a program összeomlik, gyanús a nem definiált viselkedés olyan
formája, amelyet a sanitizer nem fog el, vagy a linkelt könyvtár eltérő.
Az indoklás: az „nincs figyelmeztetés” nem bizonyíték a helyességre,
csak annyit jelent, hogy a fordító nem talált diagnosztizálható mintát.
Ellenőrzési feltétel: a hibaüzenet és a javított futás összevethető, és
a javítás okát egy mondatban meg tudod nevezni.
2. megoldás. A hiba az, hogy a függvény tulajdonosi
felelősséget vállal egy olyan pointerért, amelyet csak kölcsönkapott. A
javítás két helyes irány egyike: vagy a függvény másolatot készít
(strdup-szerű, dokumentált free
kötelezettséggel), vagy a hívó adja át a tulajdont egyértelmű típusban.
C-ben az első a szokásos; a szerződést a headerben kell rögzíteni, és a
hibakimenetet is meg kell nevezni. Ha a hívó ideiglenes buffert ad, a
másolat elkészítése után a buffer felszabadítható; ezért az ellenőrzési
terv: hozz létre egy lokális buffert, add át, majd azonnal írd felül, és
igazold, hogy a tárolt érték változatlan. Ez a teszt a tulajdonlást
ellenőrzi, nem a memóriát. Kapcsolódó részek: Opaque
handle és stabil C könyvtári interfész, C
API-szerződések ellenőrzése.
3. megoldás. Például az invalidáció témacsoportnál a
cél: „egy view érvényességi határát meg tudom nevezni”. A feladat:
készíts olyan programot, amelyben egy string_view túléli az
owner módosítását, figyeld meg a következményt, majd javítsd a kódot
owner-másolattal vagy a view élettartamának szűkítésével. A bizonyíték:
a hiba előtti és utáni kimenet, a sanitizer jelentése és a javítás
indoklása; a mérési környezet rögzítése. A címkék: a
string_view invalidációs szabálya ISO-C++, a
sanitizer viselkedése COMPILER, a konkrét kimenet
MEASUREMENT. Az ellenőrzési feltétel, hogy a bizonyítékból
kiderüljön, melyik állítás melyik forrásra támaszkodik, és hogy a
javított változat nem függ a régi buffer tartalmától.
Ez a fejezet a kurzus anyagát keresőfelületté alakítja: a döntési táblák a tulajdonlás és az API-szerződés kérdéseit fordítják választássá, az invalidációs index a hozzáférések érvényességi határait rögzíti, a C/C++ táblázat a két nyelv eltérését idézi fel, a jelölésjegyzék pedig azt szabályozza, milyen bizonyítékot fogadunk el egy állítás mellett. A diagnosztikai útvonal a tünettől a fázisig vezet, a lefedettségi mátrix pedig a tanulást teszi ellenőrizhetővé: nem elég egy témát elolvasni, kell hozzá megoldott feladat és reprodukálható bizonyíték. A táblázatok nem helyettesítik a korábbi részek feltételeit; minden sor mögött ott van a hivatkozott rész, amelyet döntés előtt érdemes újraolvasni.