Egy C API gyakran összetartozó adatokat ad át: például egy üzenet
azonosítóját, címkéjét, hosszát és tartalmát. A struct
ezeket egyetlen aggregált értékbe foglalja. Ettől azonban még nem válik
automatikusan erőforrás-kezelővé: egy pointertag másolása nem másolja a
mögötte lévő tárolót, és nem ruház át tulajdonjogot.
A Tulajdonlási szerződés és cleanup ágak részben megismert owner–borrowed megkülönböztetés ezért a struktúrák tagjaira is érvényes. A pointereket tartalmazó deklarációk olvasásához a Deklarátorok: tömb-, pointer- és függvénykapcsolatok rész ad előfeltételt.
Az alábbi példák ISO C17-et használnak. A méretek C bájtban értendők:
a sizeof egysége nem általánosan nyolcbites oktett.
A struktúratípus névvel ellátott adattagokból (member) álló típus. Egy struktúraobjektum tartalmazza saját tagobjektumait; egy beágyazott struktúra vagy fix méretű tömb ténylegesen része a befoglaló objektumnak. Pointertag esetén viszont csak a pointerobjektum része: a referált tároló külön létezik.
Az alábbi blob.h egy másolt tartalmú, változó hosszúságú
adatobjektum publikus interfésze:
#ifndef BLOB_H
#define BLOB_H
#include <stddef.h>
struct blob_meta {
unsigned sequence;
char label[8];
};
struct blob_view {
struct blob_meta meta;
const unsigned char *data;
size_t size;
};
struct blob;
typedef struct blob Blob;
Blob *blob_create(struct blob_meta meta,
const void *src, size_t size);
struct blob_view blob_get_view(const Blob *blob);
void blob_destroy(Blob *blob);
#endifA blob_meta a struktúra címkéje, angolul struct
tag. ISO C17-ben a típus neve itt struct blob_meta,
nem önmagában blob_meta. A címkék külön névtérben vannak a
közönséges azonosítóktól.
A typedef struct blob Blob; ezzel szemben típusnevet
vezet be a közönséges azonosítók névterébe. A Blob és a
struct blob ugyanazt a típust jelöli: a
typedef nem hoz létre új, elkülönült típust, és nem ad
ownership-szemantikát.
A tagnevek a saját struktúrájukon belül értelmezendők. A
blob_view meta tagja beágyazott struktúra, a
label pedig tömbtag. A kétféle tagelérés:
value.meta.sequence: közvetlenül egy struktúraérték
tagjának elérése;pointer->meta.sequence: pointeren keresztüli
tagelérés.A p->member jelentése (*p).member.
Kiértékelt hozzáféréskor p megfelelő típusú, élő objektumra
kell mutasson; a -> nem ellenőrzi sem a null pointert,
sem az élettartamot.
Az aggregált inicializáló a tagokat inicializálja. A designated initializer tagnevekkel jelöli a célokat, így nem kell fejben követni a deklarációs sorrendet. A nem megadott tagok ilyen inicializáláskor a statikus inicializálásnak megfelelő értéket kapják: például az egész tag nulla, a pointertag null pointer lesz. Ez szemantikai szabály, nem „minden reprezentációs bájt nulla” ígéret.
A (struct blob_meta){ ... } alak összetett literál,
angolul compound literal. ISO C17-ben ez névtelen objektumot
hoz létre. Függvényblokkon belül automatikus tárolási idejű, az adott
blokk végéig él; nem csak a pontosvesszőig.
A következő teljes demo.c a később megadott
implementációval fordítható:
#include "blob.h"
#include <assert.h>
#include <stdlib.h>
int main(void)
{
unsigned char input[] = {0x41u, 0x42u};
Blob *owner = blob_create(
(struct blob_meta){
.sequence = 7u,
.label = "job"
},
input,
sizeof input
);
if (owner == NULL) {
return EXIT_FAILURE;
}
struct blob_view first = blob_get_view(owner);
struct blob_view second = first;
struct blob_view *alias = &first;
second.meta.label[0] = 'J';
assert(first.meta.label[0] == 'j');
assert(second.data == first.data);
alias->meta.sequence = 8u;
assert(first.meta.sequence == 8u);
assert(second.meta.sequence == 7u);
input[0] = 0xffu;
assert(first.data[0] == 0x41u);
second = (struct blob_view){
.meta = {.sequence = 9u}
};
assert(second.data == NULL);
assert(second.size == 0u);
blob_destroy(owner);
/* first.data innen nem használható az adat elérésére. */
return EXIT_SUCCESS;
}Az első két nézet inicializálása után first és
second külön objektum. A second.meta.label[0]
módosítása ezért nem érinti first címkéjét. Bár tömbök
között önálló értékadás nem végezhető, a struktúra másolása a fix méretű
tömbtagok tartalmát is másolja.
Az alias nem új struktúraérték: ugyanahhoz a
first objektumhoz biztosít másik hozzáférési utat. Az azon
keresztüli módosítás tehát first módosítása.
A kezdeti second = first jellegű inicializálás és a
későbbi struktúra-értékadás hasonló értékmásolást eredményez, de külön
művelet. Értékadásnál a cél már létező, módosítható objektum, a forrás
típusa pedig kompatibilis struktúratípus kell legyen. Például
const adattagot tartalmazó struktúra nem rendelhető felül
ilyen értékadással.
A struktúramásolás a pointertag értékét másolja, nem a referált objektumot. Ezt a referált erőforráshoz képest sekély másolásnak, shallow copy-nak nevezzük.
A példában mindkét nézet data tagja ugyanarra a tárolóra
mutat. A meta viszont beágyazott érték: külön másolat, nem
élő kapcsolat a Blob metaadataihoz.
| Elem | Tulajdonosi szerep | Tároló és élettartam | Érvénytelenítő esemény |
|---|---|---|---|
input |
A hívó által kezelt forrásobjektum | Automatikus tömb, a blokk végéig él | Kilépés a blokkból |
owner |
A dinamikus Blob lezárásáért felelős
pointerváltozó |
Maga a pointer automatikus objektum | A változó megszűnése önmagában nem szabadít fel |
*owner és adattartalma |
Egyetlen birtokolt allokáció | Sikeres létrehozástól a blob_destroy hívásig |
blob_destroy(owner) |
first, second |
Saját metaadatérték, kölcsönzött adatelérés | A nézetobjektumok a blokk végéig élnek | Az adattároló felszabadítása érvényteleníti a kölcsönzött hozzáférést |
alias |
Nem tulajdonló pointer first objektumhoz |
first élettartamán belül használható |
first élettartamának vége |
A const unsigned char * csak az ezen a pointeren
keresztüli írást tiltja. Nem fejez ki tulajdonjogot, és önmagában nem
garantálja, hogy más hozzáférési út sem módosíthatja az adatot.
Ha ugyanilyen pointertag tulajdonosi szerződésű lenne, a sekély másolás veszélyes lehetne. Két struktúra ugyanazt a tárolót tekinthetné sajátjának, majd mindkettő felszabadítaná: ez dupla felszabadításhoz és nem definiált viselkedéshez vezetne. C-ben ezt nem oldja meg automatikus másoló konstruktor vagy destruktor. Explicit klónozási, ownership-átadási vagy tiltott másolási szerződés kell.
A másolás a tagértékekre vonatkozik, nem a teljes
objektumreprezentáció azonosságára. Struktúrára nincs beépített
==, a memcmp pedig nem általános
értékegyenlőség-vizsgálat. A padding szerepét a Padding, alignment és offsetof rész tárgyalja.
A fejlécben a
struct blob;előzetes deklaráció (forward declaration). Bejelenti a típust, de nem közli a tagjait, így az ott még befejezetlen típus, incomplete type.
A kliens deklarálhat rá pointert, átadhatja függvénynek és
tárolhatja. Nem definiálhat azonban Blob objektumot érték
szerint, nem használhatja rá a sizeof operátort, és nem
érheti el a rejtett tagokat. A pointer mérete ismert lehet attól
függetlenül, hogy a referált típus mérete nem ismert.
Az opaque handle itt Blob *: a kliens a
műveleteket ismeri, a reprezentációt nem. Ez nem hardveres
hozzáférés-védelem, hanem típusos API-határ. Lehetővé teszi az
implementáció változtatását anélkül, hogy a kliens a belső
tagelrendezésre támaszkodna; a teljes ABI-stabilitáshoz további
szerződések is szükségesek.
Az API feltételei:
blob_create a metaadatot és a forrás
size C bájtját másolja. A forrásnak a hívás idején
olvashatónak kell lennie; NULL csak nulla hosszal
megengedett.NULL.blob_get_view élő, nem null handle-t vár. A
visszaadott nézet nem tulajdonos; adatelérése a Blob
megsemmisítéséig érvényes.blob_destroy(NULL) megengedett. Ugyanazt az
allokációt kétszer megsemmisíteni tilos.A rejtett típus utolsó tagja rugalmas tömbtag, flexible array member. ISO C17-ben ilyen, méret nélkül deklarált tömbtag több névvel rendelkező tagot tartalmazó struktúra utolsó tagja lehet.
A teljes blob.c:
#include "blob.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
struct blob {
struct blob_meta meta;
size_t size;
unsigned char data[];
};
Blob *blob_create(struct blob_meta meta,
const void *src, size_t size)
{
if (src == NULL && size != 0u) {
return NULL;
}
if (size > SIZE_MAX - sizeof(Blob)) {
return NULL;
}
Blob *blob = malloc(sizeof *blob + size);
if (blob == NULL) {
return NULL;
}
blob->meta = meta;
blob->size = size;
if (size != 0u) {
memcpy(blob->data, src, size);
}
return blob;
}
struct blob_view blob_get_view(const Blob *blob)
{
return (struct blob_view){
.meta = blob->meta,
.data = blob->size == 0u ? NULL : blob->data,
.size = blob->size
};
}
void blob_destroy(Blob *blob)
{
free(blob);
}A data nem pointertag. Elemei a struktúrával közös
allokációban találhatók, ezért nincs második allokáció és második
felszabadítás. Az input tartalmának másolása miatt a létrehozott
objektum független a forrás későbbi módosításától vagy
megszűnésétől.
Legyen , az elemek száma, az elemméret. Az általános allokációs méret:
Itt unsigned char elemek vannak, ezért
C bájt, így
.
Bizonyításvázlat a túlcsordulás kizárására. Mivel
ábrázolható size_t érték, a
kivonás biztonságos. Ha
akkor mindkét oldalhoz -t adva
Az összeadás tehát nem fordul körbe. Nagyobb elemtípusnál még a szorzás előtt ezt kell ellenőrizni:
A sikeres méretellenőrzés nem garantál sikeres allokációt; ezért
külön kell vizsgálni a malloc eredményét.
A sizeof(Blob) nem tartalmazza a kért dinamikus
elemszámot, de tartalmazhat paddinget. Nem szabad feltételezni, hogy
pontosan megegyezik a data kezdőeltolásával. A
sizeof(Blob) + n alak elegendő helyet foglal, az alkalmazás
pedig kizárólag a tárolt size szerinti
[0,size) tartományt tekinti tartalomnak.
A rugalmas tömbtagra nem alkalmazható sizeof. Az ilyen
struktúra ISO C17-ben másik struktúra tagja vagy tömb eleme sem lehet;
pointerként természetesen tárolható. A struktúra-értékadás nem
biztosítja a rugalmas tömbrész tartalmának másolását: klónozásához külön
allokáció és explicit tartalommásolás szükséges.
A három fájlhoz választott laborprofil: Linux, x86-64 System V ABI, LP64; GCC 14.2.0 vagy Clang 19.1.7, glibc 2.39. Ez profil-előírás, nem itt ellenőrzött futtatási környezet. A verziózott parancsnevek mögötti pontos kiadást külön rögzíteni kell.
gcc-14 -std=c17 -Wall -Wextra -Wpedantic -Wconversion \
-O0 -g blob.c demo.c -o demo-gcc
clang-19 -std=c17 -Wall -Wextra -Wpedantic -Wconversion \
-O0 -g blob.c demo.c -o demo-clangA példák itt nem kerültek lefordításra vagy futtatásra. Az
assert feltételek az elvárt viselkedést rögzítik, nem
közölt mérési eredmények.
„A struktúra másolása mindent lemásol.” A pointer értékét igen, a referált tárolót nem. A javítás explicit klónozás vagy dokumentált borrowed kapcsolat.
„Az owner pointer nullázása minden nézetet érvénytelenít.” A tároló felszabadítása érvényteleníti a hozzáféréseket; egyetlen változó nullázása nem módosítja a többi pointert. A nézetek élettartamát kell korlátozni.
„A compound literal örökké él.” Blokkon belüli compound literal címét visszaadni dangling pointert eredményezhet. Érték szerinti visszaadás vagy megfelelően birtokolt tároló kell.
„A struktúra kiírható hálózatra a sizeof
méretével.” A belső reprezentáció nem hordozható
protokollszerződés. Ennek helyes alternatíváját a Byte order és biztonságos szerializálás rész vezeti
le.
Magyarázd meg, miért változik first.meta.sequence az
alias használatakor, de miért nem változik
first.meta.label a second módosításakor!
Használható marad-e first.meta.sequence a
blob_destroy után?
Egészítsd ki az API-t
Blob *blob_clone(const Blob *source); függvénnyel! Null
forrásra adjon NULL-t. Siker esetén a klón az eredeti
megsemmisítése után is használható legyen.
Értékeld külön:
sizeof(Blob) alapján foglalna.sizeof(Blob) + size összeget.*destination = *source műveletet tartalomklónozásnak
tekintené.Mindegyiknél nevezd meg a hibafajtát és a javítást!
Az alias ugyanarra a first objektumra
mutat. A second ezzel szemben külön struktúra, saját
beágyazott metaadattal és címketömbbel.
A blob_destroy után first.meta.sequence
továbbra is olvasható: a nézet saját tagja, amelynek élettartama nem ért
véget. A first.data által korábban elért tartalom viszont
már nem használható. Ellenőrzési feltétel, hogy külön kezeld a
nézetobjektum és a referált tároló élettartamát.
A deklaráció a fejlécbe, az alábbi kiegészítés a blob.c
fájlba kerül:
Blob *blob_clone(const Blob *source)
{
if (source == NULL) {
return NULL;
}
return blob_create(
source->meta,
source->size == 0u ? NULL : source->data,
source->size
);
}A nem null forrásnak a hívás teljes ideje alatt élnie kell. A
függvény új allokációt hoz létre, ezért a két objektum külön tulajdonosi
kötelezettség. Hibakor NULL az eredmény, a forrás
változatlan.
Ellenőrzési feltétel: az eredeti megsemmisítése után a klónból kapott új nézet tartalma egyezzen az eredetileg bemásolt tartalommal; végül a klónt is pontosan egyszer kell megsemmisíteni.
A kliensoldali sizeof(Blob) befejezetlen típus miatt
fordításkor diagnosztizálandó korlátsértés. A foglalást az
implementációra kell bízni.
Az ellenőrizetlen size_t összeadás körbefordulhat. Maga
az előjel nélküli körbefordulás definiált, de túl kicsi allokációt
eredményezhet; az ezt követő túlindexelés már UB. Az összeadás előtti
korlátellenőrzés szükséges.
A struktúra-értékadás nem klónozza a rugalmas tömbrész tartalmát. A
javítás új allokáció és explicit tartalommásolás, például a
blob_clone használata.
A struct beágyazott tagjai az aggregált érték részei; a
pointertagok referált erőforrásai nem. A másolat és az alias külön
fogalom, az ownership pedig explicit API-szerződés. Az opaque típus
elrejti a reprezentációt, a flexible array member pedig egyetlen
allokációban kapcsolhat fejlécet és változó hosszúságú tartalmat. A
helyességhez mindehhez élettartamkorlát, ellenőrzött méretszámítás és
egyértelmű hibakezelés szükséges.