Keyword static pada deklarasi variabel atau fungsi di tingkat file (di luar fungsi) mengubah linkage dari external menjadi internal. Artinya, simbol tersebut hanya terlihat di dalam translation unit tempat ia didefinisikan—linker tidak akan mengeksposnya ke file .c lain. Fasilitas ini adalah mekanisme utama encapsulation di C: kita dapat menyembunyikan helper internal dan state privat modul tanpa khawatir tabrakan dengan nama serupa di file lain. Pada chapter ini kita akan mempraktikkan static di tingkat file, memverifikasi bahwa dua file .c dapat memiliki variabel static bernama sama tanpa konflik, dan memeriksa tabel simbol hasil kompilasi via nm.

Mekanisme Internal Linkage #

Saat compiler melihat deklarasi static int counter = 0; di tingkat file, compiler menempatkan variabel counter di BSS section (jika tidak diinisialisasi) atau data section (jika diinisialisasi), dan menandai simbol sebagai local di tabel simbol. Linker kemudian mengabaikan simbol tersebut saat menyatukan object file—simbol tidak akan terlihat dari file lain. Konsekuensinya, dua file .c dapat memiliki variabel static bernama sama persis (mis. counter) tanpa konflik; keduanya adalah variabel yang berbeda di memori.

Hal yang sama berlaku untuk fungsi: static void helper(void) { ... } hanya dapat dipanggil dari dalam file .c tempat ia didefinisikan. Fungsi static tidak dapat dipanggil dari file .c lain bahkan jika kita menambahkan deklarasi manual di file lain—linker tidak akan menemukan simbolnya. Sifat ini sangat berguna untuk menyembunyikan helper internal dari API publik modul.

Demonstrasi: Dua File dengan static counter #

Mari kita buat dua file .c yang masing-masing memiliki variabel static int counter = 0;. Kedua file memiliki fungsi publik tambah_a dan tambah_b yang masing-masing menginkrementasi counternya sendiri. File main.c memanggil kedua fungsi dan kita amati bahwa kedua counter bersifat independen.

File counter_a.c:

c
#include <stdio.h>

/* static: counter ini hanya dikenal di file counter_a.c */
static int counter = 0;

void tambah_a(void) {
    counter++;
    printf("[A] counter = %d\n", counter);
}

File counter_b.c:

c
#include <stdio.h>

/* static: counter ini hanya dikenal di file counter_b.c */
static int counter = 0;

void tambah_b(void) {
    counter++;
    printf("[B] counter = %d\n", counter);
}

File main.c:

c
/* Header sederhana untuk fungsi publik */
void tambah_a(void);
void tambah_b(void);

int main(void) {
    tambah_a();
    tambah_a();
    tambah_b();
    tambah_a();
    tambah_b();
    tambah_b();
    return 0;
}

Kompilasi dan jalankan:

bash
gcc -std=c11 -Wall -Wextra -o app main.c counter_a.c counter_b.c && ./app

Output yang Dihasilkan:

text
[A] counter = 1
[A] counter = 2
[B] counter = 1
[A] counter = 3
[B] counter = 2
[B] counter = 3

Perhatikan bahwa counter di counter_a.c mencapai 3 (dipanggil 3 kali), sementara counter di counter_b.c mencapai 3 terpisah (dipanggil 3 kali). Keduanya benar-benar variabel yang berbeda di memori meskipun namanya sama persis. Tanpa keyword static, linker akan melaporkan multiple definition untuk counter.

Mari kita telaah baris-baris kunci di atas:

  1. Pada counter_a.c baris 4 (static int counter = 0;): deklarasi dengan static. Variabel counter dialokasikan di data section dan ditandai sebagai simbol lokal. File counter_b.c dapat mendefinisikan variabel static dengan nama yang sama tanpa konflik.
  2. Pada counter_a.c baris 6 (void tambah_a(void)): fungsi tanpa static di tingkat file—otomatis memiliki external linkage. Fungsi ini terlihat oleh linker dan dapat dipanggil dari main.c. Inilah API publik modul counter_a.
  3. Pada counter_b.c baris 4 (static int counter = 0;): variabel counter yang sama sekali berbeda dengan yang di counter_a.c. Walaupun namanya sama, internal linkage membuat kedua variabel terpisah di memori.
  4. Pada main.c baris 2-3 (void tambah_a(void); void tambah_b(void);): deklarasi fungsi publik. Tidak perlu extern karena deklarasi fungsi (tanpa badan) implisit extern. Kita juga tidak perlu meng-include header—pendeklarasian langsung di main.c valid meski praktik yang lebih baik adalah mengumpulkan deklarasi publik di header bersama.
  5. Pola pemanggilan: urutan tambah_a() -> tambah_a() -> tambah_b() -> tambah_a() -> tambah_b() -> tambah_b() menghasilkan kenaikan terpisah untuk masing-masing counter. Counter [A] naik tiga kali menjadi 3, counter [B] naik tiga kali menjadi 3—tetapi melalui jalur yang berbeda di memori.

Memverifikasi dengan nm #

Untuk meyakinkan diri bahwa static benar-benar mengubah linkage, kita dapat memeriksa tabel simbol hasil kompilasi menggunakan tool nm. Kompilasi masing-masing file .c menjadi object file:

bash
gcc -std=c11 -Wall -Wextra -c counter_a.c -o counter_a.o
gcc -std=c11 -Wall -Wextra -c counter_b.c -o counter_b.o
nm counter_a.o

Output nm counter_a.o:

text
0000000000000000 b counter
                 U printf
0000000000000000 T tambah_a

Output nm counter_b.o:

text
0000000000000000 b counter
                 U printf
0000000000000000 T tambah_b

Mari kita interpretasikan simbol-simbol di atas:

  1. b counter: huruf kecil b berarti simbol berada di BSS section (data tidak diinisialisasi) dengan local linkage (internal). Inilah efek static. Linker tidak akan mengekspos simbol ini ke file lain.
  2. T tambah_a dan T tambah_b: huruf besar T berarti simbol di text section dengan external linkage. Linker mengizinkan kedua fungsi ini dirujuk dari object file lain.
  3. U printf: huruf U berarti simbol undefined—counter_a.o membutuhkan printf tetapi tidak mendefinisikannya. Linker akan mencari printf di pustaka standar libc dan mencocokkannya saat linking.

Verifikasi nm ini sangat berguna untuk memastikan bahwa keyword static bekerja sesuai harapan. Dalam proyek nyata, menjalankan nm pada object file membantu memverifikasi bahwa kita tidak tanpa sengaja mengekspos simbol yang seharusnya internal.

Penggunaan Praktis static #

Beberapa pola umum yang memanfaatkan static di tingkat file:

  1. Helper internal: fungsi static hanya dipanggil dalam satu file .c dan tidak boleh muncul di API publik. Contoh: static int validate_input(int x) yang dipakai oleh beberapa fungsi publik di modul.
  2. State privat modul: variabel static yang menyimpan state yang hanya boleh diakses via fungsi publik. Contoh: static int pool_size = 0; di pool.c yang diakses via pool_alloc() dan pool_free().
  3. Konstanta internal: array lookup atau tabel yang hanya dipakai di satu file. Contoh: static const char *month_names[] = {"Jan", "Feb", ...}; di date.c.