Array struct dan Fungsi struct

Mengoper dan mengembalikan struct, serta pertimbangan mengoper pointer struct sebagai gantinya.

Daftar isi

Sebuah struct dapat dioper ke fungsi sebagai parameter dan dikembalikan darinya sebagai nilai kembali, sama seperti tipe dasar. Mekanisme pengoperannya perlu dipahami dengan tepat karena struct dioper sebagai salinan utuh, sehingga ukurannya ikut menentukan efisiensi program. Topik ini membahas cara mengoper struct ke fungsi, mengembalikan struct dari fungsi, mengumpulkan struct dalam array, serta pertimbangan mengoper alamat struct sebagai gantinya apabila salinannya terlalu besar.

Mengoper struct ke fungsi#

Parameter bertipe struct diperlakukan sebagaimana parameter tipe dasar, yaitu fungsi menerima salinan nilai dari pemanggilnya. Seluruh anggota disalin, termasuk isi array di dalamnya, dan perubahan pada parameter hanya terjadi pada salinan itu. Salinan tersebut berlaku seperti variabel lokal biasa dan hilang ketika fungsi berakhir. Perilaku ini mengikuti mekanisme pass by value yang diterapkan C pada semua argumen fungsi.

Program berikut memanggil fungsi geser dengan sebuah struct Titik sebagai argumennya.

c oper-struct.c
#include <stdio.h>

struct Titik {
    int x;
    int y;
};

void geser(struct Titik t) {
    t.x = t.x + 10;
    t.y = t.y + 5;
    printf("Di dalam fungsi : (%d, %d)\n", t.x, t.y);
}

int main(void) {
    struct Titik p = {2, 3};

    geser(p);
    printf("Di main         : (%d, %d)\n", p.x, p.y);

    return 0;
}
text Keluaran
Di dalam fungsi : (12, 8)
Di main         : (2, 3)

Pemanggilan geser(p) mengoper salinan p, sehingga perubahan t.x dan t.y di dalam fungsi tidak terlihat oleh main. Keluaran menampilkan titik (12, 8) di dalam fungsi, sedangkan titik di main tetap (2, 3) sesudah pemanggilan. Apabila hasil pergeseran perlu disimpan pemanggil, struct hasilnya dikembalikan dari fungsi, atau alamat struct dioper sebagai gantinya.

Mengembalikan struct dari fungsi#

Fungsi boleh memiliki tipe kembali berupa struct. Nilai yang dikembalikan juga berupa salinan, sehingga fungsi dapat merakit struct baru secara lokal lalu menyerahkannya kepada pemanggil. Pola ini berguna ketika satu hasil terdiri atas beberapa nilai sekaligus, misalnya titik tengah dua titik yang menggabungkan dua koordinat dalam satu nilai kembali.

c kembalikan-struct.c
#include <stdio.h>

struct Titik {
    int x;
    int y;
};

struct Titik titik_tengah(struct Titik a, struct Titik b) {
    struct Titik tengah;
    tengah.x = (a.x + b.x) / 2;
    tengah.y = (a.y + b.y) / 2;
    return tengah;
}

int main(void) {
    struct Titik awal   = {2, 4};
    struct Titik akhir  = {8, 12};
    struct Titik tengah = titik_tengah(awal, akhir);

    printf("Titik tengah: (%d, %d)\n", tengah.x, tengah.y);

    return 0;
}
text Keluaran
Titik tengah: (5, 8)

Fungsi titik_tengah menerima dua salinan struct, menghitung koordinat tengahnya, lalu mengembalikan struct tengah sebagai satu nilai utuh. Pemanggil menyimpan hasilnya ke variabel sendiri dan membaca kedua koordinatnya dengan operator titik. Dengan pola ini, dua nilai hasil perhitungan terkirim dalam satu pernyataan return tanpa variabel global maupun parameter tambahan.

Mengembalikan struct juga aman dilakukan dari sudut pandang masa hidup memori. Variabel tengah di dalam fungsi memang hilang setelah fungsi berakhir, tetapi nilainya sudah disalin keluar pada pernyataan return, sehingga pemanggil menerima salinannya yang utuh. Hal ini berbeda dari mengembalikan alamat variabel lokal, yang menyisakan pointer menunjuk memori yang tidak berlaku lagi.

Array struct dan biaya salinannya#

Deklarasi struct Titik jalur[4]; membentuk array yang setiap elemennya adalah struct utuh, dan tiap elemen diakses dengan indeks lalu operator titik, misalnya jalur[2].x. Elemen array struct saling ditugaskan sebagaimana variabel struct biasa, sehingga jalur[1] = jalur[0]; menyalin seluruh anggota dari satu elemen ke elemen lain. Banyaknya elemen dapat dihitung dengan sizeof(jalur) / sizeof(jalur[0]), pola yang sama dengan array tipe dasar. Array struct sering menjadi parameter fungsi, dan di situlah biaya salinan mulai terasa, karena mengoper satu elemennya berarti menyalin satu struct penuh.

Program berikut mengukur ukuran sebuah struct yang lebih besar dan membandingkannya dengan ukuran pointer ke struct itu.

c ukuran-salinan.c
#include <stdio.h>

struct Barang {
    char nama[24];
    int jumlah;
    double harga;
};

int main(void) {
    printf("sizeof(struct Barang)   = %zu byte\n", sizeof(struct Barang));
    printf("sizeof(struct Barang *) = %zu byte\n", sizeof(struct Barang *));
    printf("sizeof(char[24])        = %zu byte\n", sizeof(char[24]));
    printf("sizeof(int)             = %zu byte\n", sizeof(int));
    printf("sizeof(double)          = %zu byte\n", sizeof(double));

    return 0;
}
text Keluaran
sizeof(struct Barang)   = 40 byte
sizeof(struct Barang *) = 8 byte
sizeof(char[24])        = 24 byte
sizeof(int)             = 4 byte
sizeof(double)          = 8 byte

Struct Barang berukuran 40 byte, sedangkan pointer yang menyimpan alamatnya hanya 8 byte pada platform yang sama. Mengoper alamat menjadi pilihan yang jauh lebih murah apabila fungsi dipanggil berkali-kali atau struct-nya jauh lebih besar. Sebagai gambaran, array berisi 1000 struct Barang menampung sekitar 40.000 byte, dan setiap pemanggilan fungsi yang menyalin satu elemennya memindahkan seluruh 40 byte itu. Pointer juga memungkinkan fungsi mengubah struct milik pemanggil karena fungsi menerima alamat variabel aslinya dan bukan salinannya.

Aturan praktisnya dapat dirangkum sebagai berikut.

  • Gunakan parameter struct biasa apabila struct-nya kecil, sering kali hanya dua atau tiga anggota, dan fungsi tidak perlu mengubahnya.
  • Gunakan parameter const struct Tipe * apabila struct-nya besar dan fungsi hanya membacanya.
  • Gunakan parameter struct Tipe * tanpa const apabila fungsi memang perlu mengubah struct milik pemanggil.
  • Kembalikan struct biasa apabila hasilnya dirakit di dalam fungsi, dan salinan itu menjadi cara pengirimannya kepada pemanggil.
AspekMengoper structMengoper pointer struct
Yang diterima fungsiSalinan seluruh anggotaAlamat variabel pemanggil
Biaya pengoperanSebesar ukuran structDelapan byte pada x86-64
Perubahan terhadap pemanggilTidak memengaruhi pemanggilMemengaruhi pemanggil
Pemakaian yang cocokStruct kecil yang hasilnya independenStruct besar atau yang perlu diubah
c Dua bentuk parameter fungsi untuk struct
// tidak-dijalankan
double total(struct Barang b);         /* menerima salinan 40 byte */
double total(const struct Barang *b);  /* menerima alamat, 8 byte */

Kata kunci const pada bentuk kedua menegaskan bahwa fungsi hanya membaca struct, sehingga kompiler menolak perubahan yang tidak disengaja. Teknik mengakses anggota melalui pointer, termasuk operator panah ->, dibahas pada topik Pointer struct dan Operator ->.

Kesalahan umum#

Ekspektasi yang keliru paling sering muncul ketika fungsi dimaksudkan mengubah struct pemanggil, tetapi parameternya ditulis sebagai struct biasa. Fungsi hanya mengubah salinannya, pemanggil tidak melihat perubahan apa pun, dan tidak ada pesan error yang menunjuk penyebabnya. Ada dua perbaikan, yaitu mengembalikan struct hasil perubahan lalu menugaskannya kembali ke variabel pemanggil, atau mengoper alamat struct dan mengubahnya melalui pointer.

Ringkasan#

  • Struct dioper ke fungsi dan dikembalikan darinya sebagai salinan utuh, sesuai mekanisme pass by value.
  • Perubahan parameter struct di dalam fungsi tidak memengaruhi variabel pemanggilnya.
  • Tipe kembali berupa struct mengirimkan beberapa nilai hasil perhitungan dalam satu pernyataan return.
  • Untuk struct besar atau yang perlu diubah oleh fungsi, mengoper alamatnya lebih murah daripada menyalin seluruh anggotanya.

Referensi#