Keyword extern adalah penanda eksplisit bahwa sebuah deklarasi variabel atau fungsi merujuk ke simbol yang didefinisikan di tempat lain—di file .c lain, di pustaka standar, atau di static library. Tanpa extern, deklarasi variabel di header bisa menjadi tentative definition yang memicu multiple definition saat di-include berkali-kali. Dengan extern, kita memberi tahu compiler: “variabel ini ada, tapi didefinisikan di file lain—cukup catat tipenya untuk pemeriksaan tipe.” Pada chapter ini kita akan mempraktikkan deklarasi extern di header, peran extern dalam pemanggilan fungsi lintas file, dan penggunaan extern di dalam badan fungsi.

Mekanisme extern #

Saat compiler melihat deklarasi extern int x;, compiler tidak mengalokasikan memori untuk variabel x. Compiler hanya mencatat tipe dan nama simbol ke dalam tabel simbol translation unit tersebut, dengan tanda bahwa definisi akan disediakan oleh translation unit lain. Saat linker menyatukan object file, linker mencari definisi x di object file lain dan mengisi alamat memori yang sebenarnya. Jika tidak ada definisi x di mana pun, linker melaporkan undefined reference.

Untuk fungsi, situasinya sedikit berbeda. Deklarasi fungsi tanpa badan (mis. int jumlah(int a, int b);) secara implisit bersifat extern—kita tidak perlu menulis extern di depan. Keyword extern pada deklarasi fungsi valid tetapi jarang dipakai karena redundant. Untuk variabel, extern sangat penting di header agar tidak menjadi tentative definition.

Demonstrasi: Variabel extern di Header #

Mari kita bangun modul pengatur yang menyimpan dua variabel global publik (max_user dan hitungan_aktif) serta tiga fungsi publik. Header pengatur.h berisi deklarasi extern untuk variabel dan deklarasi fungsi; file pengatur.c berisi definisi aktual variabel dan implementasi fungsi.

File pengatur.h:

c
#ifndef PENGATUR_H
#define PENGATUR_H

/* Deklarasi extern di header */
extern int max_user;
extern int hitungan_aktif;

/* Deklarasi fungsi (implisit extern) */
void pengatur_init(int max);
void pengatur_tambah(void);
int  pengatur_get_hitungan(void);

#endif

File pengatur.c:

c
#include "pengatur.h"

/* Definisi aktual variabel extern -- hanya di sini */
int max_user = 10;
int hitungan_aktif = 0;

void pengatur_init(int max) {
    max_user = max;
    hitungan_aktif = 0;
}

void pengatur_tambah(void) {
    if (hitungan_aktif < max_user) {
        hitungan_aktif++;
    }
}

int pengatur_get_hitungan(void) {
    return hitungan_aktif;
}

File main.c:

c
#include <stdio.h>
#include "pengatur.h"

int main(void) {
    /* Akses variabel extern melalui header */
    printf("Default max_user = %d, hitungan_aktif = %d\n",
           max_user, hitungan_aktif);

    pengatur_init(5);
    for (int i = 0; i < 8; i++) {
        pengatur_tambah();
    }

    printf("Setelah init(5) + 8x tambah: hitungan = %d, max_user = %d\n",
           pengatur_get_hitungan(), max_user);

    return 0;
}

Kompilasi dan jalankan:

bash
gcc -std=c11 -Wall -Wextra -I. -o app main.c pengatur.c && ./app

Output yang Dihasilkan:

text
Default max_user = 10, hitungan_aktif = 0
Setelah init(5) + 8x tambah: hitungan = 5, max_user = 5

Perhatikan bahwa main.c dapat membaca dan menulis max_user walau variabel didefinisikan di pengatur.c. Fungsi pengatur_init(5) mengubah max_user menjadi 5; kemudian 8 kali pemanggilan pengatur_tambah() hanya menambah hitungan_aktif sampai 5 (karena ada batasan hitungan_aktif < max_user). Pada baris terakhir, max_user terbaca sebagai 5 dari main.c—sama dengan nilai yang diset oleh pengatur_init.

Mari kita telaah baris-baris kunci di atas:

  1. Pada pengatur.h baris 7-8 (extern int max_user; dan extern int hitungan_aktif;): deklarasi tanpa definisi. Tidak ada alokasi memori. Keyword extern memberitahu compiler bahwa definisi variabel ada di file .c lain. Header boleh di-include berkali-kali tanpa menyebabkan multiple definition karena deklarasi extern murni adalah forward declaration.
  2. Pada pengatur.h baris 11-13 (deklarasi fungsi): tidak ada extern di depan. Deklarasi fungsi tanpa badan otomatis bersifat extern. Compiler mengizinkan pemanggilan fungsi pengatur_init dari main.c meskipun definisi ada di file lain.
  3. Pada pengatur.c baris 4-5 (int max_user = 10; dan int hitungan_aktif = 0;): definisi aktual. Inilah tempat variabel mendapat alamat memori. Definisi ini hanya boleh muncul sekali di seluruh proyek. Jika dua file .c mendefinisikan int max_user = 10; tanpa static, linker akan melaporkan multiple definition.
  4. Pada main.c baris 6 (printf("Default max_user = %d, hitungan_aktif = %d\n", max_user, hitungan_aktif);): main.c membaca nilai variabel extern. Saat kompilasi, compiler hanya tahu tipe int—tidak tahu alamat. Saat linking, linker mencari definisi max_user di pengatur.o dan menemukan int max_user = 10;. Alamat tersebut diisi ke instruksi mesin di main.o.
  5. Pada main.c baris 11 (pengatur_init(5);): pemanggilan fungsi publik yang mengubah max_user menjadi 5. Setelah pemanggilan ini, nilai max_user di main.c juga berubah menjadi 5—bukti bahwa main.c dan pengatur.c berbagi variabel yang sama.

extern di Dalam Fungsi #

extern tidak harus muncul di header saja. Kita dapat menulis deklarasi extern di dalam badan fungsi untuk mengakses variabel global yang tidak dideklarasikan di header. Pola ini berguna untuk akses yang sangat lokal—mis. satu fungsi saja yang perlu membaca variabel internal modul lain.

c
#include <stdio.h>

/* extern declaration dari variabel yang didefinisikan di file lain */
extern int errno_demo;   /* misal -- di proyek nyata: errno di <errno.h> */

/* Lebih umum: deklarasi extern fungsi tanpa keyword (implisit extern) */
int jumlah(int a, int b);  /* implisit extern */

int main(void) {
    /* Contoh lebih lengkap: dapat juga extern digunakan di dalam fungsi */
    extern int pengaturan_global;   /* definisi ada di file lain */
    printf("pengaturan_global = %d\n", pengaturan_global);
    return 0;
}

int jumlah(int a, int b) { return a + b; }

/* Definisi variabel global yang dapat di-extern dari file lain */
int pengaturan_global = 42;

Output yang Dihasilkan:

text
pengaturan_global = 42

Dalam contoh ini, fungsi main mendeklarasikan extern int pengaturan_global; di dalam badan fungsi. Definisi variabel tersebut berada di akhir file yang sama (int pengaturan_global = 42;), tetapi deklarasi extern di dalam fungsi tetap valid—compiler menyelesaikan referensi saat linking di tahap akhir. Pola seperti ini lebih umum ditemui di kode lama yang belum dipecah ke banyak file; di proyek modern, sebaiknya kumpulkan semua deklarasi extern di header bersama.