C Programming Language

Memahami Pointer di C

Aldimhr
18-02-2026
15 min read

Memahami Pointer di C

Sebenarnya tentang pointer ini pernah saya tulis di Memory Address dan C. Bedanya, ditulisan ini saya ingin bahas pointer bahasa pemrograman C lebih detail.

Memori

Komputer bisa menyimpan semua data di dalam memori: int , float, char atau lainnya. Data di dalam komputer akan disimpan dalam memori dalam bentuk block-block, dan setiap block hanya bisa menyimpan 1 byte atau 8 bit.

Block Memory

Setiap block memory memiliki alamat yang digunakan untuk menandai tempat dimana data disimpan. Alamat memory biasanya dituliskan dalam bentuk hexadecimal, kenapa? agar lebih ringkas, karena 1 digit hex sama dengan 4 bit, dan karena satu block dapat menyimpan data sebanyak 8 bit, maka kita hanya perlu menuliskan 2 digit hex untuk setiap blocknya, lebih ringkas dan mudah dibaca dibanding menulis 8 bit.

Misalnya kita memiliki data int dengan value 10, di memori akan terlihat seperti ini

int x = 10;

Block Memory Variabel x

Gambar diatas, jika kita lihat lebih teliti, variabel int x memakan 4 block memori, ini berkaitan dengan bagaimana tipe data disimpan didalam memory, dalam hal ini int membutuhkan 4 bytes atau 4 block memory.

Lalu, bagaimana jika kita ingin mencetak alamat memory dari sebuah variabel? kita bisa menggunakan ampersand (&) untuk melihat alamat memori dari sebuah variabel

#include <stdio.h>

int main(void){
  int x = 10;

  // mencetak alamat memory
  printf("%p\n", &x); // output: 0x7ffe197f9524

  return 0;
}

Kode diatas kita mencetak alamat dari sebuah variabel x, jika kita coba gambarkan didalam memory akan tampak seperti gambar dibawah ini.

Memory Address dari Variabel x

Pointer

Pointer adalah variabel yang digunakan untuk menyimpan alamat memory.

Pointer bisa dibuat dengan menambahan 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;
}

Oke kita sudah tau cara membuat variabel pointer. Perlu diingat bahwa yang disimpan dari variabel pointer adalah alamat memory, sehingga kita tidak bisa menambahakan value secara langsung seperti ini

int *p = 10; // Error

Karena variabel pointer hanya bisa menyimpan alamat memory dari variabel lain, maka kita bisa menambahkan ampersand (&) variabel lain ke variabel pointer.

Berikut contoh kita untuk mengambil alamat memory dari sebuah variabel

#include  <stdio.h>

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

Kode diatas menunjukkan bahwa variabel pointer p akan melakukan copy alamat memory dari variabel x sehingga variabel pointer dan variabel x diatas memiliki alamat memory yang sama

#include  <stdio.h>

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

  printf("Alamat x: %p\n", &x); // Alamat x: 0x7ffcb0b8af7c
  printf("Alamat p: %p\n", p);  // Alamat p: 0x7ffcb0b8af7c
  
  return 0;
}

Karena kedua variabel tersebut memiliki alamat memory yang sama, maka akan tampak keduanya saling berhubungan, 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

Terdapat beberapa cara untuk membuat variabel pointer dan jika kita ingin membuat variabel yang memiliki tipe data yang sama, kita bisa mendeklarasikan 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

Variabel pointer hanya menyimpan alamat memori, sehingga kita tidak dapat langsung menggunakan variabel pointer untuk membaca value dari alamat memori tersebut.

#include <stdio.h>

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

  printf("%p\n", p); // menampilkan alamat memori
  printf("%d\n", p); // menampilkan angka acak (bukan value dari variabel x, yaitu 10)

  return 0;
}

Dereferencing merupakan cara untuk membaca value dari alamat yang sudah ditunjuk oleh variabel pointer, kita bisa menggunakan asterisk (*) 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 atau hanya angka acak dan bukan value sebenarnya.

Ukuran Pointer

Function sizeof() digunakan untuk melihat ukuran dari sebuah variabel atau tipe data. Kita coba lihat kode dibawah ini ketika mencoba print ukuran dari tipe data, variabel pointer dan variabel itu sendiri (tanpa 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 yang kita gunakan, 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, yang artinya function tersebut akan merubah value aslinya dari argumen yang dikirimkan.

String dan Pointer

Pertama kita akan lihat bagaimana string disimpan di dalam memori. Lets say, kita punya satu variabel s yang merupakan array dengan tipe data char, lalu memiliki value Hi!.

#include <stdio.h>

int main(void){
  char s[] = "Hi!";

  printf("%s\n", s); // output: Hi!

  return 0;
}

Data string variabel s jika kita lihat di memori akan tampak seperti ini

Gambaran Memori untuk Variabel Array

Wait, kenapa ada \0? itu adalah NUL terminator, yang merupakan tanda dari akhir sebuah string.

Lalu, kita memiliki variabel pointer untuk menampung alamat memori dari variabel string s, pertanyaanya, apakah variabel pointer tersebut menyimpan semua alamat memori dari string tersebut? ternyata tidak, yang disimpan dalam variabel pointer adalah alamat memori dari string pertama saja.

Kita coba buktikan dengan menampilkan alamat memory yang disimpan dari pointer p dan alamat memori dari karater pertama variabel string s.

#include <stdio.h>

int main(void){
  char s[] = "Hi!";
  char *p = s;

  printf("%p\n", &s[0]); // output: 0x7ffdca3b6ba4 
  printf("%p\n", &s[1]); // output: 0x7ffebf9cf3b5 
  printf("%p\n", p); // output: 0x7ffdca3b6ba4 

  return 0;
}

Anyway, kita bisa membuat string dengan pointer

char s[] = "Hi!";
char *p = "Hi!";

Bedanya, string yang dibuat dengan pointer bersifat read-only, jadi tidak bisa mengubah karakter dari string yang dibuat dengan pointer.

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.

Jika kita melihat persamaan diatas, artinya ketika kita memiliki function dengan parameter pointer, maka kita bisa melempar argumen berupa pointer atau array seperti ini

int my_string(char *s);

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

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

  return 0;
}

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;
}

Pointer Arithmetic

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); dan 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 NUL Terminator 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;
}

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.

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/pointers.html#pointers

https://cs50.harvard.edu/x/weeks/4/

Tags
  • C Programming Language
View All Posts