C Programming Language

Memahami Pointer di C

Aldimhr
18-02-2026
13 min read

Sebenarnya tentang pointer ini pernah saya tulis di tulisan saya yang lain: Memory Address dan C. Bedanya, ditulisan ini saya ingin membahas tentang pointer bahasa pemrograman C lebih detail.

Pointer

Pointer merupakan cara untuk kita mengakses value yang sama dengan alamat memory yang sama dengan variabel yang berbeda. Memang terdengar sedikit tidak berguna: “Kenapa harus punya 2 variabel yang sama untuk value yang sama juga?” makes sense, tapi kita coba lihat kegunannya nanti.

Kita bisa membuat pointer dengan bantuan asterisk (*) yang ditempatkan sebelum nama variabel dan setelah tipe data.

Berikut adalah contoh variabel pointer

#include <stdio.h>

int main(void){
	// variabel pointer
	int *p; 
	
	return 0;
}

Kita bisa memberikan variabel pointer dengan alamat memory dari variabel lain. Alamat memory dari sebuah variabel bisa menggunakan ampersand (&).

Berikut contoh kita untuk mengambil alamat memory dari sebuah variabel

#include <stdio.h>

int main(void){
	int x = 10;
	
	// akan mencetak alamat memory 
	// tempat dimana variabel x 
	// disimpan.
	// contoh dalam kasus ini
	// menampilkan: 0x7ffc580a4904
	printf("%p\n", &x); 
	
	return 0;
}

“%p” dalam printf bisa dibaca dengan pointer atau format untuk menampilkan memory address dari sebuah variabel.

Karena kita sudah tau cara mengambil memory address, selanjutnya kita bisa menambahkan memory address ke variabel pointer.

#include  <stdio.h>

int main(void){
	int x = 10;
	int *p = &x;
	
	return 0;
}

Kode diatas menunjukkan bahwa variabel pointer p dan variabel x memiliki alamat memory yang sama, sehingga ketika salah satu diubah, maka kedua value variabel tersebut akan berubah.

#include <stdio.h>

int main(void){
  int x = 10;
  int *y = &x;

  printf("%d \n", x); // 10

	// kita coba ubah variabel  
	// pointer y
  *y = 100; 

	// 100, disini variabel x 
  // juga ikut berubah
  printf("%d \n", x); 

	// sekarang kita coba
  // sebaliknya
  x = 200; 

	// jika kita coba akses
  // variabel pointer y
  // maka hasilnya 200
  printf("%d \n", *y); 
	return 0;
}

Declaring Pointer

Jika kita ingin membuat variabel yang memiliki tipe data yang sama, kita bisa declare variabel tersebut dengan dua cara.

Pertama di baris terpisah dan setiap variabel punya tipe data sendiri, meskipun sama

int a;
int b;

Kedua, kita bisa menggabungkan jadi satu baris, dengan menuliskan satu tipe data dan memisahkan antara variabel dengan koma

int a, b;

Kedua cara tersebut valid untuk mendefinisikan variabel.

Lalu, bagaimana jika terdapat dua variabel, salah satunya merupakan pointer, konsepnya sama, kita bisa memisahkan dengan koma jika memiliki tipe data yang sama, hanya saja symbol asterisk (*) menempel pada nama variabel. Contohnya

int a;
int *b;

// kita bisa tuliskan seperti ini juga
int a, *b;

Dereferencing

Derefercing merupakan cara kita untuk membaca value dari alamat yang sudah ditunjuk oleh variabel poiter, kita bisa menggunakan asterisk (*) lagi untuk membaca valuenya.

#include <stdio.h>

int main(void){
  int x = 10;
  int *y = &x;

  printf("%d\n", *y); // 10
  printf("%p\n", y);  // 0x7ffe2aa98844

	
	return 0;
}

Tanpa menggunakan asterisk, yang ditampilkan hanyalah alamat memory.

sizeof dan Pointer

// jika, kita punya variabel x yang point ke int
int *x;

printf("Sizeof int: %zu\n", sizeof(int)); // print size of int | 4
printf("Sizeof *x: %zu\n", sizeof(*x)); // print size of int | 4
printf("Sizeof x: %zu\n", sizeof(x)); // print size of int* | 8

kenapa int dan pointer int* memiliki size yang berbeda? alasan logisnya karena int hanya digunakan untuk menyimpan angka, sedangkan pointer int* menyimpan memory address, sehingga perlu tempat penyimpanan yang lebih besar dibanding int.

Tapi, kenapa 8? ini ditentukan dari arsitektur komputer itu sendiri, misalnya di komputer 64 bit, artinya alamat memory yang disimpan sebesar 64 bit atau 8 bytes. di komputer 32 bit akan memiliki size 4 bytes untuk pointer int*.

Function dan Pointer

Sama seperti variabel lain, variabel pointer juga bisa digunakan sebagai parameter atau dilempar sebagai argumen ke sebuah function.

Contohnya ketika kita memiliki function dengan nama sum_two yang akan menambahkan 2 setiap argumen yang ditambahkannya.

#include <stdio.h>

void sum_two(int *p){
  *p += 2;
}

int main(void){
  int x = 10;
  int *p = &x;

  sum_two(p);
  
  printf("%d\n", x);  // 12
  printf("%d\n", *p); // 12

  return 0;
}

Jika kita lihat dari kode diatas, kedua variabel tersebut memiliki angka yang sama yaitu 12, padahal function tersebut tidak melakukan return value, dan inilah yang dinamakan dengan Pass By Reference.

Array, Pointer dan Function

Jika kita memiliki function dengan parameter pointer, seperti ini

int my_strlen(char *s);

Maka, kita bisa mengisinya dengan pointer atau array untuk melempar argumen ke function tersebut.

int main(void){
  char *p = "Hello, world!";
  char s[] = "Hello, string!";

  my_string(p); // valid
  my_string(s); // valid

  return 0;
}

Null Pointer

Pointer dengan tipe data apapun bisa memiliki value spesial yang disebut dengan null. Artinya, pointer tersebut tidak mengarah ke alamat memory manapun.

int *p;
p = NULL;

Karena pointer tidak mengarah ke manapun, ketika kita melakukan dereferencing maka akan terjadi undefined behavior dan hasilnya akan crash.

int *p = NULL;
*p = 12;

Outputnya akan terlihat seperti ini: [1] 39166 segmentation fault (core dumped), sehingga hal seperti ini perlu dihindari.

Array dan Pointer

Terdapat persamaan antara pointer dan array, jika kita coba rumuskan, maka array dapat dituliskan sebagai persamaan berikut ini

a[b] = *(a + b);

Karena hal tersebut kita bisa melakukan pengambilan array member dengan cara pointer (*(a + b)).

Contoh

int main(void){
  int i;
  int arr[3] = {1,2,3};

	// menggunakan pointer
  for(i = 0; i < 3; i++){
    printf("Value dari arr[%d]: %d\n", i, *(arr + i));
  }
  
  // menggunakan array
  for(i = 0; i < 3; i++){
    printf("Value dari arr[%d]: %d\n", i, arr[i]);
  }
  
  return 0;
}

Dari kode diatas kita melakukan looping isi dari array variabel arr tapi kita membaca dengan cara pointer yaitu *(arr + i). Ketika kita coba untuk membaca value dari array menggunakan cara array itu sendiri arr[i] juga valid.

Struct dan Pointer

Kita bisa memberikan argumen berupa pointer struct ke sebuah function, seperti ini

struct animal {
  char *name;
  int leg_count;
};

void set_animal_name(struct animal *a, char *new_animal_name){
  // TODO
}

Di dalam function set_animal_name kita tidak bisa hanya menuliskan a.name = new_animal_name;, karena dot operator hanya berjalan jika digunakan untuk struct, bukan untuk pointer struct.

Sehingga kita harus melakukan dereference untuk mendapatkan member dari struct tersebut, kita bisa tuliskan seperti ini

struct animal {
  char *name;
  int leg_count;
};

void set_animal_name(struct animal *a, char *new_animal_name){
  // a.name = new_animal_name; // ERROR
  (*a).name = new_animal_name;
}

Kode diatas berjalan, dan tidak ada masalah, tapi yang umum digunakan untuk mengakses pointer struct adalah arrow operator

Arrow Operator

Arrow operator ditulis dengan tanda minus diikut dengan tanda lebih besar dari (). Sehingga function yang sebelumnya bisa kita tulis ulang menggunakan arrow operator sebagai berikut

#include <stdio.h>

struct animal {
  char *name;
  int leg_count;
};

void set_animal_name(struct animal *a, char *new_animal_name){
  a->name = new_animal_name;
}

int main(void){
  struct animal a1 = {.name="Kerbau", .leg_count=4};

  set_animal_name(&a1, "Sapi");

  printf("Name: %s\n", a1.name); // Name: Sapi
                                 
  return 0;
}

Operasi Matematika dan Pointer

Kita bisa melakukan operasi matematika dengan pointer, penjumlahan dan pengurangan.

Perlu diingat bahwa, ketika kita melakukan perubahan pada pointer, kita perlu memastikan bahwa yang ditunjuk oleh pointer harus valid sebelum melakukan dereferencing, jika tidak akan terjadi crash, atau undefined.

Penambahan Pointer

Penambahan pointer yang dimaksud disini masih sama dengan persamaan antara pointer dan array, dimana kita bisa menambahkan variabel pointer dengan angka, sehingga alamat yang ditunjuk menjadi berubah dan value yang dibawa akan ikut berubah juga.

Persamaannya adalah a[b] = *(a + b);. Dengan persamaan tersebut kita bisa mengakses value dari sebuah array dengan cara berikut

#include <stdio.h>

int main(){
  int arr[3] = {1,2,3};
  int *p = arr;
  
  // dengan persamaan tadi kita bisa membaca value dari array
  // dengan cara *(p+1)
  printf("value dari arr[1] menggunakan pointer: %d\n", *(p+1));

	return 0;
}

Contoh diatas menunjukkan kita bisa melakukan manipulasi value dari sebuah pointer dengan cara menambahkannya dengan angka.

Merubah Pointer

Kita bisa melakukan perubahan pada pointer itu sendiri untuk mengambil value yang berbeda, agar lebih mudah, kita coba buat skenario, dimana kita memiliki sebuah variable array dan variable pointer, dimana keduanya memiliki value yang sama.

#include <stdio.h>

int main(void){
  int arr[3] = {1,2,3};
  int *p = arr;

  return 0;
}

Pada kasus ini kita ingin membaca member dari array melalui variabel pointer, kita bisa lakukan dengan cara looping pointer p lalu kita tambahkan variabel tersebut dengan angka 1, sehingga jika dituliskan akan menjadi seperit berikut

#include <stdio.h>

int main(void){
  int arr[3] = {1,2,3};
  int *p = arr;

  while(*p != 3){
    printf("value dari pointer p: %d\n", *p);

    p++;
  }

  return 0;
}

Kita bisa melakukan pergeseran value dengan menambahkan variable pointer p dengan angka 1 (p++)

Pengurangan Pointer

Pointer bisa dikurangkan dengan pointer lainnya, dalam contoh ini kita bisa melakukan perhitungan panjang dari sebuah string dengan cara kita kurangkan dua variabel pointer.

Skenarionya adalah kita punya dua variable pointer, pertama terisi string, dan pointer kedua akan pointing ke variable pointer pertama, jika dituliskan akan seperti berikut

#include <stdio.h>

int main(void){
  char s[100] = "Hello, world!";
  char *p = s;

  return 0;
}

Idenya, kita akan looping variable pointer p, lalu ketika mencapai NULL Character dimana adalah batas akhir dari sebuah string, maka loop akan berhenti lalu kita kurangkan dengan variable array yang pertama, dengan begitu kita bisa mendapatkan selisih dari dua variable pointer tersebut.

#include <stdio.h>

int main(void){
  char s[100] = "Hello, world!";
  char *p = s;

  while(*p != '\0'){
    p++;
  }

  printf("Len dari string: %d\n", p-s);

  return 0;
}

Void Pointer

void di function merupakan keyword yang digunakan ketika function tersebut tidak membutuhkan parameter atau function tersebut tidak memiliki return value.

void sum(){} // function tidak memiliki return value
int main(void){} // function tidak memiliki parameter apapun

void di pointer sedikit berbeda, void disini diartikan sebagai penampung yang bisa digunakan untuk tipe data apapun, kita bisa mengisi pointer tersebut dengan int, char atau tipe data lainnya.

Umumnya, terdapat dua usecase untuk void pointer,

  1. Function yang melakukan operasi byte-by-byte. Contohnya adalah function memcpy() dimana function tersebut melakukan copy bytes dari satu pointer ke pointer yang lain, dan pointer tersebut bisa merupakan tipe data apapun.
  2. Function yang memanggil function lain, dan function yang dipanggil akan mengembalikan nilai. Kita tahu tipe data apa yang akan ditambahkan di function yang kita panggil, tapi function lain yang juga ikut terpanggil tidak tahu apa yang harus dikembalikan, sehingga dalam kasus ini penggunaan void akan membantu function tersebut menentukan tipe data yang akan dikembalikan, dan tipe data akan ditentukan oleh function pemanggilnya. Contohnya adalah function qsort() dan bsearch() yang menggunakan void pionter untuk kasus ini.

Case Pertama

Pertama kita coba lihat bagaimana memcpy() dibuat, jika dituliskan akan terlihat seperti ini

void *memcpy(void *s1, void *s2, size_t n);

Function tersebut akan melakukan copy sebanyak n bytes berawal dari memory address s2 ke memory address s1.

Tapi, pertanyaannya kenapa variabel s1 dan s2 merupakan void pointer? singkatnya, karena function ini dapat memindahkan data dari satu pointer ke pointer lain tanpa melihat tipe data yang digunakan.

Pertama, kita coba untuk memindahkan data pointer ke pointer lain dengan tipe data char.

#include <stdio.h>
#include <string.h>

int main(void){
  char s1[] = "Hello, world!";
  char s2[100];

  memcpy(s2, s1, 14);

  printf("string s2: %s\n", s2);

  return 0;
}

Kita bisa memindahkan data dari pointer satu ke pointer lain dengan tipe data int.

#include <stdio.h>
#include <string.h>

int main(void){
  int i1[] = {1, 2, 3};
  int i2[3];

  memcpy(i2, i1, 3 * sizeof(int));

  for(int i = 0; i < 3; i++){
    printf("Index: %d, value: %d\n", i, i2[i]);
  }

  return 0;
}

Satu function dapat digunakan untuk tipe data yang berbeda.

Selain itu, kita juga bisa coba untuk tipe data lain seperti floats atau struct dengan memcpy().

struct antelope my_antelope;
struct antelope my_clone_antelope;

// ...

memcpy(&my_clone_antelope, &my_antelope, sizeof my_antelope);

Kita bisa lihat bagaimana function memcpy() sangat dinamis terhadap tipe data dari argumen yang diberikan. Tanpa void pointer mungkin kita akan memerlukan memcpy() khusus char, int dan tipe data lainnya, dan itu sangat redundant.

Void pointer juga memiliki kekurangan atau keterbatasan, yaitu

  1. Kita tidak dapat melakukan pointer arithmetic dengan void*
  2. Kita tidak dapat melakukan dereference sebuah void*
  3. Kita tidak dapat menggunakan arrow operator untuk void*, karena arrow operator melakukan dereference.
  4. Kita tidak dapat melakuakn array notation untuk void*, karena juga melakukan dereference.

Jika kita lihat lagi, rules diatas masuk akal, karena operasi yang dilakukan diatas memerlukan sizeof dari tipe data yang akan dibaca dan dengan void*, kita tidak tau size data yang di pointing.

Jika pada rules diatas kita tidak dapat melakukan dereferencing, lalu apa yang harus dilakukan?

Seperti memcpy(), void pointer membantu kita untuk membuat generic function yang dapat menerima tipe data apapun. Jika kita lihat kembali, untuk melakukan dereference pada void pointer, kita harus convert void pionter ke tipe data yang kita butuhkan, sebelum melakukan dereference.

Berikut contoh kita melakukan convert tipe data dari void pionter lalu melakukan dereference

#include <stdio.h>

int main(void){
  char x = 'x';

  void *vp = &x;
  char *cp = vp;

  printf("void pointer: %c\n", *vp); // error: invalid use of void expression
  printf("char pointer: %c\n", *cp);

  return 0;
}

Case Kedua

Selanjutnya kita akan melihat bagaimana void pointer digunakan dalam function yang memanggil function lain (callback), dalam contoh ini kita akan coba bahas function qsort().

Function qsort(arr, n, size, comp) memerlukan 4 parameter

  1. arr: Pointer ke element pertama dari array
  2. n: banyaknya element dari array
  3. size: ukuran dari setiap element
  4. comp: function yang menentukan urutan atau bagaimana element diurutkan

qsort() akan mengurutkan block dari bytes berdasarkan hasil dari function comp() yang kita tambahkan di parameter terakhir dari function qsort().

Comparator function ini memiliki aturan

  1. function harus menerima dua argumen const void*
  2. akan return <0 jika argumen pertama diletakkan sebelum argumen kedua
  3. akan return 0 jika kedua argumen sama
  4. akan return >0 jika argumen pertama diletakkan setelah argumen kedua

Aturan pertama bilang function tersebut harus menerima dua argumen void pointer, kenapa? karena qsort() tidak tau isi array itu apa, yang qsort() tau cuma ukuran dari setiap element array, sehingga qsort() harus menerima generic type yang ditulis menggunakan void pointer. Siapa yang tau tipe datanya? kita, makanya kita perlu ubah void pointer menjadi tipe data yang kita inginkan ketika membuat comparator function.

Contoh, mengurutkan struct

#include <stdio.h>
#include <stdlib.h>

struct animal {
  char *name;
  int leg_count;
};

// comparator function
int compar(const void* a, const void *b){

  // kita akan ubah void pionter menjadi tipe data
  // yang kita inginkan
  const struct animal *animal1 = a;
  const struct animal *animal2 = b;

  // aturan 4: return >0 jika argumen pertama diletakkan setelah argumen kedua
  if(animal1->leg_count > animal2->leg_count) return 1;

  // aturan 2: return <0 jika argumen pertama diletakkan sebelum argumen kedua
  if(animal1->leg_count > animal2->leg_count) return -1;

  // aturan 3: return 0 jika keduanya sama
  return 0;
}

int main(void){
  struct animal a[4] = {
    {.name="Dog", .leg_count=4},
    {.name="Monkey", .leg_count=2},
    {.name="Antelope", .leg_count=4},
    {.name="Snake", .leg_count=0}
  };


  qsort(a, 4, sizeof(struct animal), compar);

  for (int i = 0; i < 4; i++) {
    printf("%d: %s\n", a[i].leg_count, a[i].name);
  }
  return 0;
}

https://beej.us/guide/bgc/html/split/index.html

Tags
  • C Programming Language
View All Posts