Selain tipe dasar, C menyediakan type qualifiers yang memodifikasi sifat tipe tanpa mengubah ukurannya. Empat qualifier utama di C modern adalah const, volatile, restrict, dan _Atomic. Masing-masing memberi tahu compiler sesuatu tentang cara variabel bisa diakses atau dioptimalkan. Pada Chapter ini kita akan memetakan keempatnya, melihat kapan masing-masing dipakai, serta bagaimana qualifier memengaruhi optimisasi compiler dan jaminan thread-safety. Bab ini menutup Section tipe data dan variabel dengan perspektif menyeluruh tentang sifat-sifat tambahan yang bisa disematkan pada tipe.

const: Tanda Tidak Bisa Diubah #

const sudah kita pelajari pada Chapter khusus. Sebagai rekap: const menandai bahwa variabel tidak boleh diubah setelah inisialisasi. Compiler akan menolak penugasan ulang dan bisa melakukan optimisasi berdasarkan jaminan tersebut. const bisa dikombinasikan dengan volatile (misalnya status register yang read-only) atau dengan restrict pada parameter pointer.

c
#include <stdio.h>

/* volatile: nilai bisa berubah dari luar alur program */
volatile int status_register = 0;

int main(void) {
    /* Compiler tidak boleh mengoptimalkan pembacaan status_register */
    while (status_register == 0) {
        /* menunggu perubahan dari sumber eksternal (simulasi) */
        status_register = 1;  /* simulasi: ISR mengubah nilai */
    }
    printf("status berubah menjadi %d\n", status_register);

    /* const: tidak boleh diubah */
    const int max_retries = 3;
    printf("max retries = %d\n", max_retries);
    return 0;
}

Output yang Dihasilkan:

text
status berubah menjadi 1
max retries = 3

Mari kita telaah baris-baris kode di atas:

  1. Baris 4 (volatile int status_register = 0;): variabel global ditandai volatile; compiler tidak boleh menghilangkan pembacaan berulang dalam loop.
  2. Baris 8 (while (status_register == 0)): tanpa volatile, compiler dengan optimisasi -O2 bisa saja membaca status_register sekali lalu menganggap nilainya tidak berubah—menghasilkan loop tak berhingga.
  3. Baris 10 (status_register = 1;): simulasi perubahan dari sumber eksternal seperti interrupt service routine (ISR) pada sistem embedded.
  4. Baris 13 (const int max_retries = 3;): variabel const lokal; nilainya tidak bisa diubah setelah inisialisasi.

Tanpa volatile, loop while (flag == 0) {} yang menunggu perubahan flag dari thread atau ISR lain bisa diubah compiler menjadi if (flag == 0) while(1) {}—optimisasi yang valid namun mematikan. volatile memaksa compiler membaca ulang nilai pada setiap iterasi.

volatile: Nilai Bisa Berubah Eksternal #

Kata kunci volatile memberi tahu compiler bahwa suatu variabel bisa berubah nilainya melalui jalur di luar kode program biasa—misalnya oleh ISR, thread lain, atau perangkat keras. Compiler tidak boleh mengoptimalkan pembacaan atau penulisan variabel volatile dengan menyimpannya di register atau menghilangkan pembacaan yang tampaknya tidak berguna.

Pemakaian utama volatile ada pada:

  • Memory-mapped I/O: register perangkat keras yang dipetakan ke alamat memori.
  • Flag ISR: variabel yang diubah oleh interrupt service routine.
  • Variable shared antar thread (meskipun C11 _Atomic lebih disarankan).
  • Variabel yang dimodifikasi oleh debugger atau lingkungan runtime.
c
/* Contoh memory-mapped register (ilustratif, bukan dijalankan) */
volatile unsigned int *UART_DATA = (volatile unsigned int *)0x40004000;

/* Tanpa volatile, compiler bisa saja menyimpan nilai
   *UART_DATA di register dan tidak pernah membaca ulang,
   menyebabkan driver kehilangan data yang baru tiba. */

restrict: Pointer Tanpa Alias #

Kata kunci restrict diperkenalkan di C99 dan hanya berlaku untuk pointer. restrict menyatakan bahwa pointer tertentu adalah satu-satunya cara untuk mengakses objek yang ditunjuk selama masa hidup pointer tersebut—dengan kata lain, tidak ada alias. Compiler memakai jaminan ini untuk optimisasi yang lebih agresif, terutama pada loop yang mengakses array.

c
#include <stdio.h>

/* restrict: pointer tidak punya alias */
void tambah_array(int *restrict dest,
                  const int *restrict a,
                  const int *restrict b,
                  size_t n) {
    for (size_t i = 0; i < n; i++) {
        dest[i] = a[i] + b[i];
    }
}

int main(void) {
    int x[3] = {1, 2, 3};
    int y[3] = {10, 20, 30};
    int hasil[3];

    tambah_array(hasil, x, y, 3);
    printf("hasil = %d, %d, %d\n", hasil[0], hasil[1], hasil[2]);
    return 0;
}

Output yang Dihasilkan:

text
hasil = 11, 22, 33

Pada baris 4, parameter int *restrict dest menyatakan bahwa dest adalah satu-satunya pointer yang mengakses array tujuan selama pemanggilan fungsi. Begitu juga a dan b tidak boleh saling ber-alias. Compiler bisa mengoptimalkan loop dengan asumsi bahwa penulisan ke dest[i] tidak akan memengaruhi pembacaan a[i] atau b[i]. Pada baris 15, pemanggilan dengan tiga array berbeda (hasil, x, y) memenuhi janji restrict. Jika pemanggil melanggar janji ini (misalnya memanggil tambah_array(x, x, y, 3)), hasilnya undefined behavior.

_Atomic: Akses Thread-Safe (C11) #

Kata kunci _Atomic diperkenalkan di C11 sebagai bagian dari model concurrency standar C. Variabel _Atomic menjamin bahwa operasi baca dan tulis terhadap variabel tersebut bersifat atomic—tidak bisa disela oleh thread lain di tengah operasi. Hal ini menghindari data race pada variabel yang diakses banyak thread.

c
#include <stdio.h>
#include <stdatomic.h>
#include <threads.h>

atomic_int counter = 0;

int tambah_thread(void *arg) {
    (void)arg;
    for (int i = 0; i < 1000; i++) {
        atomic_fetch_add(&counter, 1);
    }
    return 0;
}

int main(void) {
    thrd_t t1, t2;
    thrd_create(&t1, tambah_thread, NULL);
    thrd_create(&t2, tambah_thread, NULL);
    thrd_join(t1, NULL);
    thrd_join(t2, NULL);

    printf("counter = %d (seharusnya 2000)\n", atomic_load(&counter));
    return 0;
}

Output yang Dihasilkan:

text
counter = 2000 (seharusnya 2000)

Pada baris 6, atomic_int counter adalah alias dari _Atomic int counter. Variabel ini bisa diakses aman dari banyak thread. Pada baris 11, fungsi atomic_fetch_add(&counter, 1) melakukan operasi increment atomik—tidak ada thread lain yang bisa membaca nilai sementara. Pada baris 19-20, dua thread dijalankan dan bergabung kembali. Pada baris 22, atomic_load(&counter) membaca nilai akhir secara atomik. Tanpa _Atomic, operasi counter++ pada dua thread bersamaan bisa menyebabkan lost update—salah satu inkremen hilang karena dua thread membaca nilai yang sama sebelum salah satunya menulis.

Kombinasi Qualifier #

Keempat qualifier bisa dikombinasikan pada satu deklarasi (meskipun kombinasi tertentu jarang masuk akal). Contoh:

  • const volatile int *reg: pointer ke register hardware yang read-only; nilai bisa berubah dari luar (perangkat keras) tetapi kode tidak boleh menulisnya.
  • int *restrict const p: const pointer dengan janji restrict; pointer tidak bisa diubah dan tidak punya alias.
  • _Atomic const int x: variabel atomik yang nilainya tidak boleh diubah setelah inisialisasi (jarang dipakai).

Berikut adalah ringkasan keempat qualifier:

QualifierSejakTargetTujuan
constC89variabel, pointer, parametermelarang penugasan ulang
volatileC89variabel, pointermencegah optimisasi yang menghilangkan pembacaan
restrictC99pointer sajajanji tidak ada alias untuk optimisasi
_AtomicC11variabel, tipeoperasi baca/tulis thread-safe

Dengan keempat qualifier ini, C menyediakan kontrol yang sangat halus terhadap cara variabel diakses dan dioptimalkan. Pemakaian qualifier yang tepat menghasilkan kode yang aman (mencegah bug koncurrency), cepat (memungkinkan optimisasi), dan jelas (mendokumentasikan intensi). Section tipe data dan variabel ini melengkapi kita dengan pondasi yang dibutuhkan untuk memahami topik-topik lanjutan berikutnya: operator, kontrol alur, fungsi, dan pointer.