Yapı dolgu ve paketleme


209

Düşünmek:

struct mystruct_A
{
   char a;
   int b;
   char c;
} x;

struct mystruct_B
{
   int b;
   char a;
} y;

Yapıların boyutları sırasıyla 12 ve 8'dir.

Bu yapılar dolgulu veya paketlenmiş mi?

Dolgu veya paketleme ne zaman yapılır?



24
C Yapı Ambalajının Kayıp Sanatı - catb.org/esr/sttruc-packing
Paolo

paddingişleri büyütür. packingişleri daha küçük yapar. Tamamen farklı.
smwikipedia

Yanıtlar:


264

Doldurma , yapı üyelerini "doğal" adres sınırlarına hizalar - diyelim ki intüyeler mod(4) == 032 bit platformda ofsetler kullanırdı . Dolgu varsayılan olarak açıktır. İlk yapınıza aşağıdaki "boşlukları" ekler:

struct mystruct_A {
    char a;
    char gap_0[3]; /* inserted by compiler: for alignment of b */
    int b;
    char c;
    char gap_1[3]; /* -"-: for alignment of the whole struct in an array */
} x;

Öte yandan paketleme , derleyicinin dolgu yapmasını önler - bu açıkça talep edilmelidir - GCC altında __attribute__((__packed__)), bu nedenle aşağıdakiler:

struct __attribute__((__packed__)) mystruct_A {
    char a;
    int b;
    char c;
};

boyut yapısı üretecek 632 bit mimaride .

Yine de bir not - hizalanmamış bellek erişimi izin veren mimarilerde daha yavaştır (x86 ve amd64 gibi) ve SPARC gibi katı hizalama mimarilerinde açıkça yasaktır .


2
Acaba: kıvılcım üzerinde hizalanmamış belleğin yasaklanması, normal bir bayt dizileriyle başa çıkamayacağı anlamına mı geliyor? Bildiğim gibi yapı paketleme, bir yapıya bir bayt dizisi atmanız gerektiğinde ve bir dizinin bir yapı alanlarına uyduğundan emin olduğunuzda, bir veri iletiminde (yani ağ oluşturma) kullanılır. Kıvılcım bunu yapamazsa, nasıl çalışıyor ?!
Hi-Angel

14
IP, UDP ve TCP başlık düzenlerine bakarsanız, tüm tamsayı alanlarının aynı hizada olduğunu görürsünüz.
Nikolai Fetissov

17
"C Yapı Ambalajının Kayıp Sanatı" dolgu ve ambalajlama zamanını açıklar - catb.org/esr/sttruc-packing
Rob11311

3
İlk üye önce gelmek zorunda mı? Düzenlemenin tamamen uygulamaya bağlı olduğunu ve (sürümden sürüme bile) güvenilemeyeceğini düşündüm.
allyourcode

4
+ allyourcode Standart, üyelerin sırasının korunacağını ve ilk üyenin 0 ofsetle başlayacağını garanti eder.
martinkunev

64

( Yukarıdaki cevaplar nedeni oldukça açık bir şekilde açıkladı, ancak dolgu boyutu hakkında tamamen net görünmüyor, bu yüzden Yapının Kayıp Sanatı Ambalajından öğrendiklerime bir cevap ekleyeceğim C, ancak bununla sınırlı kalmamak için gelişti , ancak Aynı işlem Go, Rust. )


Bellek hizalama (yapı için)

Kurallar:

  • Her bir üyeden önce, boyutuna göre bölünebilen bir adreste başlamasını sağlamak için dolgu olacaktır.
    örneğin 64 bit sistemde, int4 ve long8 shortile 2'ye bölünebilen adreste başlamalıdır .
  • charve char[]özeldir, herhangi bir bellek adresi olabilir, bu yüzden onlardan önce dolguya ihtiyaç duymazlar.
  • Çünkü struct, her bir üye için hizalama ihtiyacı dışında, tüm yapının büyüklüğü, en büyük bireysel üye boyutuna göre bölünebilir bir boyuta, sonunda dolgu ile hizalanacaktır.
    örneğin, yapıların en büyük üyesi long8, intsonra 4, shortsonra 2 ile bölünebilirse .

Üye sırası:

  • Üyenin sırası yapının gerçek boyutunu etkileyebilir, bu yüzden bunu aklınızda bulundurun. örneğin stu_cve stu_daşağıdaki örnekte aynı üyeler vardır, ancak farklı sırayla ve 2 yapı için farklı boyutlarda sonuçlanır.

Bellekteki adres (yapı için)

Kurallar:

  • 64 bit sistem
    Yapı adresi (n * 16)bayttan başlar . ( Aşağıdaki örnekte, yapıların tüm basılı onaltılık adresleri ile biter 0. )
    Sebep : olası en büyük bireysel yapı üyesi 16 bayttır ( long double).
  • (Güncelleme) Bir yapı yalnızcacharüye olarakiçeriyorsa, adresi herhangi bir adreste başlayabilir.

Boş alan :

  • 2 yapı arasındaki boş alan, sığabilecek yapısal olmayan değişkenler tarafından kullanılabilir.
    Örneğin test_struct_address()aşağıda değişken, xbitişik yapı gve arasında bulunur h. Beyan
    edilip edilmediğine bakılmaksızın x, hadresi değişmez, xsadece gboşa giden boş alanı tekrar kullanır .
    İçin benzer durum y.

Misal

( 64 bit sistem için )

memory_align.c :

/**
 * Memory align & padding - for struct.
 * compile: gcc memory_align.c
 * execute: ./a.out
 */ 
#include <stdio.h>

// size is 8, 4 + 1, then round to multiple of 4 (int's size),
struct stu_a {
    int i;
    char c;
};

// size is 16, 8 + 1, then round to multiple of 8 (long's size),
struct stu_b {
    long l;
    char c;
};

// size is 24, l need padding by 4 before it, then round to multiple of 8 (long's size),
struct stu_c {
    int i;
    long l;
    char c;
};

// size is 16, 8 + 4 + 1, then round to multiple of 8 (long's size),
struct stu_d {
    long l;
    int i;
    char c;
};

// size is 16, 8 + 4 + 1, then round to multiple of 8 (double's size),
struct stu_e {
    double d;
    int i;
    char c;
};

// size is 24, d need align to 8, then round to multiple of 8 (double's size),
struct stu_f {
    int i;
    double d;
    char c;
};

// size is 4,
struct stu_g {
    int i;
};

// size is 8,
struct stu_h {
    long l;
};

// test - padding within a single struct,
int test_struct_padding() {
    printf("%s: %ld\n", "stu_a", sizeof(struct stu_a));
    printf("%s: %ld\n", "stu_b", sizeof(struct stu_b));
    printf("%s: %ld\n", "stu_c", sizeof(struct stu_c));
    printf("%s: %ld\n", "stu_d", sizeof(struct stu_d));
    printf("%s: %ld\n", "stu_e", sizeof(struct stu_e));
    printf("%s: %ld\n", "stu_f", sizeof(struct stu_f));

    printf("%s: %ld\n", "stu_g", sizeof(struct stu_g));
    printf("%s: %ld\n", "stu_h", sizeof(struct stu_h));

    return 0;
}

// test - address of struct,
int test_struct_address() {
    printf("%s: %ld\n", "stu_g", sizeof(struct stu_g));
    printf("%s: %ld\n", "stu_h", sizeof(struct stu_h));
    printf("%s: %ld\n", "stu_f", sizeof(struct stu_f));

    struct stu_g g;
    struct stu_h h;
    struct stu_f f1;
    struct stu_f f2;
    int x = 1;
    long y = 1;

    printf("address of %s: %p\n", "g", &g);
    printf("address of %s: %p\n", "h", &h);
    printf("address of %s: %p\n", "f1", &f1);
    printf("address of %s: %p\n", "f2", &f2);
    printf("address of %s: %p\n", "x", &x);
    printf("address of %s: %p\n", "y", &y);

    // g is only 4 bytes itself, but distance to next struct is 16 bytes(on 64 bit system) or 8 bytes(on 32 bit system),
    printf("space between %s and %s: %ld\n", "g", "h", (long)(&h) - (long)(&g));

    // h is only 8 bytes itself, but distance to next struct is 16 bytes(on 64 bit system) or 8 bytes(on 32 bit system),
    printf("space between %s and %s: %ld\n", "h", "f1", (long)(&f1) - (long)(&h));

    // f1 is only 24 bytes itself, but distance to next struct is 32 bytes(on 64 bit system) or 24 bytes(on 32 bit system),
    printf("space between %s and %s: %ld\n", "f1", "f2", (long)(&f2) - (long)(&f1));

    // x is not a struct, and it reuse those empty space between struts, which exists due to padding, e.g between g & h,
    printf("space between %s and %s: %ld\n", "x", "f2", (long)(&x) - (long)(&f2));
    printf("space between %s and %s: %ld\n", "g", "x", (long)(&x) - (long)(&g));

    // y is not a struct, and it reuse those empty space between struts, which exists due to padding, e.g between h & f1,
    printf("space between %s and %s: %ld\n", "x", "y", (long)(&y) - (long)(&x));
    printf("space between %s and %s: %ld\n", "h", "y", (long)(&y) - (long)(&h));

    return 0;
}

int main(int argc, char * argv[]) {
    test_struct_padding();
    // test_struct_address();

    return 0;
}

Yürütme sonucu - test_struct_padding():

stu_a: 8
stu_b: 16
stu_c: 24
stu_d: 16
stu_e: 16
stu_f: 24
stu_g: 4
stu_h: 8

Yürütme sonucu - test_struct_address():

stu_g: 4
stu_h: 8
stu_f: 24
address of g: 0x7fffd63a95d0  // struct variable - address dividable by 16,
address of h: 0x7fffd63a95e0  // struct variable - address dividable by 16,
address of f1: 0x7fffd63a95f0 // struct variable - address dividable by 16,
address of f2: 0x7fffd63a9610 // struct variable - address dividable by 16,
address of x: 0x7fffd63a95dc  // non-struct variable - resides within the empty space between struct variable g & h.
address of y: 0x7fffd63a95e8  // non-struct variable - resides within the empty space between struct variable h & f1.
space between g and h: 16
space between h and f1: 16
space between f1 and f2: 32
space between x and f2: -52
space between g and x: 12
space between x and y: 12
space between h and y: 8

Böylece her değişken için adres başlangıcı g: d0 x: dc h: e0 y: e8

resim açıklamasını buraya girin


4
"Kurallar" aslında bunu çok netleştirdi, hiçbir yerde açık bir kural bulamadım. Teşekkürler.
Pervez Alam

2
@PervezAlam Kitap <The Lost Art of C Structure Packing>, bu cevabından biraz daha uzun olduğunu düşünmüş olsa bile kuralları oldukça iyi açıklıyor. Kitap çevrimiçi olarak ücretsiz olarak mevcuttur: catb.org/esr/sttruc-packing
Eric Wang

Ben bir deneyeceğim, btw Yapı paketleme ile sınırlı mı? Kitaptaki açıklamayı sevdiğim gibi sadece merak ediyorum.
Pervez Alam

1
@PervezAlam Çok kısa bir kitap, özellikle c programının bellek ayak izini azaltacak teknolojiye odaklanıyor, okumayı bitirmek sadece birkaç gün sürüyor.
Eric Wang

1
@ValidusOculus Evet, 16 bayt hizalı demektir.
Eric Wang

44

Bu sorunun eski olduğunu biliyorum ve buradaki cevapların çoğu dolguları gerçekten iyi açıklıyor, ancak bunu kendim anlamaya çalışırken, neler olduğunu "görsel" bir görüntüye sahip olduğunu düşündüm.

İşlemci belleği belirli boyutta (kelime) "parçalar" halinde okur. İşlemci kelimesinin 8 bayt uzunluğunda olduğunu varsayalım. Belleğe 8 baytlık yapı taşlarından oluşan büyük bir sıra olarak bakacaktır. Bellekten biraz bilgi alması gerektiğinde, bu bloklardan birine ulaşır ve onu alır.

Değişkenler Hizalaması

Yukarıdaki resimde görüldüğü gibi, bir Char'ın (1 byte uzunluğunda) nerede olduğu önemli değildir, çünkü bu bloklardan birinin içinde olacaktır ve CPU'nun sadece 1 kelimeyi işlemesini gerektirir.

4 bayt int veya 8 bayt çift gibi bir bayttan daha büyük verilerle uğraştığımızda, bellekte hizalanma şekilleri CPU tarafından kaç kelimenin işlenmesi gerektiğinde bir fark yaratır. 4 baytlık parçalar her zaman bir bloğun içine sığacak şekilde hizalanmışsa (bellek adresi 4'ün katıdır) yalnızca bir kelimenin işlenmesi gerekir. Aksi takdirde, 4 baytlık bir yığının bir kısmı bir blokta diğeri bir parça üzerinde olabilir ve işlemcinin bu verileri okumak için 2 kelime işlemesini gerektirebilir.

Aynısı 8 baytlık bir çift için de geçerlidir, ancak şimdi her zaman bir blok içinde olmasını garanti etmek için 8'in bir bellek adresinde olması gerekir.

Bu, 8 baytlık bir kelime işlemcisini dikkate alır, ancak kavram diğer kelime boyutları için geçerlidir.

Dolgu, bu bloklarla hizalandıklarından emin olmak için bu veriler arasındaki boşlukları doldurarak çalışır, böylece belleği okurken performansı artırır.

Ancak, başkalarının cevaplarında belirtildiği gibi, bazen alan performansın kendisinden daha önemlidir. Belki çok fazla RAM olmayan bir bilgisayarda çok fazla veri işliyorsunuz (takas alanı kullanılabilir, ancak ÇOK daha yavaştır). Sen az dolgu yapılana kadar (büyük ölçüde diğer bazı cevaplar örneklendiği gibi) programında değişkenleri düzenlemek ama verebilecek yeterli değilse ne olduğunu açıkça devre dışı dolgu, could ambalaj olduğunu.


3
Bu, yapı paketlemesini açıklamaz, ancak CPU kelime hizalamasını oldukça güzel gösterir.
David Foerster


1
@ CiroSantilli709大抓捕六四事件法轮功, bu gimp oldu, ama ben haha olsa boya yapıyor biraz zaman kaydetmiş herhalde
IanC

1
Açık kaynaklı (Y) beri daha da iyi
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功

21

Yapı paketleme, yapı dolgusu, hizalama en önemli olduğunda kullanılan dolgu, alan en önemli olduğunda kullanılan dolgulamayı baskılar.

Bazı derleyiciler #pragmadolguyu bastırmayı veya n baytlara paketlenmesini sağlar. Bazıları bunu yapmak için anahtar kelimeler sağlar. Genellikle yapı dolgularını değiştirmek için kullanılan pragma aşağıdaki formatta olacaktır (derleyiciye bağlıdır):

#pragma pack(n)

Örneğin ARM __packed yapı dolgusunu bastırmak anahtar kelime . Bununla ilgili daha fazla bilgi edinmek için derleyici kılavuzunuzu gözden geçirin.

Yani paketlenmiş bir yapı, dolgusuz bir yapıdır.

Genellikle paketlenmiş yapılar kullanılacaktır

  • yerden tasarruf etmek

  • bir veri yapısını bazı protokolü kullanarak ağ üzerinden iletilecek şekilde biçimlendirmek için (bu elbette iyi bir uygulama değildir çünkü
    endianness ile uğraşmanız gerekir )


5

Dolgu ve paketleme aynı şeyin iki yönüdür:

  • paketleme veya hizalama her üyenin yuvarlandığı boyuttur
  • dolgu, hizalamaya uyması için eklenen fazladan alandır

İçinde mystruct_A, varsayılan bir hizalama 4 olduğu varsayılarak, her üye 4 baytın katları üzerinde hizalanır. Boyutu char1 olduğundan, ave c4 - 1 = 3 bayt için dolgulama yapılırken int b, zaten 4 bayt olan dolgu gerekmez . Aynı şekilde çalışır mystruct_B.


1

Yapı paketleme sadece derleyicinize yapıyı açıkça paketlemesini söylediğinizde yapılır. Dolgu gördüğünüz şeydir. 32 bit sisteminiz her alanı sözcük hizalaması için dolduruyor. Derleyicinize yapıları paketlemesini söylemiş olsaydınız, sırasıyla 6 ve 5 bayt olurdu. Yine de yapma. Taşınabilir değildir ve derleyicilerin çok daha yavaş (ve hatta bazen buggy) kod üretmesini sağlar.


1

Bu konuda hiçbir iz yok! Konuyu kavramak isteyenlerin aşağıdakileri yapması gerekir,


1

Dolgu için kurallar:

  1. Yapının her üyesi, boyutuna göre bölünebilen bir adreste olmalıdır. Bu kuralın karşılandığından emin olmak için elemanlar arasına veya yapının sonuna dolgu eklenir. Bu, donanım tarafından daha kolay ve daha verimli Bus erişimi için yapılır.
  2. Yapının sonundaki dolgu, yapının en büyük üyesinin büyüklüğüne göre belirlenir.

Neden Kural 2: Aşağıdaki yapıyı düşünün,

Yapı 1

Bu yapının bir dizisini (2 yapıdan) oluşturacak olsaydık, sonunda dolgu gerekmez:

Struct1 dizisi

Bu nedenle, yapı boyutu = 8 bayt

Aşağıdaki gibi başka bir yapı oluşturacağımızı varsayın:

Yapı 2

Bu yapının bir dizisini oluşturacak olsaydık, sonunda gerekli olan bayt dolgusu sayısının 2 olasılığı vardır.

A. Sonuna 3 bayt ekler ve Long için değil int için hizalarsak:

İnt ile hizalanmış Struct2 dizisi

Sonuna 7 bayt ekler ve Long için hizalarsak:

Uzun'a hizalanmış Struct2 dizisi

İkinci dizinin başlangıç ​​adresi 8'in katıdır (yani 24). Yapının boyutu = 24 bayt

Bu nedenle, yapının bir sonraki dizisinin başlangıç ​​adresini en büyük üyenin bir katıyla hizalayarak (yani, bu yapının bir dizisini oluşturacak olursak, ikinci dizinin ilk adresi, birden çok olan bir adreste başlamalıdır. Burada, 24 (3 * 8)), sonunda gerekli olan dolgu baytlarının sayısını hesaplayabiliriz.


-1

Veri yapısı hizalama, verilerin bilgisayar belleğinde düzenlenme ve erişilme şeklidir. İki ayrı fakat ilgili sorundan oluşur: veri hizalama ve veri yapısı dolgusu . Modern bir bilgisayar bir bellek adresinden okuduğunda veya bir adrese yazdığında, bunu kelime boyutunda parçalar halinde (örneğin 32 bit sistemde 4 baytlık parçalar halinde) veya daha büyük olarak yapar. Veri hizalama, verilerin kelime boyutunun birkaç katına eşit bir bellek adresine yerleştirilmesi anlamına gelir ve bu da CPU'nun belleği işleme biçimi nedeniyle sistemin performansını artırır. Verileri hizalamak için, veri yapısı dolgusu olan son veri yapısının sonu ile bir sonrakinin başlangıcı arasına anlamsız baytlar eklemek gerekebilir.

  1. Bellekteki verileri hizalamak için, bellek tahsisi sırasında diğer yapı üyeleri için ayrılan bellek adresleri arasına bir veya daha fazla boş bayt (adres) eklenir (veya boş bırakılır). Bu konsepte yapı dolgusu denir.
  2. Bir bilgisayar işlemcisinin mimarisi, bir kerede bellekten 1 kelimeyi (32 bit işlemcide 4 bayt) okuyabilecek şekildedir.
  3. İşlemcinin bu avantajından yararlanmak için, veriler her zaman 4 baytlık bir paket olarak hizalanır ve bu da diğer üyenin adresi arasına boş adresler ekler.
  4. C'deki bu yapı dolgu konsepti nedeniyle, yapının boyutu her zaman düşündüğümüzle aynı değildir.

1
Cevabınızda neden aynı makaleye 5 kez bağlantı kurmanız gerekiyor ? Lütfen örnek için yalnızca bir bağlantı bulundurun. Ayrıca, makalenize bağlantı verdiğiniz için, bu gerçeği açıklamanız gerekir.
Artjom B.Ağustos
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.