sizeof
Operatör neden bir yapı için yapı üyelerinin toplam boyutlarından daha büyük bir boyut döndürüyor?
sizeof
Operatör neden bir yapı için yapı üyelerinin toplam boyutlarından daha büyük bir boyut döndürüyor?
Yanıtlar:
Bunun nedeni, hizalama kısıtlamalarını karşılamak için eklenen dolgulamadır. Veri yapısı hizalaması , programların hem performansını hem de doğruluğunu etkiler:
SIGBUS
).İşte bir x86 işlemci için tipik ayarları kullanan bir örnek (tümü 32 ve 64 bit modları kullanılır):
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 */
Üyeleri hizalamaya göre sıralayarak yapıların boyutunu en aza indirebilir (temel türlerde buna göre boyut yeterliliklerine göre sıralama) ( Z
yukarıdaki örnekteki yapı gibi).
ÖNEMLİ NOT: Hem C hem de C ++ standartları yapı hizalamasının uygulama tanımlı olduğunu belirtir. Bu nedenle her derleyici, verileri farklı şekilde hizalamayı seçerek farklı ve uyumsuz veri düzenlerine neden olabilir. Bu nedenle, farklı derleyiciler tarafından kullanılacak kütüphanelerle uğraşırken, derleyicilerin verileri nasıl hizaladığını anlamak önemlidir. Bazı derleyiciler #pragma
, yapı hizalama ayarlarını değiştirmek için komut satırı ayarlarına ve / veya özel ifadelere sahiptir.
Paketleme ve bayt hizalama, burada C SSS'de açıklandığı gibi :
Uyum için. Birçok işlemci her yöne sıkışmışlarsa 2 ve 4 bayt miktarlara (örneğin, ints ve uzun ints) erişemez.
Bu yapıya sahip olduğunuzu varsayalım:
struct { char a[3]; short int b; long int c; char d[3]; };
Şimdi, bu yapıyı şu şekilde belleğe paketlemenin mümkün olduğunu düşünebilirsiniz:
+-------+-------+-------+-------+ | a | b | +-------+-------+-------+-------+ | b | c | +-------+-------+-------+-------+ | c | d | +-------+-------+-------+-------+
Ancak derleyici bunu böyle düzenlerse işlemcide çok, çok daha kolay:
+-------+-------+-------+ | a | +-------+-------+-------+ | b | +-------+-------+-------+-------+ | c | +-------+-------+-------+-------+ | d | +-------+-------+-------+
Paketli versiyonda, b ve c alanlarının nasıl sarıldığını görmenin sizin ve benim için en azından biraz zor olduğunu fark ettiniz mi? Özetle, işlemci için de zor. Bu nedenle, çoğu derleyici yapıyı (ekstra, görünmez alanlarda olduğu gibi) şu şekilde doldurur:
+-------+-------+-------+-------+ | a | pad1 | +-------+-------+-------+-------+ | b | pad2 | +-------+-------+-------+-------+ | c | +-------+-------+-------+-------+ | d | pad3 | +-------+-------+-------+-------+
s
o zaman &s.a == &s
ve &s.d == &s + 12
(yanıtta gösterilen hizalama verildiğinde). İşaretçi yalnızca dizilerin değişken bir boyutu varsa (örneğin, bunun yerine a
bildirilmişse ) saklanır, ancak öğelerin başka bir yerde depolanması gerekir. char a[]
char a[3]
Yapının GCC ile belirli bir boyutta olmasını istiyorsanız, örneğin kullanın __attribute__((packed))
.
Windows'ta cl.exe derleyicisini / Zp seçeneğiyle kullanırken hizalamayı bir bayta ayarlayabilirsiniz .
Genellikle, CPU'nun platforma ve ayrıca derleyiciye bağlı olarak 4 (veya 8) 'in katı olan verilere erişmesi daha kolaydır.
Bu temelde bir uyum meselesidir.
Değiştirmek için iyi nedenlere ihtiyacınız var.
Bunun nedeni, yapının platformunuzda eşit sayıda bayt (veya kelime) oluşturması için bayt hizalaması ve dolgusu olabilir. Örneğin Linux'taki C'de aşağıdaki 3 yapı:
#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;
}
Boyutları (bayt cinsinden) sırasıyla 4 bayt (32 bit), 8 bayt (2x 32 bit) ve 1 bayt (2 + 6 bit) olan üyelere sahip olun. Yukarıdaki program (gcc kullanan Linux'ta) boyutları 4, 8 ve 4 olarak yazdırır - burada son yapı tek bir sözcük olacak şekilde doldurulur (32bit platformumda 4 x 8 bit bayt).
oneInt=4
twoInts=8
someBits=4
:2
ve :6
aslında 2 ve 6 bit, bu durumda tam 32 bit tamsayı belirtiyor . someBits.x, yalnızca 2 bit olmakla birlikte yalnızca 4 olası değeri saklayabilir: 00, 01, 10 ve 11 (1, 2, 3 ve 4). Bu mantıklı mı? İşte bu özellik hakkında bir makale: geeksforgeeks.org/bit-fields-c
Ayrıca bakınız:
Microsoft Visual C için:
http://msdn.microsoft.com/en-us/library/2e70t5y1%28v=vs.80%29.aspx
ve GCC, Microsoft'un derleyicisiyle uyumluluk talep ediyor .:
http://gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html
Önceki cevaplara ek olarak, pakete bakılmaksızın, C ++ 'da üye sipariş garantisi olmadığını lütfen unutmayın . Derleyiciler, yapıya sanal tablo işaretçisi ve taban yapıları üyeleri ekleyebilir (ve kesinlikle ekleyebilirler). Sanal tablonun varlığı bile standart tarafından garanti edilmez (sanal mekanizma uygulaması belirtilmez) ve bu nedenle bu garantinin imkansız olduğu sonucuna varılabilir.
Ben eminim üyesi sipariş edilir C garantili , ama bir çapraz platform veya çapraz derleyici programı yazarken ben, üzerinde saymak olmaz.
Bir yapının boyutu, ambalaj denilen şey nedeniyle parçalarının toplamından daha büyüktür. Belirli bir işlemci, birlikte çalıştığı tercih edilen bir veri boyutuna sahiptir. Çoğu modern işlemcinin 32 bit (4 bayt) olması durumunda tercih ettiği boyut. Veri bu tür bir sınırdayken belleğe erişmek, bu boyut sınırını aşan şeylerden daha etkilidir.
Örneğin. Basit yapıyı düşünün:
struct myStruct
{
int a;
char b;
int c;
} data;
Makine 32 bitlik bir makine ise ve veriler 32 bitlik bir sınırda hizalanmışsa, hemen bir sorunla karşılaşırız (yapı hizalaması olmadığı varsayılarak). Bu örnekte, yapı verilerinin 1024 adresinden başladığını varsayalım (0x400 - en düşük 2 bitin sıfır olduğuna dikkat edin, bu nedenle veriler 32 bitlik bir sınıra hizalanmıştır). Data.a erişimi, 0x400 sınırında başladığı için iyi çalışır. Data.b'ye erişim de iyi çalışır, çünkü 0x404 adresinde - başka bir 32 bitlik sınır. Ancak hizalanmamış bir yapı, data.c'yi 0x405 adresine koyacaktır. 4 baytlık veri.c, 0x405, 0x406, 0x407, 0x408'dedir. 32 bitlik bir makinede, sistem bir bellek döngüsü sırasında veriyi c okur, ancak 4 bayttan yalnızca 3 tanesini alır (4. bayt bir sonraki sınırdadır). Bu nedenle, sistemin 4. baytı almak için ikinci bir bellek erişimi yapması gerekir,
Şimdi, veri.c'yi 0x405 adresine koymak yerine, derleyici yapıyı 3 bayt doldurdu ve veri.c'yi 0x408 adresine koydu, o zaman sistemin verileri okumak için sadece 1 döngüye ihtiyacı vardır, bu veri öğesine erişim süresini kısaltır % 50 oranında. Dolgu, işlem verimliliği için bellek verimliliğini değiştirir. Bilgisayarların büyük miktarda belleğe (birçok gigabayt) sahip olduğu göz önüne alındığında, derleyiciler takasın (boyut üzerindeki hız) makul olduğunu düşünüyor.
Ne yazık ki, bir ağ üzerinden yapıları göndermeye veya ikili verileri ikili dosyaya yazmaya çalıştığınızda bu sorun bir katil olur. Bir yapının veya sınıfın elemanları arasına yerleştirilen dolgu, dosyaya veya ağa gönderilen verileri bozabilir. Taşınabilir kod (birkaç farklı derleyiciye gidecek bir kod) yazmak için, muhtemelen uygun "paketleme" sağlamak için yapının her elemanına ayrı ayrı erişmeniz gerekecektir.
Öte yandan, farklı derleyiciler veri yapısı paketlemesini yönetmek için farklı yeteneklere sahiptir. Örneğin, Visual C / C ++ 'da derleyici #pragma pack komutunu destekler. Bu, veri paketleme ve hizalamayı ayarlamanıza olanak tanır.
Örneğin:
#pragma pack 1
struct MyStruct
{
int a;
char b;
int c;
short d;
} myData;
I = sizeof(myData);
Şimdi 11 uzunluğa sahip olmalıyım. Pragma olmadan, derleyicinin varsayılan paketlemesine bağlı olarak 11'den 14'e kadar (ve bazı sistemler için 32'ye kadar) herhangi bir şey olabilirim.
#pragma pack
. Üyeler varsayılan hizalamalarına ayrılırsa, genellikle yapının paketli olmadığını söyleyebilirim .
Yapının hizalamasını örtülü veya açık bir şekilde ayarladıysanız bunu yapabilir. Üyelerinin boyutu 4 baytın katı olmayan bir şey olsa bile, 4 ile hizalanmış bir yapı her zaman 4 baytın katı olacaktır.
Ayrıca bir kütüphane x86 altında 32-bit ints ile derlenebilir ve 64-bit bir işlemde bileşenlerini karşılaştırıyor olsanız, bunu elle yapsaydınız farklı bir sonuç verirsiniz.
C99 N1256 standart taslak
http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf
6.5.3.4 Operatörün boyutu :
3 Yapısı veya birleşim tipi olan bir işlenene uygulandığında sonuç, iç ve sondaki dolgu dahil olmak üzere böyle bir nesnedeki toplam bayt sayısıdır.
6.7.2.1 Yapı ve birleşim belirteçleri :
13 ... Bir yapı nesnesi içinde isimsiz bir dolgu olabilir, ancak başında olmayabilir.
ve:
15 Bir yapının veya birliğin sonunda isimsiz dolgu olabilir.
Yeni C99 esnek dizi üyesi özelliği ( struct S {int is[];};
) de dolguyu etkileyebilir:
Özel bir durum olarak, birden fazla adlandırılmış üyesi olan bir yapının son öğesi eksik bir dizi türüne sahip olabilir; buna esnek dizi üyesi denir. Çoğu durumda, esnek dizi üyesi yok sayılır. Özellikle, yapının boyutu, esnek dizi elemanının, ihmal edilebileceğinden daha fazla arka dolguya sahip olabilmesi dışında çıkarılmış gibidir.
Ek J Taşınabilirlik Konuları şunları yineler:
Aşağıdakiler belirtilmemiş: ...
- Yapılarda veya birliklerde değerler depolarken dolgu baytlarının değeri (6.2.6.1)
C ++ 11 N3337 standart taslak
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf
5.3.3 Boyutları :
2 Bir sınıfa uygulandığında sonuç, o sınıftaki bir nesnede, o türdeki nesneleri bir diziye yerleştirmek için gereken herhangi bir dolgu da dahil olmak üzere bayt sayısıdır.
9.2 Sınıf üyeleri :
Bir reinterpret_cast kullanılarak uygun şekilde dönüştürülmüş standart mizanpaj yapı nesnesine bir işaretçi, ilk üyesini (veya bu üye bir bit alanı, sonra bulunduğu birim) işaret eder ve bunun tersi de geçerlidir. [Not: Bu nedenle, standart yerleşimli bir yapı nesnesinde adsız dolgu olabilir, ancak uygun hizalama elde etmek için gerektiği gibi başlangıçta olmayabilir. - son not]
Ben sadece notu anlamak için yeterli C ++ biliyorum :-)
Diğer cevaplara ek olarak, bir yapı sanal işlevlere sahip olabilir (ancak genellikle yoktur), bu durumda yapının boyutu da vtbl için alan içerir.
C dili derleyiciye yapısal elemanların hafızadaki yeri hakkında bir miktar özgürlük bırakır:
C dili, programcıya yapıdaki elemanların yerleşimi konusunda bir miktar güvence sağlar:
Elemanların hizalanması ile ilgili sorunlar:
Hizalama nasıl çalışır?
ps Daha detaylı bilgiye buradan ulaşabilirsiniz: "Samuel P.Harbison, Guy L.Steele CA Referansı, (5.6.2 - 5.6.7)"
Fikir, hız ve önbellek konuları için, işlenenlerin doğal boyutlarına hizalanmış adreslerden okunması gerektiğidir. Bunun gerçekleşmesi için, derleyici pedleri, aşağıdaki üye veya aşağıdaki yapı hizalanacak şekilde üyeleri yapılandırır.
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 mimarisi her zaman yanlış hizalanmış adresleri getirebilmiştir. Bununla birlikte, daha yavaştır ve yanlış hizalama iki farklı önbellek satırıyla çakıştığında, hizalanmış bir erişim yalnızca bir önbellek çıkardığında iki önbellek satırını çıkarır.
Bazı mimariler aslında yanlış hizalanmış okumaları ve yazmaları ve ARM mimarisinin (bugünün tüm mobil CPU'larına evrilen) erken sürümlerini tuzağa düşürmek zorundalar ... (Düşük dereceli bitleri göz ardı ettiler.)
Son olarak, önbellek satırlarının keyfi olarak büyük olabileceğini ve derleyicinin bunları tahmin etmeye veya uzay-hız hızı dengesi yapmaya çalışmadığını unutmayın. Bunun yerine, hizalama kararları ABI'nin bir parçasıdır ve sonunda bir önbellek satırını eşit olarak dolduracak minimum hizalamayı temsil eder.
TL; DR: hizalama önemlidir.