Tipe akses

Tipe akses pool-specific dan general beserta aturan scope pada manajemen memori.

Daftar isi

Tipe akses (access type) adalah tipe yang nilainya berupa penunjuk ke objek lain, dan objek yang ditunjuk dapat dialokasikan secara dinamis maupun dideklarasikan sebagai objek biasa. Topik ini membahas tipe akses pool-specific yang mengelola penyimpanan sendiri untuk objek yang dialokasikan dengan new, tipe akses general yang dapat menunjuk objek biasa yang diberi penanda aliased, serta aturan lingkup (scope) yang mencegah penunjuk menunjuk objek yang masa hidupnya telah berakhir. Contoh yang digunakan adalah penunjuk titik koordinat dan penunjuk nilai bacaan.

Tipe akses dan dereferensi#

Deklarasi tipe akses menuliskan kata access diikuti tipe objek yang ditunjuk, misalnya type Penunjuk is access Titik;. Variabel bertipe akses yang tidak diberi nilai awal otomatis bernilai null, yaitu penunjuk yang tidak menunjuk objek mana pun. Nilai objek yang ditunjuk dibaca atau ditulis melalui dereferensi (dereference) dengan akhiran .all, misalnya P.all untuk objek yang ditunjuk P. Notasi titik boleh digunakan langsung pada penunjuk, sehingga P.X berarti P.all.X, dan kebiasaan ini membuat kode dengan tipe akses terbaca seperti kode record biasa. Tipe akses pada Ada tidak menyediakan aritmetika alamat sebagaimana bahasa C, sehingga nilai penunjuk hanya berubah melalui penugasan antar penunjuk atau melalui atribut 'Access.

ada Contoh: deklarasi tipe akses dan dereferensi
-- tidak-dijalankan
type Penunjuk is access Titik;

P : Penunjuk := new Titik'(X => 1.0, Y => 2.0);
Put (P.X);   -- sama dengan Put (P.all.X);

Alokasi dengan new menyimpan objek baru di dalam pool, yaitu kumpulan penyimpanan milik tipe akses tersebut. Nilai awal agregat dapat disertakan pada alokasi, misalnya new Titik'(1.0, 2.0), dan apabila tidak disertakan, seluruh bidang mengambil nilai bawaan tipe Titik. Setiap tipe akses memiliki pool miliknya sendiri, sehingga pembebasan memori dilakukan melalui tipe akses yang sama dengan tipe yang digunakan pada saat alokasi.

Tipe akses pool-specific dan pembebasan memori#

Tipe akses pool-specific dideklarasikan dengan bentuk access Titik sebagaimana contoh di atas, dan pembebasan memorinya dilakukan melalui prosedur generik Ada.Unchecked_Deallocation. Prosedur tersebut wajib diinstansiasi dengan pasangan tipe objek dan tipe aksesnya, dan instansiasi ini berlaku sebagai satu-satunya cara yang sah untuk membebaskan memori alokasi. Ukuran pool ditentukan implementasi, dan permintaan alokasi yang tidak terpenuhi membangkitkan Storage_Error, sehingga kegagalan memori tetap muncul sebagai exception dan bukan perilaku tak terdefinisi. Program berikut memperagakan siklus alokasi, pengubahan nilai melalui dereferensi, pembebasan, dan alokasi ulang.

ada penunjuk_titik.adb
with Ada.Text_IO;             use Ada.Text_IO;
with Ada.Unchecked_Deallocation;

procedure Penunjuk_Titik is

   type Titik is record
      X : Float := 0.0;
      Y : Float := 0.0;
   end record;

   -- Tipe akses pool-specific: objek yang ditunjuk dialokasikan di pool.
   type Penunjuk is access Titik;

   -- Instansiasi generik pembebasan memori untuk tipe akses tersebut.
   procedure Bebas is new Ada.Unchecked_Deallocation
     (Object => Titik, Name => Penunjuk);

   package Real_IO is new Ada.Text_IO.Float_IO (Float);
   use Real_IO;

   P : Penunjuk;

   procedure Cetak (Label : String; T : Titik) is
   begin
      Put (Label & ": (");
      Put (T.X, Fore => 0, Aft => 1, Exp => 0);
      Put (", ");
      Put (T.Y, Fore => 0, Aft => 1, Exp => 0);
      Put_Line (")");
   end Cetak;

begin
   -- Alokasi dengan new beserta nilai awal agregat.
   P := new Titik'(1.5, 2.5);
   Cetak ("Setelah alokasi", P.all);

   -- Penulisan melalui dereferensi.
   P.all := (X => 4.0, Y => 6.0);
   Cetak ("Setelah penugasan", P.all);

   -- Pembebasan memori secara eksplisit.
   Bebas (P);
   Put_Line ("Setelah pembebasan, P = null: " & Boolean'Image (P = null));

   -- Alokasi ulang untuk objek yang lain.
   P := new Titik;
   Cetak ("Alokasi ulang", P.all);
   Bebas (P);
end Penunjuk_Titik;
bash
# kompilasi program
gnatmake -q penunjuk_titik.adb
# jalankan program
./penunjuk_titik
text Keluaran
Setelah alokasi: (1.5, 2.5)
Setelah penugasan: (4.0, 6.0)
Setelah pembebasan, P = null: TRUE
Alokasi ulang: (0.0, 0.0)

Keluaran menunjukkan tiga kejadian yang penting dicermati. Pertama, Bebas (P) membebaskan memori objek dan sekaligus mengisi P dengan null, sehingga pemeriksaan P = null menghasilkan TRUE. Kedua, alokasi new Titik tanpa agregat menghasilkan objek dengan nilai bawaan (0.0, 0.0), karena nilai bawaan bidang pada deklarasi Titik digunakan oleh alokasi. Ketiga, pembebasan ditujukan kepada instansiasi yang menggunakan tipe akses yang sama dengan alokasinya, sehingga pasangan tipe pada new dan Bebas selalu sepadan.

Tipe akses general#

Tipe akses general dideklarasikan dengan access all agar dapat menunjuk objek yang tidak dialokasikan new, dan objek yang ditunjuk wajib diberi penanda aliased pada deklarasinya. Alamat objek diambil dengan atribut 'Access, dan nilai yang ditunjuk tetap dibaca melalui .all. Bentuk access constant hanya dapat membaca nilai yang ditunjuk, sehingga cocok untuk penunjuk yang tidak boleh mengubah objeknya.

ada akses_umum.adb
with Ada.Text_IO; use Ada.Text_IO;

procedure Akses_Umum is

   -- Tipe akses general: dapat menunjuk objek yang tidak dialokasikan new.
   type Penunjuk is access all Integer;

   Bacaan : aliased Integer := 25;
   P      : Penunjuk := Bacaan'Access;

begin
   Put_Line ("Nilai awal melalui P:" & Integer'Image (P.all));
   P.all := 40;
   Put_Line ("Nilai variabel Bacaan:" & Integer'Image (Bacaan));
end Akses_Umum;
bash
# kompilasi program
gnatmake -q akses_umum.adb
# jalankan program
./akses_umum
text Keluaran
Nilai awal melalui P: 25
Nilai variabel Bacaan: 40

Keluaran menegaskan bahwa P tidak menyimpan salinan nilai, melainkan alamat objek Bacaan. Penugasan P.all := 40 mengubah objek yang sama, sehingga pembacaan langsung terhadap Bacaan menghasilkan 40. Perilaku ini berbeda dari penugasan antar record yang menyalin nilai, karena tipe akses selalu merujuk pada satu objek yang sama selama penunjuknya tidak diisi ulang.

Atribut 'Access pada tipe akses general diperiksa kecocokan tipenya pada waktu kompilasi, sehingga alamat yang disimpan selalu bertipe yang tepat. Apabila pemeriksaan keterjangkauan terlalu membatasi pengambilan alamat, GNAT menyediakan atribut 'Unchecked_Access, dan penggunaannya merupakan keputusan yang disengaja karena pemeriksaan tersebut dilewati.

Aturan scope dan pemeriksaan akses#

Aturan keterjangkauan (accessibility rule) melarang penunjuk menyimpan alamat objek yang masa hidupnya lebih pendek daripada masa hidup tipe aksesnya. Tanpa aturan ini, penunjuk dapat bertahan lebih lama daripada objek yang ditunjuk dan menjadi penunjuk liar (dangling pointer). Berkas berikut sengaja mengambil 'Access dari objek yang dideklarasikan di dalam blok lebih dalam, sedangkan tipe aksesnya berada di luar.

ada uji_scope_salah.adb
--  Berkas ini sengaja salah: alamat objek diambil dari scope lebih dalam.
procedure Uji_Scope_Salah is

   type Penunjuk is access all Integer;

   P : Penunjuk;

begin
   declare
      Dalam : aliased Integer := 5;
   begin
      P := Dalam'Access;
   end;
end Uji_Scope_Salah;
bash
# kompilasi berkas yang sengaja salah
gnatmake -q uji_scope_salah.adb
text Keluaran
uji_scope_salah.adb:12:12: error: non-local pointer cannot point to local object
gnatmake: "uji_scope_salah.adb" compilation error

Pesan error menyatakan penunjuk non-lokal tidak boleh menunjuk objek lokal, dan penolakan terjadi pada waktu kompilasi karena tingkat keterjangkauan kedua deklarasi sudah diketahui secara statis. Masa hidup Dalam berakhir setiap kali blok dalamnya selesai, sedangkan P tetap dapat digunakan setelahnya, sehingga bentuk kode ini memang harus ditolak. Perbaikannya, pindahkan objek yang ditunjuk ke lingkup yang setidak-tidaknya sama dengan lingkup tipe aksesnya.

Pelanggaran yang hanya diketahui saat berjalan adalah dereferensi terhadap null. Program berikut menunjukkan bahwa pemeriksa akses aktif tanpa flag tambahan.

ada uji_null.adb
with Ada.Text_IO; use Ada.Text_IO;

procedure Uji_Null is

   type Penunjuk is access Integer;

   P : Penunjuk := null;

begin
   Put_Line ("Sebelum dereferensi");
   Put_Line ("Nilai:" & Integer'Image (P.all));
end Uji_Null;
bash
# kompilasi program
gnatmake -q uji_null.adb
# jalankan program
./uji_null
text Keluaran
uji_null.adb:11:40: warning: null value not allowed here [enabled by default]
uji_null.adb:11:40: warning: Constraint_Error will be raised at run time [enabled by default]
Sebelum dereferensi

raised CONSTRAINT_ERROR : uji_null.adb:11 access check failed

GNAT memberi peringatan pada waktu kompilasi karena nilai null pada P diketahui secara statis, lalu pemeriksa akses membangkitkan Constraint_Error dengan pesan access check failed pada saat P.all diakses. Baris Sebelum dereferensi tetap tercetak, sedangkan baris pembacaan nilai tidak pernah tercapai. Apabila kemungkinan null memang bagian dari rancangan, periksa P /= null terlebih dahulu, atau terima objek melalui parameter bertipe biasa pada subprogram yang tidak memerlukan penunjuk.

Kesalahan umum#

  • Membiarkan objek yang dialokasikan dengan new tidak pernah dibebaskan. Penyimpanan pada pool tetap terpakai sampai program berhenti, dan pola ini menimbulkan kebocoran memori (memory leak). Perbaikannya, pasangkan alokasi dan pembebasan pada satu lingkup yang jelas, atau kelola objek melalui tipe privat yang menangani pembebasannya sendiri.
  • Mengambil 'Access dari objek tanpa penanda aliased. Kompilator menolaknya karena alamat objek biasa tidak dijamin dapat dirujuk dengan aman. Perbaikannya, beri penanda aliased pada objek yang memang perlu ditunjuk oleh tipe akses general.
  • Menganggap pembacaan melalui penunjuk null aman karena tidak mengubah apa pun. Pemeriksa akses membangkitkan Constraint_Error untuk pembacaan maupun penulisan. Perbaikannya, periksa penunjuk terhadap null apabila keadaan kosong memang mungkin terjadi.
  • Menginstansiasi Ada.Unchecked_Deallocation dengan tipe akses yang berbeda dari tipe alokasinya. Pembebasan melalui instansiasi yang salah mengacaukan pasangan alokasi dan pembebasan pada pool yang sama. Perbaikannya, gunakan instansiasi dengan pasangan tipe objek dan tipe akses yang persis sama dengan yang digunakan saat alokasi.

Ringkasan#

  • Tipe akses pool-specific mengelola pool sendiri, dialokasi dengan new, dan dibebaskan melalui instansiasi Ada.Unchecked_Deallocation yang mengisi penunjuk dengan null.
  • Tipe akses general dengan access all menunjuk objek aliased biasa melalui atribut 'Access, dan access constant membatasinya pada pembacaan.
  • Aturan keterjangkauan menolak alamat objek berumur pendek pada waktu kompilasi, sedangkan dereferensi null ditangkap pemeriksa akses dengan Constraint_Error.

Referensi#