C Language
Structuurvulling en verpakking
Zoeken…
Invoering
Standaard leggen C-compilers structuren op zodat elk lid snel kan worden benaderd, zonder boetes op te leggen voor 'niet-uitgelijnde toegang, een probleem met RISC-machines zoals de DEC Alpha en sommige ARM-CPU's.
Afhankelijk van de CPU-architectuur en de compiler, kan een structuur meer ruimte in het geheugen innemen dan de som van de afmetingen van de componentleden. De compiler kan opvulling toevoegen tussen leden of aan het einde van de structuur, maar niet aan het begin.
Verpakking heeft voorrang op de standaard opvulling.
Opmerkingen
Eric Raymond heeft een artikel over The Lost Art van C Structure Packing dat handig te lezen is.
Verpakkingsstructuren
Standaard worden structuren opgevuld in C. Als u dit gedrag wilt voorkomen, moet u dit expliciet aanvragen. Onder GCC is het __attribute__((__packed__))
. Overweeg dit voorbeeld op een 64-bits machine:
struct foo {
char *p; /* 8 bytes */
char c; /* 1 byte */
long x; /* 8 bytes */
};
De structuur wordt automatisch opgevuld met een uitlijning van 8-byte
en ziet er als volgt uit:
struct foo {
char *p; /* 8 bytes */
char c; /* 1 byte */
char pad[7]; /* 7 bytes added by compiler */
long x; /* 8 bytes */
};
Dus sizeof(struct foo)
geeft ons 24
plaats van 17
. Dit gebeurde vanwege een 64-bits compiler lezen / schrijven van / naar Geheugen in 8 bytes woord in elke stap en duidelijk als je probeert om char c;
te schrijven char c;
een één byte in het geheugen, een volledige 8 bytes (dat wil zeggen woord) opgehaald en verbruikt alleen de eerste byte ervan en zijn zeven opeenvolgende bytes blijft leeg en niet toegankelijk voor enige lees- en schrijfbewerking voor structuuropvulling.
Structuur verpakking
Maar als u het packed
kenmerk toevoegt, voegt de compiler geen opvulling toe:
struct __attribute__((__packed__)) foo {
char *p; /* 8 bytes */
char c; /* 1 byte */
long x; /* 8 bytes */
};
Nu zal sizeof(struct foo)
17
retourneren.
Over het algemeen worden gepakte structuren gebruikt:
- Om ruimte te besparen.
- Een gegevensstructuur formatteren voor verzending via een netwerk zonder afhankelijk te zijn van elke architectuuruitlijning van elk knooppunt van het netwerk.
Er moet rekening worden gehouden met het feit dat sommige processors zoals de ARM Cortex-M0 geen ongealigneerde geheugentoegang toestaan; in dergelijke gevallen kan structuurverpakking leiden tot ongedefinieerd gedrag en kan de CPU crashen.
Structuurvulling
Stel dat deze struct
is gedefinieerd en gecompileerd met een 32-bits compiler:
struct test_32 {
int a; // 4 byte
short b; // 2 byte
int c; // 4 byte
} str_32;
We verwachten dat deze struct
slechts 10 bytes geheugen in sizeof(str_32)
, maar door de sizeof(str_32)
drukken sizeof(str_32)
we dat het 12 bytes gebruikt.
Dit gebeurde omdat de compiler variabelen uitlijnt voor snelle toegang. Een gebruikelijk patroon is dat wanneer het basistype N bytes bezet (waarbij N een macht van 2 is, zoals 1, 2, 4, 8, 16 - en zelden groter), de variabele moet worden uitgelijnd op een N-bytegrens ( een veelvoud van N bytes).
Voor de structuur die wordt getoond met sizeof(int) == 4
en sizeof(short) == 2
, is een gemeenschappelijke lay-out:
-
int a;
opgeslagen bij offset 0; maat 4. -
short b;
opgeslagen bij offset 4; maat 2. - naamloze opvulling bij offset 6; maat 2.
-
int c;
opgeslagen bij offset 8; maat 4.
Zodoende neemt struct test_32
12 bytes geheugen in beslag. In dit voorbeeld is er geen padding.
De compiler zorgt ervoor dat alle struct test_32
variabelen worden opgeslagen vanaf een grens van 4 bytes, zodat de leden binnen de structuur correct worden uitgelijnd voor snelle toegang. Geheugentoewijzingsfuncties zoals malloc()
, calloc()
en realloc()
zijn vereist om ervoor te zorgen dat de geretourneerde aanwijzer voldoende goed is uitgelijnd voor gebruik met elk gegevenstype, zodat dynamisch toegewezen structuren ook correct worden uitgelijnd.
Je kunt eindigen met vreemde situaties, zoals op een 64-bits Intel x86_64-processor (bijv. Intel Core i7 - een Mac met macOS Sierra of Mac OS X), waarbij de compilers bij het compileren in 32-bits modus double
uitgelijnd op een 4-byte grens; maar op dezelfde hardware, bij het compileren in 64-bit modus, plaatsen de compilers double
uitgelijnd op een grens van 8 bytes.