Ketika kita mendeklarasikan struct { char c; int i; }, ukuran total bukan 5 byte (1 + 4) melainkan 8 byte. Selisih 3 byte itu bukan bug—melainkan padding yang sengaja ditambahkan compiler demi performa. Banyak arsitektur CPU membaca int lebih cepat bila alamatnya kelipatan 4 (disebut aligned). Padding menjaga agar setiap member int dalam struct tetap aligned, sekaligus menjaga agar struct secara keseluruhan aligned bila dipakai sebagai elemen array. Pada chapter ini kita akan menelusuri mengapa padding ada, bagaimana mengamatinya lewat sizeof dan _Alignof, serta bagaimana mengontrolnya lewat pragma pack dan penyusunan member yang rapi.
Konsep Alignment dan Padding #
Alignment adalah syarat bahwa objek bertipe tertentu harus diletakkan pada alamat yang merupakan kelipatan suatu nilai. Misalnya, int umumnya harus berada di alamat kelipatan 4 pada arsitektur x86-64. _Alignof(int) mengembalikan nilai requirement alignment tersebut (umumnya 4). Padding adalah byte-byte kosong yang ditambahkan compiler di dalam struct untuk memenuhi syarat alignment setiap member.
Aturan padding di balik layar: setiap member ditempatkan pada offset yang merupakan kelipatan alignment-nya. Jika member sebelumnya berakhir pada offset yang tidak memenuhi syarat, compiler menyisipkan byte padding sampai offset berikutnya memenuhi syarat. Setelah semua member ditempatkan, ukuran akhir struct dibulatkan ke kelipatan alignment terbesar dari semua member—agar struct tetap aligned ketika dipakai sebagai elemen array.
Visualisasi Konsep #
Diagram berikut membandingkan tata letak struct A (urutan member buruk: char, int, char) dengan struct B (urutan rapi: int, char, char) untuk menunjukkan bagaimana urutan member memengaruhi jumlah padding:
Pada diagram di atas, kedua struct memiliki tiga member yang sama (char, int, char = total 6 byte), tetapi ukuran akhirnya berbeda karena padding. Pada struct A, member int i ditempatkan setelah char c, sehingga compiler menyisipkan 3 byte padding di antara keduanya agar i berada di offset kelipatan 4. Member char d di akhir hanya menempati 1 byte, tetapi ukuran total dibulatkan ke kelipatan 4 (alignment int) menjadi 12 byte. Pada struct B, member int i ditempatkan paling awal di offset 0, lalu kedua char berdempetan di offset 4 dan 5; hanya 2 byte padding di akhir yang dibutuhkan untuk pembulatan ke kelipatan 4, sehingga total hanya 8 byte. Aturan praktis: susun member dari yang alignment-nya terbesar ke terkecil untuk meminimalkan padding.
#include <stdio.h>
/* struct sederhana untuk demonstrasi padding */
struct Demo {
char c; /* 1 byte di awal */
int i; /* 4 byte */
};
int main(void) {
/* Tanpa padding, seharusnya 1 + 4 = 5 byte.
Dengan padding, ukurannya menjadi 8 byte. */
printf("sizeof(struct Demo) = %zu byte\n", sizeof(struct Demo));
printf("Padahal total member = 1 + 4 = 5 byte\n");
printf("Selisih %zu byte adalah padding\n",
sizeof(struct Demo) - 5);
/* Bandingkan dengan array */
struct Demo arr[4];
printf("\nArray of 4 struct Demo: %zu byte\n", sizeof(arr));
printf("4 * sizeof(struct Demo) = %zu\n", 4 * sizeof(struct Demo));
/* Jarak antar elemen array */
printf("Jarak antar elemen = %zu byte (harusnya = sizeof)\n",
(size_t)((char*)&arr[1] - (char*)&arr[0]));
return 0;
}Output yang Dihasilkan:
sizeof(struct Demo) = 8 byte
Padahal total member = 1 + 4 = 5 byte
Selisih 3 byte adalah padding
Array of 4 struct Demo: 32 byte
4 * sizeof(struct Demo) = 32
Jarak antar elemen = 8 byte (harusnya = sizeof)Mari kita telaah baris-baris kode di atas:
- Baris 5-8 (
struct Demo { char c; int i; };): dua member dengan alignment berbeda.charpunya alignment 1 (bisa di alamat mana pun),intpunya alignment 4 (harus di kelipatan 4). - Baris 13 (
sizeof(struct Demo)): hasil 8 byte—bukan 5. Setelahcdi offset 0, compiler menambahkan 3 byte padding (offset 1, 2, 3) supayaibisa ditempatkan di offset 4. Setelahidi offset 4-7, tidak ada padding akhir tambahan karena ukuran 8 sudah kelipatan alignment 4. - Baris 14-16 (komentar ukuran): menampilkan perhitungan eksplisit—total member 5 byte, selisih 3 byte adalah padding. Pesan ini membantu pembaca memahami asal-usul angka 8.
- Baris 19-21 (
struct Demo arr[4]; sizeof(arr)): array 4 elemen menghasilkan 32 byte. Karena tiap elemen 8 byte, total 4 × 8 = 32 byte—tidak ada padding tambahan antar elemen array, hanya padding internal tiapstruct. - Baris 24-25 (
(char*)&arr[1] - (char*)&arr[0]): menghitung jarak antar alamat elemen pertama dan kedua. Hasilnya 8 byte, sama dengansizeof(struct Demo)—memastikan bahwa jarak antar elemen array persis sama dengan ukuran tipe.
Mengamati Offset dan Mengontrol Padding #
Kita bisa melihat di mana tepatnya padding berada dengan mengamati offset tiap member. Kita juga bisa mengurangi padding dengan menyusun member yang alignment-nya sama berdekatan, atau memakai pragma pack untuk menghapus padding sepenuhnya (dengan harga performa yang lebih rendah pada akses yang unaligned).
#include <stdio.h>
#include <stdalign.h>
/* struct dengan urutan buruk: char, int, char */
struct A {
char c; /* 1 byte */
int i; /* 4 byte */
char d; /* 1 byte */
};
/* struct yang sama dengan urutan member diatur rapi */
struct B {
int i; /* 4 byte */
char c; /* 1 byte */
char d; /* 1 byte */
};
/* struct padat dengan pragma pack */
#pragma pack(push, 1)
struct C {
char c;
int i;
char d;
};
#pragma pack(pop)
int main(void) {
printf("sizeof(struct A) = %zu byte (urutan jelek)\n", sizeof(struct A));
printf("sizeof(struct B) = %zu byte (urutan rapi)\n", sizeof(struct B));
printf("sizeof(struct C) = %zu byte (pragma pack 1)\n", sizeof(struct C));
/* _Alignof: requirement alignment dari tipe */
printf("\nAlignment:\n");
printf(" _Alignof(char) = %zu\n", _Alignof(char));
printf(" _Alignof(int) = %zu\n", _Alignof(int));
printf(" _Alignof(struct A) = %zu\n", _Alignof(struct A));
printf(" _Alignof(struct B) = %zu\n", _Alignof(struct B));
printf(" _Alignof(struct C) = %zu\n", _Alignof(struct C));
/* Alamat member di dalam struct A */
struct A a;
printf("\nOffset member struct A:\n");
printf(" &a.c = %p (offset %zu)\n", (void*)&a.c,
(size_t)((char*)&a.c - (char*)&a));
printf(" &a.i = %p (offset %zu)\n", (void*)&a.i,
(size_t)((char*)&a.i - (char*)&a));
printf(" &a.d = %p (offset %zu)\n", (void*)&a.d,
(size_t)((char*)&a.d - (char*)&a));
return 0;
}Output yang Dihasilkan:
sizeof(struct A) = 12 byte (urutan jelek)
sizeof(struct B) = 8 byte (urutan rapi)
sizeof(struct C) = 6 byte (pragma pack 1)
Alignment:
_Alignof(char) = 1
_Alignof(int) = 4
_Alignof(struct A) = 4
_Alignof(struct B) = 4
_Alignof(struct C) = 1
Offset member struct A:
&a.c = 0x7ffd849d40f4 (offset 0)
&a.i = 0x7ffd849d40f8 (offset 4)
&a.d = 0x7ffd849d40fc (offset 8)Pada contoh di atas, struct A berukuran 12 byte—jauh lebih besar dari total member 6 byte. Penyebabnya bisa dilihat di offset: c di offset 0, i di offset 4 (dengan 3 byte padding setelah c), d di offset 8, lalu 3 byte padding akhir supaya ukuran total kelipatan 4. struct B yang sama tetapi dengan urutan rapi (int dulu, lalu dua char) berukuran 8 byte—lebih hemat 4 byte. struct C dengan #pragma pack(push, 1) menghapus padding sepenuhnya menjadi 6 byte, tetapi _Alignof(struct C) menjadi 1—akses int di dalamnya mungkin lebih lambat pada beberapa arsitektur. Header <stdalign.h> menyediakan macro _Alignof (sejak C11) untuk memeriksa alignment tipe.