Saat array diteruskan ke fungsi, C tidak menyalin seluruh elemen—sangat tidak efisien bila array berukuran besar. Sebagai gantinya, compiler menyederhanakan tipe parameter int arr[] menjadi int *arr, sebuah pointer ke elemen pertama. Proses ini disebut array-to-pointer decay. Konsekuensinya signifikan: di dalam fungsi, sizeof(arr) mengembalikan ukuran pointer (8 byte pada sistem 64-bit), bukan ukuran array asli. Pada chapter ini kita akan menelusuri mekanisme decay, implikasinya terhadap sizeof, dan pola aman untuk melewatkan array ke fungsi.

Mekanisme Array-to-Pointer Decay #

Ketika kita menulis parameter int arr[] pada deklarasi fungsi, compiler mengubahnya menjadi int *arr. Kedua bentuk ini ekuivalen—pilih salah satu sesuai selera. Akibatnya, fungsi tidak lagi memiliki informasi ukuran array; ia hanya menerima alamat elemen pertama. Inilah mengapa setiap fungsi yang menerima array juga biasanya menerima parameter tambahan int n untuk jumlah elemen.

Dampak praktis decay:

  1. Tidak ada informasi ukuran — sizeof(arr) di dalam fungsi adalah sizeof(int *), biasanya 8 byte pada sistem 64-bit.
  2. Modifikasi elemen mempengaruhi array asli — karena fungsi menerima alamat elemen asli (bukan salinan), perubahan pada arr[i] di dalam fungsi langsung terlihat di pemanggil.
  3. Pointer arithmetic berlaku — arr + i di dalam fungsi menghitung alamat elemen ke-i dengan benar.

Demonstrasi Decay dengan sizeof #

Contoh berikut secara eksplisit membandingkan sizeof array di main dan di dalam fungsi yang menerima array sebagai parameter.

c
#include <stdio.h>

/* Parameter arr[] meluruh (decay) menjadi int *arr */
void cetak_array(int arr[], int n) {
    printf("Di dalam fungsi: sizeof(arr) = %zu\n", sizeof(arr));
    for (int i = 0; i < n; i++) {
        printf("  arr[%d] = %d\n", i, arr[i]);
    }
}

/* Versi eksplisit sebagai pointer - perilaku identik */
void kali_dua(int *arr, int n) {
    for (int i = 0; i < n; i++) {
        arr[i] = arr[i] * 2;
    }
}

int main(void) {
    int data[5] = {10, 20, 30, 40, 50};

    printf("Di dalam main  : sizeof(data) = %zu\n", sizeof(data));
    cetak_array(data, 5);

    kali_dua(data, 5);
    printf("Setelah kali_dua:\n");
    for (int i = 0; i < 5; i++) {
        printf("  data[%d] = %d\n", i, data[i]);
    }

    return 0;
}

Output yang Dihasilkan:

text
Di dalam main  : sizeof(data) = 20
Di dalam fungsi: sizeof(arr) = 8
  arr[0] = 10
  arr[1] = 20
  arr[2] = 30
  arr[3] = 40
  arr[4] = 50
Setelah kali_dua:
  data[0] = 20
  data[1] = 40
  data[2] = 60
  data[3] = 80
  data[4] = 100

Mari kita telaah baris-baris kode di atas:

  1. Baris 4 (void cetak_array(int arr[], int n)): parameter arr[] sebenarnya adalah int *arr setelah decay. Parameter n wajib karena fungsi tidak bisa menentukan panjang sendiri.
  2. Baris 5 (printf("Di dalam fungsi: sizeof(arr) = %zu\n", sizeof(arr));): sizeof(arr) di sini mengembalikan ukuran pointer int * (8 byte pada 64-bit), bukan ukuran array asli (20 byte). Compiler memberi peringatan -Wsizeof-array-argument untuk mengingatkan kita.
  3. Baris 11 (void kali_dua(int *arr, int n)): bentuk parameter int *arr secara eksplisit. Perilakunya identik dengan int arr[]—decay terjadi sama.
  4. Baris 12-14 (arr[i] = arr[i] * 2;): memodifikasi elemen melalui pointer. Karena arr menunjuk ke elemen asli di array data milik main, perubahan langsung terlihat di pemanggil.
  5. Baris 18 (int data[5] = {10, 20, 30, 40, 50};): array data berukuran 5 elemen × 4 byte = 20 byte.
  6. Baris 20 (printf("Di dalam main : sizeof(data) = %zu\n", sizeof(data));): di main, data masih berupa array (belum decay), sehingga sizeof(data) = 20 byte. Bandingkan dengan sizeof(arr) = 8 di dalam fungsi.
  7. Baris 21 (cetak_array(data, 5);): pemanggilan dengan array data dan ukuran 5. Hanya alamat elemen pertama yang diteruskan; tidak ada salinan elemen.
  8. Baris 23 (kali_dua(data, 5);): setelah pemanggilan ini, setiap elemen data menjadi dua kali lipat. Modifikasi langsung terlihat tanpa perlu return value—ini ciri khas pass-by-pointer.

Parameter Array 2D dan Pointer Eksplisit #

Array 2D yang diteruskan ke fungsi juga meluruh, tetapi hanya dimensi pertama yang menjadi pointer. Dimensi kedua harus ditentukan secara eksplisit. Alternatif modern (C99) memakai parameter VLA int m[][kolom] dengan kolom sebagai parameter pertama.

c
#include <stdio.h>

/* Mengubah parameter dari array menjadi pointer eksplisit */
void skala(int *arr, int n, int faktor) {
    for (int i = 0; i < n; i++) {
        arr[i] *= faktor;
    }
}

/* Menerima array 2D: parameter kolom wajib ditentukan */
void cetak_2d(int baris, int kolom, int m[][kolom]) {
    for (int i = 0; i < baris; i++) {
        for (int j = 0; j < kolom; j++) {
            printf("%d ", m[i][j]);
        }
        printf("\n");
    }
}

int main(void) {
    int data[4] = {1, 2, 3, 4};

    skala(data, 4, 10);
    for (int i = 0; i < 4; i++) {
        printf("%d ", data[i]);
    }
    printf("\n");

    int grid[2][3] = {{1, 2, 3}, {4, 5, 6}};
    cetak_2d(2, 3, grid);

    return 0;
}

Output yang Dihasilkan:

text
10 20 30 40
1 2 3
4 5 6

Pada contoh di atas, fungsi skala menerima pointer eksplisit int *arr. Pola arr[i] *= faktor identik dengan arr[i] = arr[i] * faktor. Untuk array 2D, parameter int m[][kolom] menggunakan variable-length array parameter (C99). Compiler perlu tahu jumlah kolom untuk menghitung offset baris—tanpa informasi kolom, ekspresi m[i][j] tidak bisa dihitung alamatnya.