Header <stddef.h> adalah salah satu header paling fundamental di C. Walaupun isinya sedikit, hampir setiap program C mengandalkannya. Header ini mendefinisikan tipe size_t (untuk merepresentasikan ukuran objek), ptrdiff_t (hasil pengurangan dua pointer), konstanta NULL (pointer null), dan makro offsetof (mengambil offset field dalam struct). Tipe-tipe ini compiler-defined, artinya implementasinya bergantung pada platform—di sistem 64-bit, size_t adalah 8 byte. Pada chapter ini kita akan meninjau keempat simbol tersebut beserta penggunaannya.

size_t: Tipe untuk Ukuran Objek #

Tipe size_t didefinisikan sebagai unsigned integer type yang cukup besar untuk menyimpan ukuran objek maksimum yang dapat dialokasikan. Fungsi-fungsi standar seperti strlen, sizeof, malloc semua memakai size_t sebagai tipe ukuran. Untuk mencetak size_t dengan printf, gunakan format specifier %zu.

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

int main(void) {
    /* size_t: tipe untuk merepresentasikan ukuran objek */
    size_t panjang = strlen("Belajar");
    size_t ukuran_array = sizeof(int[10]);
    printf("strlen(\"Belajar\") = %zu\n", panjang);
    printf("sizeof(int[10])  = %zu byte\n", ukuran_array);
    printf("sizeof(size_t)   = %zu byte\n", sizeof(size_t));

    /* ptrdiff_t: tipe untuk hasil pengurangan dua pointer */
    int arr[] = {10, 20, 30, 40, 50};
    int *p1 = &arr[1];
    int *p2 = &arr[4];
    ptrdiff_t selisih = p2 - p1;
    printf("\np2 - p1 = %td elemen\n", selisih);

    /* NULL: konstanta pointer null */
    int *q = NULL;
    printf("\nq = %p (NULL)\n", (void *)q);
    if (q == NULL) {
        printf("q belum dialokasikan\n");
    }

    return 0;
}

Output yang Dihasilkan:

text
strlen("Belajar") = 7
sizeof(int[10])  = 40 byte
sizeof(size_t)   = 8 byte

p2 - p1 = 3 elemen

q = (nil) (NULL)
q belum dialokasikan

Mari kita telaah baris-baris kode di atas:

  1. Baris 7 (size_t panjang = strlen("Belajar");): fungsi strlen dari <string.h> mengembalikan size_t. Menyimpan hasilnya ke int berisiko truncation bila string lebih panjang dari INT_MAX.
  2. Baris 9 (printf("... = %zu\n", panjang)): format specifier %zu adalah cara portabel mencetak size_t. Memakai %d dapat memicu undefined behavior karena size_t biasanya lebih besar dari int di platform 64-bit.
  3. Baris 13 (ptrdiff_t selisih = p2 - p1;): pengurangan dua pointer menghasilkan ptrdiff_t (signed). Pada contoh, &arr[4] - &arr[1] menghasilkan 3 karena selisih indeks adalah 4 - 1. Format specifier untuk ptrdiff_t adalah %td.
  4. Baris 18 (int *q = NULL;): NULL adalah makro yang di-expand ke konstanta pointer null. Implementasinya bervariasi: ((void*)0) di banyak sistem, atau 0 literal.
  5. Baris 19 (printf("q = %p\n", (void *)q);): specifier %p mencetak alamat pointer. Hasilnya (nil) menunjukkan pointer null. Cast ke void * diperlukan karena %p mengharapkan void *, bukan int *.

NULL: Pointer Null #

NULL adalah konstanta yang merepresentasikan pointer null—pointer yang tidak menunjuk ke lokasi memori manapun. Konvensi di C: fungsi yang mengembalikan pointer memakai NULL sebagai penanda kegagalan (fopen, malloc, getenv). Pemanggil wajib memeriksa NULL sebelum memakai pointer.

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

int main(void) {
    /* NULL sebagai penanda awal pointer yang belum dialokasikan */
    int *data = NULL;
    printf("Inisial: data = %p\n", (void *)data);

    /* Alokasi 100 elemen */
    data = malloc(100 * sizeof(int));
    if (data == NULL) {
        fprintf(stderr, "malloc gagal\n");
        return 1;
    }

    /* Pakai data */
    for (int i = 0; i < 100; i++) data[i] = i * 2;
    printf("Setelah alokasi: data[99] = %d\n", data[99]);

    /* Bebaskan, lalu set ke NULL untuk mencegah double-free */
    free(data);
    data = NULL;

    /* Pemeriksaan NULL melindungi dari dereferensi setelah free */
    if (data != NULL) {
        printf("Mengakses data[0] = %d\n", data[0]);
    } else {
        printf("data sudah NULL, aman dari dereferensi\n");
    }

    return 0;
}

Output yang Dihasilkan:

text
Inisial: data = (nil)
Setelah alokasi: data[99] = 198
data sudah NULL, aman dari dereferensi

Pola inisialisasi int *data = NULL; adalah praktik penting: bila kita lupa mengalokasikan memori, dereferensi *data akan langsung segfault di sistem modern, bukan mengakses memori acak yang mungkin berhasil. Setelah free(data), segera set data = NULL untuk mencegah double-free dan use-after-free—pemanggilan free(NULL) well-defined dan tidak melakukan apa-apa, sedangkan free dua kali pada pointer yang sama adalah undefined behavior.

offsetof: Mengambil Offset Field Struct #

Makro offsetof(type, member) mengembalikan offset byte dari awal struct ke field member. Hasilnya bertipe size_t. Makro ini sangat berguna untuk memahami layout struct, mendiagnosis padding, atau mengimplementasikan struktur data generik seperti linked list intrusif.

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

/* Struct dengan beberapa field berbeda tipe */
typedef struct {
    char id;          /* 1 byte, padding 3 byte */
    int  nilai;       /* 4 byte */
    double rata_rata; /* 8 byte */
    char nama[16];    /* 16 byte */
} siswa;

int main(void) {
    printf("=== Layout struct siswa ===\n");
    printf("sizeof(siswa)       = %zu byte\n", sizeof(siswa));
    printf("offsetof(siswa, id)         = %zu\n", offsetof(siswa, id));
    printf("offsetof(siswa, nilai)      = %zu\n", offsetof(siswa, nilai));
    printf("offsetof(siswa, rata_rata)  = %zu\n", offsetof(siswa, rata_rata));
    printf("offsetof(siswa, nama)       = %zu\n", offsetof(siswa, nama));

    /* Demonstrasikan alignment dengan padding */
    printf("\nsizeof(char)   = %zu\n", sizeof(char));
    printf("sizeof(int)    = %zu\n", sizeof(int));
    printf("sizeof(double) = %zu\n", sizeof(double));
    printf("sizeof(char[16])= %zu\n", sizeof(char[16]));

    /* Total tanpa padding: 1+4+8+16 = 29 byte. */
    /* Actual size: 32 byte karena padding setelah id. */

    return 0;
}

Output yang Dihasilkan:

text
=== Layout struct siswa ===
sizeof(siswa)       = 32 byte
offsetof(siswa, id)         = 0
offsetof(siswa, nilai)      = 4
offsetof(siswa, rata_rata)  = 8
offsetof(siswa, nama)       = 16

sizeof(char)   = 1
sizeof(int)    = 4
sizeof(double) = 8
sizeof(char[16])= 16

Mari kita analisis layout: id di offset 0, lalu tiga byte padding agar nilai (yang butuh alignment 4-byte) mulai di offset 4. Field rata_rata (alignment 8-byte) langsung di offset 8 karena 4 + 4 = 8 sudah rapi. Field nama di offset 16—sebelumnya 8 + 8 = 16, tidak perlu padding. Total tanpa padding adalah 1 + 4 + 8 + 16 = 29 byte, tetapi sizeof(siswa) = 32 byte karena trailing padding agar alignment struct tetap konsisten bila ada array siswa[]. Memahami layout struct penting saat kita berkomunikasi dengan kode yang ditulis dalam bahasa lain (misalnya FFI Python, atau file format biner).