Lingkup dan Masa Hidup

Perbedaan variabel lokal dan global, aturan lingkup, serta perilaku shadowing.

Daftar isi

Lingkup (scope) adalah daerah kode tempat sebuah nama dikenali, sedangkan masa hidup (lifetime) adalah rentang waktu variabel menempati memori selama program berjalan. Topik ini membedah keduanya untuk variabel lokal dan global, termasuk perilaku shadowing ketika dua deklarasi memakai nama yang sama. Setelah topik ini dipahami, letak deklarasi tidak lagi dipilih secara acak karena konsekuensinya diketahui dengan pasti.

Lingkup blok dan variabel lokal#

Lingkup terkecil dalam C adalah blok, yaitu sekumpulan pernyataan yang diapit kurung kurawal. Variabel yang dideklarasikan di dalam blok dikenali mulai baris deklarasinya sampai tanda kurung kurawal penutup blok, dan setelah itu namanya tidak lagi berarti. Parameter fungsi berlaku sama, karena parameter pada dasarnya adalah variabel lokal milik tubuh fungsi.

c Contoh: lingkup variabel dalam blok
// tidak-dijalankan
int main(void)
{
    int luar = 1;

    {
        int dalam = 2;        /* dikenali hanya di blok ini */
        printf("%d %d\n", luar, dalam);
    }
    /* printf("%d\n", dalam);  error: dalam tidak dikenali */
    printf("%d\n", luar);
    return 0;
}

Blok yang berada di dalam blok lain dapat membaca variabel milik blok luarnya, sebagaimana dalam bersanding dengan luar pada contoh di atas. Arah sebaliknya tidak berlaku, karena kode di luar blok tidak pernah mengenal variabel milik blok dalamnya. Aturan sederhana ini berlaku berlapis-lapis pada perulangan, percabangan, maupun fungsi yang saling memanggil. Lingkup yang rapat juga memudahkan pencarian asal sebuah nilai, karena deklarasinya selalu berdekatan dengan pemakaiannya.

Blok yang paling sering ditemui adalah tubuh perulangan dan percabangan. Variabel yang dideklarasikan di dalam tubuh perulangan dibuat ulang pada setiap iterasi, sehingga nilainya tidak terbawa antarputaran. Kebiasaan mendeklarasikan variabel sedekat mungkin dengan pemakaiannya menjadikan setiap blok sebagai unit yang mandiri dan mudah dipahami.

Masa hidup variabel lokal#

Variabel lokal memiliki masa hidup otomatis (automatic storage duration), yaitu dibuat ketika eksekusi masuk ke bloknya dan dimusnahkan ketika eksekusi keluar dari blok itu. Durasi ini berlaku untuk parameter juga, karena parameter dibuat bersamaan dengan tubuh fungsi yang menampungnya. Konsekuensinya, nilai yang dituliskan ke variabel lokal pada satu pemanggilan tidak terbawa ke pemanggilan berikutnya. Program berikut memanggil satu fungsi dua kali untuk memperlihatkan akibatnya.

c masa-hidup-lokal.c
#include <stdio.h>

void catat_kunjungan(void)
{
    int jumlah = 0;           /* dibuat ulang setiap pemanggilan */

    jumlah = jumlah + 1;
    printf("Kunjungan ke-%d\n", jumlah);
}

int main(void)
{
    catat_kunjungan();
    catat_kunjungan();
    return 0;
}
text Keluaran
Kunjungan ke-1
Kunjungan ke-1

Meskipun fungsi dipanggil dua kali, kedua pemanggilan mencetak nilai yang sama karena jumlah selalu berawal dari nol. Antara satu pemanggilan dan pemanggilan berikutnya, memori milik jumlah sudah lepas dan dapat dipakai kembali oleh bagian lain program. Apabila nilai memang perlu dipertahankan antarpanggilan, kelas penyimpanan static memberikan solusinya tanpa perlu mengubah lingkup variabelnya.

Konsep yang sejajar dengan masa hidup variabel adalah keadaan pemanggilan fungsi itu sendiri. Parameter, variabel lokal, dan posisi eksekusi setiap fungsi disimpan pada stack, dan seluruhnya dibebaskan ketika fungsi kembali. Pemahaman ini menjelaskan mengapa rekursi menuntut memori stack yang sebanding dengan kedalaman pemanggilannya.

Variabel global#

Variabel yang dideklarasikan di luar semua fungsi disebut variabel global, dengan lingkup mulai baris deklarasinya sampai akhir berkas. Semua fungsi yang tertulis setelahnya dapat membaca dan mengubahnya tanpa perlu mengoper apa pun. Masa hidupnya menyatu dengan program, yaitu dibuat saat program dimulai dan dibebaskan saat program berakhir. Berbeda dari variabel lokal, variabel global yang tidak diberi nilai awal otomatis berisi nol. Deklarasinya juga mudah dicari karena selalu berada di bagian atas berkas, sebelum fungsi pertama.

c Contoh: variabel global diakses dua fungsi
// tidak-dijalankan
int skor = 0;                 /* dikenali semua fungsi di bawahnya */

void tambah_skor(int poin)
{
    skor = skor + poin;       /* langsung mengubah variabel global */
}

void tampilkan_skor(void)
{
    printf("Skor: %d\n", skor);
}

Kemudahan inilah yang sekaligus menjadi risikonya. Fungsi yang menyentuh variabel global membawa ketergantungan tersembunyi, sehingga perilakunya sulit dilacak dan tubuhnya tidak mudah dipindahkan ke program lain. Batasi variabel global untuk keadaan yang memang dimiliki seluruh program, dan utamakan parameter sebagai jalur data antarfungsi. Dengan pembatasan itu, tanda tangan setiap fungsi tetap menceritakan seluruh masukan yang dibutuhkannya. Fungsi yang berdiri dengan parameternya sendiri juga lebih mudah diuji, karena tidak perlu menyiapkan keadaan global terlebih dahulu.

Shadowing: nama yang menaungi#

Deklarasi di dalam blok boleh memakai nama yang sudah dipakai di luar blok. Keadaan ini disebut shadowing, yaitu nama yang dalam menaungi nama yang luar selama blok dalam berjalan. Nama luar kembali dikenali begitu blok dalam berakhir. Program berikut menaungi sebuah variabel global dengan variabel lokal yang memakai nama sama.

c penaungan.c
#include <stdio.h>

int nilai = 5;                /* variabel global */

void tampilkan(void)
{
    printf("Di dalam fungsi : nilai = %d\n", nilai);
}

int main(void)
{
    printf("Sebelum blok    : nilai = %d\n", nilai);

    {
        int nilai = 100;      /* menaungi variabel global */

        printf("Di dalam blok   : nilai = %d\n", nilai);
    }

    printf("Setelah blok    : nilai = %d\n", nilai);
    tampilkan();
    return 0;
}
text Keluaran
Sebelum blok    : nilai = 5
Di dalam blok   : nilai = 100
Setelah blok    : nilai = 5
Di dalam fungsi : nilai = 5

Baris kedua mencetak 100 karena deklarasi lokal menaungi variabel global di sepanjang blok itu. Tiga baris lainnya kembali mencetak 5 karena pembacaan dilakukan di luar lingkup deklarasi tersebut. Penaungan sah menurut standar, tetapi hampir selalu menyesatkan pembaca kode karena satu nama terasa punya dua arti.

Penaungan tidak terbatas pada variabel global. Deklarasi lokal di blok dalam dapat pula menaungi parameter fungsi atau variabel lokal blok luarnya, dan aturan penyelesaiannya sama, yaitu nama yang paling dalam menang. Semakin dalam naungan terjadi, semakin mudah pula pembaca kode kehilangan jejak nilai yang dimaksud.

Kesalahan umum#

Kesalahan pada lingkup jarang memunculkan pesan yang jelas, sehingga daftar berikut perlu diperiksa setiap kali perilaku program terasa tidak masuk akal.

  • Membaca variabel lokal yang belum diberi nilai. Variabel lokal tidak diinisialisasi otomatis dan isinya tidak dapat dipercaya, berbeda dari variabel global yang bernilai nol. Beri nilai awal pada saat deklarasinya.
  • Menaungi nama tanpa sadar. Deklarasi int jumlah di dalam perulangan yang sudah memiliki jumlah di luarnya mengarahkan pembaruan ke variabel yang salah. Flag -Wshadow membantu menemukan kejadian semacam ini.
  • Memakai variabel sebelum baris deklarasinya. Lingkup baru dimulai pada baris deklarasi, sehingga kode di atasnya tidak mengenal nama tersebut. Pindahkan deklarasi ke awal blok atau ke bagian atas berkas.
  • Mengira posisi deklarasi global tidak berpengaruh. Fungsi yang tertulis di atas deklarasi tidak mengenal nama tersebut, sehingga pemakaiannya ditolak saat kompilasi. Susun berkas dengan deklarasi global di bagian paling atas.

Ringkasan#

  • Lingkup blok dimulai pada baris deklarasi dan berakhir pada kurung kurawal penutup, sedangkan masa hidup variabel lokal mengikuti blok yang sama.
  • Variabel global dikenali sepanjang berkas setelah deklarasinya, bertahan sepanjang program, dan diinisialisasi nol secara otomatis.
  • Shadowing membuat nama yang dalam menyembunyikan nama yang luar untuk sementara, sah menurut standar tetapi rawan menyesatkan.

Referensi#