Struktur bantalan dan pengepakan


209

Mempertimbangkan:

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

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

Ukuran struktur masing-masing adalah 12 dan 8.

Apakah struktur ini empuk atau dikemas?

Kapan bantalan atau pengemasan berlangsung?



24
Seni Struktur C yang Hilang yang Hilang - catb.org/esr/structure-packing
Paolo

paddingmembuat segalanya lebih besar. packingmembuat segalanya lebih kecil. Benar-benar berbeda.
smwikipedia

Jawaban:


264

Padding menyelaraskan anggota struktur ke batas alamat "alami" - katakanlah, intanggota akan memiliki offset, yang ada mod(4) == 0di platform 32-bit. Padding aktif secara default. Ini memasukkan "celah" berikut ke dalam struktur pertama Anda:

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;

Pengepakan , di sisi lain mencegah kompiler melakukan padding - ini harus diminta secara eksplisit - di bawah GCC itu __attribute__((__packed__)), jadi yang berikut:

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

akan menghasilkan struktur ukuran 6pada arsitektur 32-bit.

Namun catatan - akses memori yang tidak selaras lebih lambat pada arsitektur yang memungkinkannya (seperti x86 dan amd64), dan secara eksplisit dilarang pada arsitektur penyelarasan ketat seperti SPARC.


2
Saya bertanya-tanya: apakah larangan memori yang tidak selaras pada percikan berarti tidak dapat menangani byte array yang biasa? Struct packing seperti yang saya tahu sebagian besar digunakan dalam mentransmisikan (yaitu jaringan) data, ketika Anda perlu melemparkan array byte ke sebuah struct, dan pastikan bahwa sebuah array cocok dengan bidang struct. Jika percikan tidak bisa melakukan itu, bagaimana mereka bekerja sama sekali ?!
Hi-Angel

14
Itulah sebabnya, jika Anda melihat tata letak tajuk IP, UDP, dan TCP, Anda akan melihat bahwa semua bidang bilangan selaras.
Nikolai Fetissov

17
"Lost Art of C Structure Packing" menjelaskan padding dan pengemasan ptimisations - catb.org/esr/structure-packing
Rob11311

3
Apakah anggota pertama harus didahulukan? Saya pikir pengaturan sepenuhnya tergantung pada implementasi, dan tidak dapat diandalkan (bahkan dari versi ke versi).
allyourcode

4
+ allyourcode Anda. Standar menjamin bahwa urutan anggota akan dipertahankan dan anggota pertama akan mulai dari 0 offset.
martinkunev

64

( Jawaban di atas menjelaskan alasannya dengan cukup jelas, tetapi tampaknya tidak sepenuhnya jelas tentang ukuran bantalan, jadi, saya akan menambahkan jawaban sesuai dengan apa yang saya pelajari dari The Lost Art of Structure Packing , telah berevolusi menjadi tidak terbatas pada C, tetapi juga berlaku untuk Go, Rust. )


Perataan memori (untuk struct)

Aturan:

  • Sebelum setiap anggota individu, akan ada padding sehingga untuk membuatnya mulai dari alamat yang dapat dibagi berdasarkan ukurannya.
    misalnya pada sistem 64 bit, intharus dimulai pada alamat yang dapat dibagi dengan 4, dan longoleh 8, shortdengan 2.
  • chardan char[]istimewa, bisa berupa alamat memori apa pun, sehingga tidak perlu diisi sebelumnya.
  • Sebab struct, selain kebutuhan pelurusan untuk setiap anggota individu, ukuran seluruh struct itu sendiri akan disejajarkan dengan ukuran yang dapat dibagi berdasarkan ukuran anggota individu terbesar, dengan melapisi bagian ujungnya.
    misalnya jika anggota struct terbesar adalahlong kemudian dapat dibagi 8, intkemudian oleh 4, shortkemudian oleh 2.

Urutan anggota:

  • Urutan anggota mungkin mempengaruhi ukuran struct yang sebenarnya, jadi ingatlah itu. misalnya stu_cdan stu_ddari contoh di bawah ini memiliki anggota yang sama, tetapi dalam urutan yang berbeda, dan menghasilkan ukuran yang berbeda untuk 2 struct.

Alamat dalam memori (untuk struct)

Aturan:

  • Sistem 64 bit
    alamat Struct dimulai dari (n * 16)byte. ( Anda dapat melihat pada contoh di bawah ini, semua alamat hex yang dicetak dari structs berakhir dengan 0. )
    Alasan : anggota struct individu terbesar yang mungkin adalah 16 byte (long double ).
  • (Pembaruan) Jika sebuah struct hanya berisi anggotacharsebagai, alamatnya dapat dimulai dari alamat mana pun.

Ruang kosong :

  • Ruang kosong antara 2 struct dapat digunakan oleh variabel non-struct yang bisa masuk.
    Misalnya di test_struct_address()bawah, variabel xberada di antara struct yang berdekatan gdan h.
    Tidak peduli apakah xdinyatakan, halamat tidak akan berubah, xcukup gunakan kembali ruang kosong yang gterbuang.
    Kasus serupa untuk y.

Contoh

( untuk sistem 64 bit )

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;
}

Hasil eksekusi - 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

Hasil eksekusi - 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

Dengan demikian alamat mulai untuk setiap variabel adalah g: d0 x: dc h: e0 y: e8

masukkan deskripsi gambar di sini


4
"Aturan" sebenarnya membuatnya sangat jelas, saya tidak bisa menemukan aturan langsung di mana pun. Terima kasih.
Pervez Alam

2
@PervezAlam Buku ini <The Lost Art of C Structure Packing>, menjelaskan aturan dengan cukup baik, bahkan berpikir itu sedikit lebih lama dari jawaban ini. Buku ini tersedia gratis secara online: catb.org/esr/structure-packing
Eric Wang

Saya akan mencobanya, apakah ini terbatas pada packing Struktur? Hanya rasa ingin tahu ketika saya menyukai penjelasan di buku.
Pervez Alam

1
@PervezAlam Ini adalah buku yang sangat singkat, terutama berfokus pada teknologi yang akan mengurangi jejak memori program c, hanya membutuhkan waktu paling banyak beberapa hari untuk menyelesaikan membaca.
Eric Wang

1
@ValidusOculus Ya, itu berarti 16 byte selaras.
Eric Wang

44

Saya tahu pertanyaan ini sudah tua dan sebagian besar jawaban di sini menjelaskan padding dengan sangat baik, tetapi ketika mencoba memahaminya sendiri, saya pikir memiliki gambar "visual" tentang apa yang terjadi membantu.

Prosesor membaca memori dalam "potongan" dengan ukuran yang pasti (kata). Ucapkan kata prosesor sepanjang 8 byte. Ini akan melihat memori sebagai deretan besar blok bangunan 8 byte. Setiap kali perlu mendapatkan beberapa informasi dari memori, itu akan mencapai salah satu blok dan mendapatkannya.

Alignment Variabel

Seperti tampak pada gambar di atas, tidak masalah di mana Char (panjang 1 byte) berada, karena akan berada di dalam salah satu blok itu, membutuhkan CPU untuk memproses hanya 1 kata.

Ketika kita berurusan dengan data yang lebih besar dari satu byte, seperti 4 byte int atau 8 byte ganda, cara mereka disejajarkan dalam memori membuat perbedaan pada berapa banyak kata yang harus diproses oleh CPU. Jika potongan 4-byte disejajarkan dengan cara mereka selalu cocok dengan bagian dalam blok (alamat memori menjadi kelipatan 4) hanya satu kata yang harus diproses. Kalau tidak, sepotong 4-byte dapat memiliki bagian dari dirinya sendiri di satu blok dan bagian lain, yang membutuhkan prosesor untuk memproses 2 kata untuk membaca data ini.

Hal yang sama berlaku untuk ganda 8-byte, kecuali sekarang itu harus dalam kelipatan alamat memori 8 untuk menjamin itu akan selalu berada di dalam blok.

Ini mempertimbangkan pengolah kata 8-byte, tetapi konsep ini berlaku untuk ukuran kata lain.

Padding berfungsi dengan mengisi celah di antara data tersebut untuk memastikan mereka sejajar dengan blok-blok itu, sehingga meningkatkan kinerja saat membaca memori.

Namun, seperti yang dinyatakan pada jawaban orang lain, terkadang ruang lebih penting daripada kinerja itu sendiri. Mungkin Anda sedang memproses banyak data di komputer yang tidak memiliki banyak RAM (ruang swap dapat digunakan tetapi jauh lebih lambat). Anda dapat mengatur variabel-variabel dalam program sampai padding paling tidak selesai (seperti yang sangat dicontohkan dalam beberapa jawaban lain) tetapi jika itu tidak cukup Anda dapat secara eksplisit menonaktifkan padding, yang merupakan pengemasan .


3
Ini tidak menjelaskan pengemasan struktur tetapi ini menggambarkan penyelarasan kata CPU dengan cukup baik.
David Foerster

Apakah Anda menggambar itu dengan cat? :-)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
@ CiroSantilli709 大 抓捕 六四 事件 法轮功, itu ada di gimp, tapi saya kira saya akan menghemat waktu melakukannya dengan cat meskipun haha
IanC

1
Bahkan lebih baik sejak open source (Y)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

21

Packing struktur menekan padding struktur, padding digunakan saat pelurusan paling penting, packing digunakan saat ruang paling penting.

Beberapa kompiler menyediakan #pragmauntuk menekan padding atau membuatnya dikemas ke sejumlah byte. Beberapa menyediakan kata kunci untuk melakukan ini. Secara umum pragma yang digunakan untuk memodifikasi padding struktur akan dalam format di bawah ini (tergantung pada kompiler):

#pragma pack(n)

Misalnya ARM menyediakan __packed kata kunci untuk menekan padding struktur. Bacalah manual kompiler Anda untuk mempelajari lebih lanjut tentang ini.

Jadi struktur yang dikemas adalah struktur tanpa bantalan.

Struktur yang dikemas umumnya akan digunakan

  • untuk menghemat ruang

  • untuk memformat struktur data untuk mentransmisikan melalui jaringan menggunakan beberapa protokol (ini bukan praktik yang baik tentu saja karena Anda harus
    berurusan dengan endianness)


5

Padding dan packing hanyalah dua aspek dari hal yang sama:

  • pengepakan atau pelurusan adalah ukuran di mana setiap anggota dibulatkan
  • padding adalah ruang ekstra yang ditambahkan untuk mencocokkan perataan

Dalam mystruct_A, dengan asumsi penyelarasan default 4, setiap anggota disejajarkan pada kelipatan 4 byte. Karena ukurannya charadalah 1, padding untuk adan c4 - 1 = 3 byte sedangkan padding tidak diperlukan untuk int byang sudah 4 byte. Cara kerjanya sama untuk mystruct_B.


1

Pengepakan struktur hanya dilakukan ketika Anda memberi tahu kompiler Anda secara eksplisit untuk mengemas struktur. Padding adalah apa yang Anda lihat. Sistem 32-bit Anda mengisi setiap bidang dengan perataan kata. Jika Anda telah memberitahu kompiler Anda untuk mengemas struktur, mereka akan menjadi 6 dan 5 byte, masing-masing. Jangan lakukan itu. Ini tidak portabel dan membuat kompiler menghasilkan kode lebih lambat (dan kadang-kadang bahkan buggy).


1

Tidak ada yang lain selain itu! Yang ingin memahami subjek harus melakukan hal-hal berikut,


1

Aturan untuk bantalan:

  1. Setiap anggota struct harus di alamat yang dapat dibagi dengan ukurannya. Padding disisipkan di antara elemen atau di akhir struct untuk memastikan aturan ini terpenuhi. Ini dilakukan untuk akses Bus yang lebih mudah dan lebih efisien oleh perangkat keras.
  2. Padding pada akhir struct ditentukan berdasarkan ukuran anggota terbesar dari struct.

Mengapa Aturan 2: Pertimbangkan struct berikut,

Struct 1

Jika kami membuat array (terdiri dari 2 struct) dari struct ini, Padding tidak diperlukan di akhir:

Array Struct1

Oleh karena itu, ukuran struct = 8 byte

Asumsikan kita akan membuat struct lain seperti di bawah ini:

Struct 2

Jika kita membuat array dari struct ini, ada 2 kemungkinan, dari jumlah byte padding yang diperlukan di akhir.

A. Jika kita menambahkan 3 byte di akhir dan sejajarkan untuk int dan bukan Long:

Array Struct2 selaras dengan int

B. Jika kita menambahkan 7 byte di akhir dan sejajarkan untuk Long:

Array Struct2 sejajar dengan Long

Alamat awal array kedua adalah kelipatan 8 (yaitu 24). Ukuran struct = 24 byte

Oleh karena itu, dengan menyelaraskan alamat mulai dari array berikutnya dari struct ke beberapa anggota terbesar (yaitu jika kita ingin membuat array dari struct ini, alamat pertama dari array kedua harus dimulai pada alamat yang merupakan beberapa dari anggota terbesar dari struct. Ini dia, 24 (3 * 8)), kita dapat menghitung jumlah byte padding yang dibutuhkan di akhir.


-1

Penyelarasan struktur data adalah cara data disusun dan diakses dalam memori komputer. Ini terdiri dari dua masalah yang terpisah tetapi terkait: penyelarasan data dan struktur data padding . Ketika komputer modern membaca dari atau menulis ke alamat memori, itu akan melakukan ini dalam potongan berukuran kata (misalnya potongan 4 byte pada sistem 32-bit) atau lebih besar. Penyelarasan data berarti menempatkan data pada alamat memori sama dengan beberapa kelipatan dari ukuran kata, yang meningkatkan kinerja sistem karena cara CPU menangani memori. Untuk menyelaraskan data, mungkin perlu untuk menyisipkan beberapa byte yang tidak berarti antara akhir struktur data terakhir dan awal berikutnya, yaitu padding struktur data.

  1. Untuk menyelaraskan data dalam memori, satu atau lebih byte kosong (alamat) dimasukkan (atau dibiarkan kosong) di antara alamat memori yang dialokasikan untuk anggota struktur lainnya saat alokasi memori. Konsep ini disebut padding struktur.
  2. Arsitektur prosesor komputer sedemikian rupa sehingga dapat membaca 1 kata (prosesor 4 byte dalam 32 bit) dari memori pada suatu waktu.
  3. Untuk memanfaatkan keunggulan prosesor ini, data selalu diselaraskan sebagai paket 4 byte yang mengarah untuk memasukkan alamat kosong antara alamat anggota lain.
  4. Karena konsep padding struktur ini dalam C, ukuran struktur selalu tidak sama dengan apa yang kita pikirkan.

1
Mengapa Anda perlu menautkan ke artikel yang sama 5 kali dalam jawaban Anda? Harap simpan hanya satu tautan ke contoh. Juga, karena Anda menautkan ke artikel Anda, Anda perlu mengungkapkan fakta itu.
Artjom B.
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.