Kombinasi const dengan pointer menghasilkan tiga konfigurasi berbeda yang sering membingungkan pemula: pointer ke data konstan, pointer konstan ke data biasa, dan pointer konstan ke data konstan. Posisi const relatif terhadap * menentukan apa yang tidak boleh diubah—apakah data yang ditunjuk, pointer itu sendiri, atau keduanya. Memahami ketiganya penting karena memengaruhi contract antara pemanggil dan fungsi: apakah fungsi boleh memodifikasi data, apakah pointer akan dipindah, dan seterusnya. Pada chapter ini kita akan membedakan ketiganya secara konkret dan menerapkannya pada API fungsi yang membaca atau menulis array.
Tiga Konfigurasi const dan Pointer #
Tata cara membaca tipe const di C adalah dari kanan ke kiri:
const int *pci(atauint const *pci): “pci adalah pointer ke int yang konstan”. Data tidak boleh diubah lewat pointer, tetapi pointer itu sendiri bisa dipindah ke alamat lain.int *const cpi: “cpi adalah pointer konstan ke int”. Pointer tidak bisa dipindah ke alamat lain, tetapi data di alamat tersebut bisa diubah.const int *const cpci: “cpci adalah pointer konstan ke int yang konstan”. Keduanya tidak bisa diubah—pointer tetap menunjuk ke alamat yang sama, dan data di alamat tersebut tidak bisa dimodifikasi lewat pointer ini.
Mekanisme kerjanya: compiler memeriksa setiap penugasan dan dereference berdasarkan qualifier const. Jika pointer adalah const int *, compiler menolak penugasan *pci = ... dengan error “assignment of read-only location”. Jika pointer adalah int *const, compiler menolak cpi = ... (reassign pointer) dengan error “assignment of read-only variable”. Kombinasi keduanya, const int *const, menolak keduanya.
#include <stdio.h>
int main(void) {
int x = 10, y = 20;
/* 1) Pointer ke const int: data tidak bisa diubah lewat pointer */
const int *pci = &x;
/* *pci = 99; // ERROR: assignment of read-only location */
pci = &y; /* OK: pointer itu sendiri bisa diubah */
printf("const int *pci: alamat %p, *pci = %d\n", (void*)pci, *pci);
/* 2) const pointer ke int: pointer tidak bisa diubah, data bisa */
int *const cpi = &x;
*cpi = 99; /* OK: data bisa diubah */
/* cpi = &y; // ERROR: assignment of read-only variable 'cpi' */
printf("int *const cpi: alamat %p, *cpi = %d\n", (void*)cpi, *cpi);
/* 3) const pointer ke const int: keduanya tidak bisa diubah */
const int *const cpci = &y;
/* *cpci = 50; // ERROR */
/* cpci = &x; // ERROR */
printf("const int *const cpci: alamat %p, *cpci = %d\n",
(void*)cpci, *cpci);
return 0;
}Output yang Dihasilkan:
const int *pci: alamat 0x7ffe01f919b0, *pci = 20
int *const cpi: alamat 0x7ffe01f919b4, *cpi = 99
const int *const cpci: alamat 0x7ffe01f919b0, *cpci = 20Pada kasus pertama, pci bisa dipindah dari &x ke &y (alamat akhir 0x...9b0 adalah alamat y), tetapi *pci = ... akan ditolak compiler. Pada kasus kedua, cpi tidak bisa dipindah—alamatnya tetap 0x...9b4 (alamat x)—tetapi *cpi = 99 berhasil mengubah nilai x dari 10 menjadi 99. Kasus ketiga, cpci, mengunci keduanya. Perhatikan juga bahwa pci = &y mengubah pci sehingga kini menunjuk ke y bernilai 20—sebelumnya pci menunjuk ke x.
Mari kita telaah baris-baris kode di atas:
- Baris 5 (
const int *pci = &x;): pointer keconst int.*pcibersifat read-only;pcibisa di-assign ulang. - Baris 8 (
pci = &y;): pemindahan pointer. Hal ini legal karena hanya*pci(data) yang konstan, bukanpci(pointer) sendiri. - Baris 11 (
int *const cpi = &x;): pointer konstan keint.cpitidak bisa di-assign ulang;*cpibisa ditulis. - Baris 12 (
*cpi = 99;): modifikasi data melalui pointer konstan. Legal karena data tidak bersifatconst. - Baris 16 (
const int *const cpci = &y;): gabungan keduanya. Pointer konstan ke data konstan—paling restriktif.
Kontrak API dengan const #
Penggunaan const yang tepat menyatakan kontrak antara fungsi dan pemanggil: apakah fungsi akan memodifikasi data, dan apakah fungsi akan memindah pointer. Konvensi yang baik:
- Fungsi yang hanya membaca array sebaiknya menerima
const T *(pointer ke data konstan). Pemanggil tahu datanya tidak akan diubah. - Fungsi yang menulis array sebaiknya menerima
T *(pointer ke data non-konstan). Pemanggil sadar datanya bisa berubah. - Fungsi yang menyimpan pointer untuk dipakai nanti (misalnya dalam struktur data) sebaiknya menerima
T *constatau lebih spesifik—tidak perlu memindah pointer, cukup simpan alamat.
#include <stdio.h>
/* Fungsi read-only: const int * (pointer tidak bisa ubah data) */
int jumlahkan(const int *arr, int n) {
int total = 0;
for (int i = 0; i < n; i++) {
/* arr[i] = 0; // ERROR: read-only */
total += arr[i];
}
return total;
}
/* Fungsi pengisi: int * (pointer bisa ubah data) */
void isi_nol(int *arr, int n) {
for (int i = 0; i < n; i++) {
arr[i] = 0;
}
}
int main(void) {
int data[5] = {10, 20, 30, 40, 50};
printf("Jumlah: %d\n", jumlahkan(data, 5));
printf("Sebelum isi_nol: ");
for (int i = 0; i < 5; i++) printf("%d ", data[i]);
printf("\n");
isi_nol(data, 5);
printf("Setelah isi_nol : ");
for (int i = 0; i < 5; i++) printf("%d ", data[i]);
printf("\n");
return 0;
}Output yang Dihasilkan:
Jumlah: 150
Sebelum isi_nol: 10 20 30 40 50
Setelah isi_nol : 0 0 0 0 0 Fungsi jumlahkan menerima const int *—pemanggil yakin array tidak akan dimodifikasi. Mencoba arr[i] = 0 di dalam fungsi ini akan ditolak compiler. Fungsi isi_nol sebaliknya menerima int * (tanpa const pada data), menyatakan bahwa fungsi ini akan memodifikasi array. Perbedaan tipe ini menjadi dokumentasi yang dapat diverifikasi compiler—pemanggil tidak perlu membaca isi fungsi untuk tahu apakah datanya aman.