Защо операторът sizeof
връща размер, по-голям за структура от общите размери на членовете на структурата?
Защо sizeof за структура не е равен на сумата от sizeof на всеки член?
Отговори (12)
Това се дължи на добавената подложка за удовлетворяване на ограниченията за подравняване. Подравняването на структурата на данните влияе както на производителността, така и на коректността на програмите:
- Неправилно подравненият достъп може да е сериозна грешка (често
SIGBUS
). - Mis-aligned access might be a soft error.
- Either corrected in hardware, for a modest performance-degradation.
- Или коригиран чрез емулация в софтуера, за сериозно влошаване на производителността.
- В допълнение, атомарността и другите гаранции за едновременност могат да бъдат нарушени, което води до фини грешки.
Ето пример, използващ типични настройки за x86 процесор (всички използвани 32 и 64 битови режими):
struct X
{
short s; /* 2 bytes */
/* 2 padding bytes */
int i; /* 4 bytes */
char c; /* 1 byte */
/* 3 padding bytes */
};
struct Y
{
int i; /* 4 bytes */
char c; /* 1 byte */
/* 1 padding byte */
short s; /* 2 bytes */
};
struct Z
{
int i; /* 4 bytes */
short s; /* 2 bytes */
char c; /* 1 byte */
/* 1 padding byte */
};
const int sizeX = sizeof(struct X); /* = 12 */
const int sizeY = sizeof(struct Y); /* = 8 */
const int sizeZ = sizeof(struct Z); /* = 8 */
Човек може да минимизира размера на структурите чрез сортиране на членовете по подравняване (сортирането по размер е достатъчно за това в основните типове) (като структура Z
в примера по-горе).
ВАЖНА ЗАБЕЛЕЖКА: Както C, така и C++ стандартите заявяват, че подравняването на структурата се определя от изпълнението. Следователно всеки компилатор може да избере да подреди данните по различен начин, което води до различни и несъвместими оформления на данни. Поради тази причина, когато се работи с библиотеки, които ще се използват от различни компилатори, е важно да се разбере как компилаторите подравняват данните. Някои компилатори имат настройки на командния ред и/или специални #pragma
изрази за промяна на настройките за подравняване на структурата.
char
има 3 байта запълване, а за следващите 2 има само 1 байт?
- person WWZee; 26.01.2017
X
има 2 байта запълване след short
, за да се гарантира, че 4-байтовият int
започва на граница от 4 байта. В Y
има 1 байт подложка след char
, за да се гарантира, че 2 байта short
започва на граница от 2 байта. Тъй като компилаторът не може да знае какво може да има след дадена структура в паметта (а това може да са много различни неща), той се подготвя за най-лошото и вмъква достатъчно подпълване, за да направи структурата кратна на 4 байта. X
се нуждае от 3 байта, за да стигне до 12, Y
се нуждае само от 1 за 8.
- person 8bittree; 17.02.2017
struct {long long a; char b;}
обикновено има 7 байта запълване в края след b
, което го прави общо 16 байта. (на повечето 64-битови архитектури дяда дяда)
- person Mooing Duck; 24.01.2019
Пакетиране и подравняване на байтове, както е описано в често задаваните въпроси за C тук:
Това е за подравняване. Много процесори не могат да получат достъп до 2- и 4-байтови количества (напр. int и long int), ако са натъпкани по всякакъв начин.
Да предположим, че имате тази структура:
struct { char a[3]; short int b; long int c; char d[3]; };
Сега може би си мислите, че би трябвало да е възможно тази структура да се опакова в паметта по следния начин:
+-------+-------+-------+-------+ | a | b | +-------+-------+-------+-------+ | b | c | +-------+-------+-------+-------+ | c | d | +-------+-------+-------+-------+
Но е много, много по-лесно за процесора, ако компилаторът го подреди по следния начин:
+-------+-------+-------+ | a | +-------+-------+-------+ | b | +-------+-------+-------+-------+ | c | +-------+-------+-------+-------+ | d | +-------+-------+-------+
В опакованата версия, забележете как е поне малко трудно за вас и мен да видим как се увиват полетата b и c? С две думи, трудно е и за процесора. Следователно, повечето компилатори ще подпълнят структурата (сякаш с допълнителни, невидими полета) по следния начин:
+-------+-------+-------+-------+ | a | pad1 | +-------+-------+-------+-------+ | b | pad2 | +-------+-------+-------+-------+ | c | +-------+-------+-------+-------+ | d | pad3 | +-------+-------+-------+-------+
s
, тогава &s.a == &s
и &s.d == &s + 12
(предвид подравняването, показано в отговора). Указателят се съхранява само ако масивите имат променлив размер (напр. a
е деклариран char a[]
вместо char a[3]
), но тогава елементите трябва да се съхраняват някъде другаде.
- person kbolino; 31.03.2020
Ако искате структурата да има определен размер с GCC например, използвайте __attribute__((packed))
.
В Windows можете да зададете подравняването на един байт, когато използвате компилатора cl.exe с /Zp опция.
Обикновено за процесора е по-лесно да има достъп до данни, които са кратни на 4 (или 8), в зависимост от платформата, а също и от компилатора.
Така че основно е въпрос на привеждане в съответствие.
Трябва да имате основателни причини, за да го промените.
__attribute__((packed))
е потенциално опасно в някои случаи: stackoverflow.com/q/8568432/827263
- person Keith Thompson; 10.06.2015
#pragma pack
, правенето на това с опция от командния ред е зло. (GCC и clang в Windows използват __attribute__
точно както във всяка друга операционна система)
- person Ben Voigt; 31.03.2019
#pragma pack(1)
- той се поддържа от MSVC, gcc и clang, което прави кода ви по-преносим
- person mvp; 21.03.2021
Това може да се дължи на подравняване на байтове и подпълване, така че структурата да излезе на четен брой байтове (или думи) на вашата платформа. Например в C на Linux, следните 3 структури:
#include "stdio.h"
struct oneInt {
int x;
};
struct twoInts {
int x;
int y;
};
struct someBits {
int x:2;
int y:6;
};
int main (int argc, char** argv) {
printf("oneInt=%zu\n",sizeof(struct oneInt));
printf("twoInts=%zu\n",sizeof(struct twoInts));
printf("someBits=%zu\n",sizeof(struct someBits));
return 0;
}
Имате членове, чиито размери (в байтове) са съответно 4 байта (32 бита), 8 байта (2x 32 бита) и 1 байт (2+6 бита). Горната програма (на Linux, използваща gcc) отпечатва размерите като 4, 8 и 4 - където последната структура е подплатена, така че да е една дума (4 x 8 бита байта на моята 32-битова платформа).
oneInt=4
twoInts=8
someBits=4
:2
и :6
всъщност указват 2 и 6 бита, а не пълни 32-битови цели числа в този случай. someBits.x, тъй като е само 2 бита, може да съхранява само 4 възможни стойности: 00, 01, 10 и 11 (1, 2, 3 и 4). Това има ли смисъл? Ето статия за функцията: geeksforgeeks.org/bit-fields-c
- person Kyle Burton; 13.07.2018
Вижте също:
за Microsoft Visual C:
http://msdn.microsoft.com/en-us/library/2e70t5y1%28v=vs.80%29.aspx
и GCC заявяват съвместимост с компилатора на Microsoft.:
https://gcc.gnu.org/onlinedocs/gcc-4.6.4/gcc/Structure_002dPacking-Pragmas.html
В допълнение към предишните отговори, имайте предвид, че независимо от опаковката, в C++ няма гаранция за поръчка за членове. Компилаторите могат (и със сигурност го правят) да добавят указател на виртуална таблица и членове на базови структури към структурата. Дори съществуването на виртуална таблица не е гарантирано от стандарта (реализацията на виртуален механизъм не е посочена) и следователно може да се заключи, че такава гаранция е просто невъзможна.
Съвсем сигурен съм, че редът на членовете е гарантиран в C, но не бих разчитал на това, когато пиша програма за различни платформи или компилатори.
Размерът на една структура е по-голям от сбора на нейните части поради това, което се нарича опаковка. Конкретен процесор има предпочитан размер на данните, с които работи. Предпочитаният размер на повечето съвременни процесори е 32 бита (4 байта). Достъпът до паметта, когато данните са на този вид граница, е по-ефективен от нещата, които пресичат тази граница на размера.
Например. Помислете за простата структура:
struct myStruct
{
int a;
char b;
int c;
} data;
Ако машината е 32-битова машина и данните са подравнени на 32-битова граница, виждаме незабавен проблем (ако приемем, че няма подравняване на структурата). В този пример нека приемем, че структурните данни започват от адрес 1024 (0x400 - имайте предвид, че най-ниските 2 бита са нула, така че данните са подравнени към 32-битова граница). Достъпът до data.a ще работи добре, защото започва от граница - 0x400. Достъпът до data.b също ще работи добре, защото е на адрес 0x404 - друга 32-битова граница. Но неподравнена структура би поставила data.c на адрес 0x405. 4-те байта data.c са на 0x405, 0x406, 0x407, 0x408. На 32-битова машина системата ще прочете data.c по време на един цикъл на паметта, но ще получи само 3 от 4-те байта (4-тият байт е на следващата граница). Така че системата ще трябва да направи втори достъп до паметта, за да получи 4-тия байт,
Сега, ако вместо да постави data.c на адрес 0x405, компилаторът подпълни структурата с 3 байта и постави data.c на адрес 0x408, тогава системата ще се нуждае само от 1 цикъл, за да прочете данните, намалявайки времето за достъп до този елемент от данни с 50%. Подплънките разменят ефективността на паметта за ефективност на обработката. Като се има предвид, че компютрите могат да имат огромни количества памет (много гигабайти), компилаторите смятат, че размяната (скорост над размера) е разумна.
За съжаление, този проблем се превръща в убиец, когато се опитате да изпратите структури през мрежа или дори да запишете двоичните данни в двоичен файл. Подложката, вмъкната между елементи на структура или клас, може да наруши данните, изпратени до файла или мрежата. За да напишете преносим код (този, който ще отиде в няколко различни компилатора), вероятно ще трябва да имате достъп до всеки елемент от структурата поотделно, за да осигурите правилното "опаковане".
От друга страна, различните компилатори имат различни способности да управляват опаковането на структурата на данните. Например във Visual C/C++ компилаторът поддържа командата #pragma pack. Това ще ви позволи да коригирате пакетирането и подравняването на данни.
Например:
#pragma pack 1
struct MyStruct
{
int a;
char b;
int c;
short d;
} myData;
I = sizeof(myData);
Сега трябва да имам дължина 11. Без прагмата бих могъл да бъда всичко от 11 до 14 (и за някои системи до 32), в зависимост от пакетирането по подразбиране на компилатора.
#pragma pack
. Ако членовете са разпределени според тяхното подравняване по подразбиране, обикновено бих казал, че структурата е не пакетирана.
- person Keith Thompson; 13.06.2015
Може да го направи, ако сте задали неявно или изрично подравняването на структурата. Структура, която е подравнена 4, винаги ще бъде кратна на 4 байта, дори ако размерът на нейните членове е нещо, което не е кратно на 4 байта.
Също така библиотека може да бъде компилирана под x86 с 32-битови int и може да сравнявате нейните компоненти на 64-битов процес, което би ви дало различен резултат, ако правите това на ръка.
C99 N1256 стандартен проект
http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf
6.5.3.4 Операторът sizeof:
3 Когато се прилага към операнд, който има тип структура или обединение, резултатът е общият брой байтове в такъв обект, включително вътрешни и завършващи подложки.
6.7.2.1 Спецификатори на структура и съюз:
13 ... Може да има неименувана подложка в структурен обект, но не и в началото му.
и:
15 Може да има ненаименувана подложка в края на структура или обединение.
Новата C99 функция за член на гъвкав масив (struct S {int is[];};
) също може да засегне подпълването:
16 Като специален случай, последният елемент на структура с повече от един именуван член може да има тип непълен масив; това се нарича член на гъвкав масив. В повечето ситуации членът на гъвкавия масив се игнорира. По-конкретно, размерът на структурата е като ако членът на гъвкавия масив е пропуснат, с изключение на това, че може да има повече завършващи подложки, отколкото предполага пропускът.
Проблеми с преносимостта на приложение J повтаря:
Неуточнени са:...
- Стойността на байтовете за допълване при съхраняване на стойности в структури или обединения (6.2.6.1)
Стандартна чернова на C++11 N3337
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf
5.3.3 Sizeof:
2 Когато се приложи към клас, резултатът е броят байтове в обект от този клас, включително подпълването, необходимо за поставяне на обекти от този тип в масив.
9.2 Членове на класа:
Указател към структурен обект със стандартно оформление, подходящо преобразуван с помощта на reinterpret_cast, сочи към неговия първоначален член (или ако този член е битово поле, тогава към единицата, в която се намира) и обратно. [ Бележка: Следователно може да има неименувано подпълване в структурен обект със стандартно оформление, но не и в началото му, както е необходимо за постигане на подходящо подравняване. — крайна бележка]
Знам само достатъчно C++, за да разбера бележката :-)
В допълнение към другите отговори, една структура може (но обикновено не) да има виртуални функции, в който случай размерът на структурата ще включва и пространството за vtbl.
Езикът C оставя на компилатора известна свобода относно местоположението на структурните елементи в паметта:
- дупки в паметта могат да се появят между всеки два компонента и след последния компонент. Това се дължи на факта, че определени типове обекти на целевия компютър могат да бъдат ограничени от границите на адресиране
- размерът на "дупки в паметта", включен в резултата от оператора sizeof. Размерът само на sizeof не включва размера на гъвкавия масив, който е наличен в C/C++
- Някои реализации на езика ви позволяват да контролирате оформлението на паметта на структурите чрез прагмата и опциите на компилатора
Езикът C предоставя известна увереност на програмиста за оформлението на елементите в структурата:
- компилатори, необходими за присвояване на последователност от компоненти, увеличаващи адресите на паметта
- Адресът на първия компонент съвпада с началния адрес на структурата
- неименуваните битови полета могат да бъдат включени в структурата към необходимите подравнявания на адреси на съседни елементи
Проблеми, свързани с подравняването на елементите:
- Различните компютри очертават ръбовете на обектите по различни начини
- Различни ограничения за ширината на битовото поле
- Компютрите се различават по това как да съхраняват байтовете в една дума (Intel 80x86 и Motorola 68000)
Как работи подравняването:
- Обемът, зает от структурата, се изчислява като размер на подравнения единичен елемент от масив от такива структури. Структурата трябва да завършва така, че първият елемент от следващата структура да не нарушава изискванията за подравняване
p.s. По-подробна информация е налична тук: „Samuel P.Harbison, Guy L.Steele C A Reference, (5.6.2 - 5.6.7)“
Идеята е, че от съображения за скорост и кеш, операндите трябва да се четат от адреси, подравнени към техния естествен размер. За да се случи това, компилаторът допълва структурните елементи, така че следващият член или следващата структура да бъдат подравнени.
struct pixel {
unsigned char red; // 0
unsigned char green; // 1
unsigned int alpha; // 4 (gotta skip to an aligned offset)
unsigned char blue; // 8 (then skip 9 10 11)
};
// next offset: 12
Архитектурата x86 винаги е успявала да извлича неправилно подравнени адреси. Той обаче е по-бавен и когато несъответствието припокрива две различни кеш линии, тогава изгонва две кеш линии, когато подравнен достъп би изгонил само една.
Някои архитектури всъщност трябва да прихващат неправилно подравнени четения и записи, а ранните версии на ARM архитектурата (тази, която еволюира във всички днешни мобилни процесори) ... добре, те всъщност просто върнаха лоши данни за тях. (Те пренебрегнаха битовете от нисък ред.)
И накрая, имайте предвид, че редовете на кеша могат да бъдат произволно големи и компилаторът не се опитва да ги познае или да направи компромис между пространство и скорост. Вместо това решенията за подравняване са част от ABI и представляват минималното подравняване, което в крайна сметка равномерно ще запълни реда на кеша.
TL;DR: подравняването е важно.
Сред другите добре обяснени отговори относно подравняването на паметта и подпълването/опаковането на структурата има нещо, което открих в самия въпрос, като го прочетох внимателно.
Защо
sizeof
за структура не е равно на сумата отsizeof
на всеки член?Защо операторът
sizeof
връща размер, по-голям за структура от общите размери на членовете на структурата?
И двата въпроса предполагат нещо, което не е наред. Поне в общ, нефокусиран изглед, какъвто е случаят тук.
Резултатът от операнда sizeof
, приложен към структурен обект, може да бъде равен на сбора от sizeof
, приложен към всеки член поотделно. Не е задължително да е по-голям/различен.
Ако няма причина за подпълване, паметта няма да бъде подплатена.
Една от повечето реализации, ако структурата съдържа само членове от един и същи тип:
struct foo {
int a;
int b;
int c;
} bar;
Ако приемем sizeof(int) == 4
, размерът на структурата bar
ще бъде равен на сумата от размерите на всички членове заедно, sizeof(bar) == 12
. Тук не е направена подплата.
Същото важи например тук:
struct foo {
short int a;
short int b;
int c;
} bar;
Ако приемем sizeof(short int) == 2
и sizeof(int) == 4
. Сумата от разпределените байтове за a
и b
е равна на разпределените байтове за c
, най-големият член и с това всичко е идеално подравнено. По този начин, sizeof(bar) == 8
.
Това също е обект на втория най-популярен въпрос относно структурната подложка тук:
SO, answer
,Geeks4Geeks
, друга връзка - person EsmaeelE   schedule 09.12.2017