Setiap tipe data di C memiliki alignment requirement—syarat bahwa alamat memori variabel harus kelipatan suatu nilai tertentu. Sebuah int, misalnya, biasanya harus berada di alamat kelipatan 4 byte. Bila struct berisi beberapa field dengan alignment berbeda, compiler menyisipkan byte padding di antara field untuk memenuhi syarat tersebut. Hal ini membuat sizeof(struct) sering lebih besar dari jumlah sizeof setiap field—pemahaman alignment penting untuk efisiensi memori, binary compatibility, dan komunikasi dengan hardware. Pada chapter ini kita akan mengukur alignment setiap tipe dasar, melihat padding di struct, dan bereksperimen dengan #pragma pack untuk menghilangkan padding.

Konsep Alignment #

Standar C (§6.2.8) mendefinisikan alignment sebagai implementation-defined nilai yang menentukan alamat di mana objek dapat dialokasikan. Setiap tipe memiliki alignment:

  • char — alignment 1 (boleh di alamat mana pun).
  • short — alignment 2 (boleh di alamat genap).
  • int, float — alignment 4 (boleh di alamat kelipatan 4).
  • long, double, void* (pada sistem 64-bit) — alignment 8.

Mekanisme kerja: CPU modern mengakses memori dalam chunk 8 atau 16 byte. Bila sebuah int berada di alamat 0x1001 (bukan kelipatan 4), CPU harus melakukan dua pembacaan (ke alamat 0x1000 dan 0x1004), lalu menggabungkan dengan operasi mask/shift. Pada arsitektur tertentu (misalnya ARM lama), akses unaligned menyebabkan exception—program crash. Pada x86-64, akses unaligned diizinkan tetapi lebih lambat.

Mengukur Alignment dengan _Alignof #

Operator _Alignof(T) (atau alignof(T) dari <stdalign.h>) mengembalikan alignment tipe T dalam byte.

c
#include <stdio.h>
#include <stddef.h>
#include <stdalign.h>

int main(void) {
    printf("== Alignment setiap tipe ==\n");
    printf("  _Alignof(char)   = %zu\n", _Alignof(char));
    printf("  _Alignof(short)  = %zu\n", _Alignof(short));
    printf("  _Alignof(int)    = %zu\n", _Alignof(int));
    printf("  _Alignof(long)   = %zu\n", _Alignof(long));
    printf("  _Alignof(double) = %zu\n", _Alignof(double));
    printf("  _Alignof(void*)  = %zu\n", _Alignof(void*));

    printf("\n== sizeof setiap tipe ==\n");
    printf("  sizeof(char)     = %zu\n", sizeof(char));
    printf("  sizeof(short)    = %zu\n", sizeof(short));
    printf("  sizeof(int)      = %zu\n", sizeof(int));
    printf("  sizeof(long)     = %zu\n", sizeof(long));
    printf("  sizeof(double)   = %zu\n", sizeof(double));
    printf("  sizeof(void*)    = %zu\n", sizeof(void*));

    return 0;
}

Output yang Dihasilkan:

text
== Alignment setiap tipe ==
  _Alignof(char)   = 1
  _Alignof(short)  = 2
  _Alignof(int)    = 4
  _Alignof(long)   = 8
  _Alignof(double) = 8
  _Alignof(void*)  = 8

== sizeof setiap tipe ==
  sizeof(char)     = 1
  sizeof(short)    = 2
  sizeof(int)      = 4
  sizeof(long)     = 8
  sizeof(double)   = 8
  sizeof(void*)    = 8

Pada sistem x86-64 Linux, alignment setiap tipe sama dengan ukurannya untuk tipe dasar—int 4 byte alignment 4, double 8 byte alignment 8. Pengecualian: long pada Windows 64-bit memiliki ukuran 4 byte tetapi alignment 8 (LLP64 model). Portabilitas antar sistem perlu memperhatikan perbedaan ini.

Padding di Struct #

Saat compiler menyusun struct, compiler menyisipkan byte padding agar setiap field memenuhi alignment-nya. Sebagai contoh, struct TanpaPack berisi char a; int b; char c;. Tanpa padding, ukurannya akan 1 + 4 + 1 = 6 byte, tetapi alignment int adalah 4—b harus di alamat kelipatan 4.

c
#include <stdio.h>
#include <stddef.h>

/* Struct tanpa pragma pack -- default alignment */
struct TanpaPack {
    char a;       /* 1 byte + 3 byte padding */
    int  b;       /* 4 byte, alignment 4 */
    char c;       /* 1 byte + 3 byte padding */
};

/* Struct dengan pragma pack(1) -- no padding */
#pragma pack(push, 1)
struct DenganPack1 {
    char a;
    int  b;
    char c;
};
#pragma pack(pop)

/* Struct dengan pragma pack(2) */
#pragma pack(push, 2)
struct DenganPack2 {
    char a;
    int  b;
    char c;
};
#pragma pack(pop)

int main(void) {
    printf("sizeof(struct TanpaPack)    = %zu (alignment default)\n",
           sizeof(struct TanpaPack));
    printf("sizeof(struct DenganPack1)  = %zu (pragma pack 1)\n",
           sizeof(struct DenganPack1));
    printf("sizeof(struct DenganPack2)  = %zu (pragma pack 2)\n",
           sizeof(struct DenganPack2));

    /* Tunjukkan offset setiap field di struct default */
    printf("\nOffset struct TanpaPack:\n");
    printf("  a: %zu, b: %zu, c: %zu\n",
           offsetof(struct TanpaPack, a),
           offsetof(struct TanpaPack, b),
           offsetof(struct TanpaPack, c));

    return 0;
}

Output yang Dihasilkan:

text
sizeof(struct TanpaPack)    = 12 (alignment default)
sizeof(struct DenganPack1)  = 6 (pragma pack 1)
sizeof(struct DenganPack2)  = 8 (pragma pack 2)

Offset struct TanpaPack:
  a: 0, b: 4, c: 8

Pada struct TanpaPack (default alignment), ukuran struct adalah 12 byte—bukan 6 byte. Field a di offset 0, lalu 3 byte padding (offset 1-3), field b di offset 4 (kelipatan 4, sesuai alignment int), lalu field c di offset 8. Setelah c, ada 3 byte padding lagi (offset 9-11) agar total ukuran struct kelipatan alignment maksimum (4). Inilah alasan sizeof(struct TanpaPack) = 12.

Pada struct DenganPack1 (#pragma pack(1)), padding dihilangkan sepenuhnya. Ukuran turun ke 6 byte—tetapi akses ke b sekarang unaligned (di offset 1, bukan kelipatan 4). Pada x86-64, ini hanya lebih lambat; pada ARM lama, ini bisa menyebabkan crash.

Pada struct DenganPack2 (#pragma pack(2)), alignment maksimum di-set ke 2. Field b sekarang boleh di offset kelipatan 2 (bukan 4), sehingga b di offset 2 dan total ukuran 8 byte—kompromi antara ukuran dan alignment.

Mari kita telaah baris-baris kode di atas:

  1. Baris 4 (struct TanpaPack { char a; int b; char c; };): deklarasi struct dengan urutan field berbeda alignment. Compiler bebas menyisipkan padding untuk memenuhi alignment setiap field.
  2. Baris 11 (#pragma pack(push, 1)): menyimpan pengaturan pack saat ini ke stack dan menggantinya dengan 1. Setelah direktif ini, semua struct yang dideklarasikan akan memiliki alignment maksimum 1—tidak ada padding.
  3. Baris 12-16 (struct DenganPack1 { ... };): struct yang dipengaruhi #pragma pack(1). Semua field alignment 1, tidak ada padding.
  4. Baris 17 (#pragma pack(pop)): mengembalikan pengaturan pack ke nilai sebelum push. Pola push/pop memastikan pengaturan pack tidak “bocor” ke kode lain.
  5. Baris 31 (offsetof(struct TanpaPack, b)): macro dari <stddef.h> yang mengembalikan offset field dalam struct, dalam byte. Hasil 4 menunjukkan b berada di byte ke-4—bukan ke-1—karena padding 3 byte setelah a.