stdlib.h Lanjutan

qsort dan bsearch dengan pointer fungsi, keluarga strtol, dan membaca variabel lingkungan.

Daftar isi

Header stdlib.h menyimpan fungsi pengurutan, pencarian, konversi teks ke angka, dan akses variabel lingkungan, sehingga kebutuhan-kebutuhan itu tidak perlu ditulis sendiri. Topik ini membahas qsort dan bsearch yang keduanya bekerja melalui pointer fungsi pembanding, keluarga strtol sebagai konversi string yang dapat memeriksa error, serta getenv untuk membaca konfigurasi dari variabel lingkungan. Konsep void * dan pointer fungsi yang diperkenalkan pada modul Pointer dipakai kembali di sini dalam bentuk pemakaiannya yang utuh.

qsort dan pointer fungsi pembanding#

Fungsi qsort mengurutkan array bertipe apa pun dengan menerima empat argumen, yaitu alamat awal array, banyak elemen, ukuran satu elemen, dan pointer ke fungsi pembanding. Fungsi pembanding menerima dua parameter bertipe const void * dan mengembalikan nilai negatif apabila elemen pertama harus berada sebelum elemen kedua, nol apabila keduanya setara, dan positif apabila elemen pertama harus berada sesudahnya.

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

int banding_int(const void *a, const void *b) {
    int x = *(const int *)a;
    int y = *(const int *)b;
    return (x > y) - (x < y);
}

int main(void) {
    int nilai[] = {75, 90, 60, 85, 70};
    int banyak = sizeof nilai / sizeof nilai[0];

    qsort(nilai, banyak, sizeof nilai[0], banding_int);

    for (int i = 0; i < banyak; i++) {
        printf("%d ", nilai[i]);
    }
    printf("\n");
    return 0;
}
text Keluaran
60 70 75 85 90

Idiom kembalian (x > y) - (x < y) menghasilkan 1, 0, atau -1 sesuai urutan, dan dipilih karena selisih x - y dapat meluap apabila kedua nilainya berjauhan ekstrem. Mengurutkan menurun tidak memerlukan algoritma baru, cukup membalik tanda kembalian pembandingnya menjadi (x < y) - (x > y). Satu catatan perilaku perlu diingat: qsort tidak menjamin urutan elemen yang dianggap setara tetap pada posisi asalnya, sehingga algoritma ini bukan pilihan untuk pengurutan yang menuntut kestabilan.

Ukuran elemen dikirim secara terpisah karena qsort tidak mengetahui tipenya, dan argumen itulah yang menentukan jarak antar-elemen ketika pertukaran dilakukan. Fungsi pembanding dipanggil berulang kali selama pengurutan berlangsung, sehingga tubuhnya perlu tetap ringkas dan bebas dari efek samping. Urutan pemanggilannya tidak dijanjikan apa pun, sehingga pembanding tidak boleh bergantung pada kondisi di luar kedua parameternya.

bsearch: pencarian biner pada data terurut#

Fungsi bsearch melakukan pencarian biner (binary search) atas array yang sudah terurut dan memakai pembanding yang sama dengan qsort. Argumennya terdiri atas alamat kunci, alamat array, banyak elemen, ukuran elemen, dan fungsi pembanding. Kembaliannya berupa pointer ke elemen yang cocok, atau NULL apabila tidak ditemukan.

c cari-biner.c
#include <stdio.h>
#include <stdlib.h>

int banding_int(const void *a, const void *b) {
    int x = *(const int *)a;
    int y = *(const int *)b;
    return (x > y) - (x < y);
}

int main(void) {
    int nilai[] = {75, 90, 60, 85, 70};
    int banyak = sizeof nilai / sizeof nilai[0];

    qsort(nilai, banyak, sizeof nilai[0], banding_int);

    int kunci = 85;
    int *hasil = bsearch(&kunci, nilai, banyak, sizeof nilai[0], banding_int);
    if (hasil != NULL) {
        printf("Nilai %d ditemukan pada indeks %d\n", kunci, (int) (hasil - nilai));
    } else {
        printf("Nilai %d tidak ditemukan\n", kunci);
    }

    kunci = 65;
    hasil = bsearch(&kunci, nilai, banyak, sizeof nilai[0], banding_int);
    if (hasil == NULL) {
        printf("Nilai %d tidak ditemukan\n", kunci);
    }
    return 0;
}
text Keluaran
Nilai 85 ditemukan pada indeks 3
Nilai 65 tidak ditemukan

Kunci pencarian dikirim melalui alamatnya, yaitu &kunci, karena parameter bsearch bertipe const void *. Indeks elemen yang ditemukan diperoleh dari selisih pointer, yaitu hasil - nilai. Selisih semacam ini sah dilakukan pada dua pointer yang menunjuk array yang sama.

Prasyarat terurut inilah yang menjadikan pencarian biner cepat, karena separuh data dibuang pada setiap perbandingan. Untuk seribu elemen, pencarian biner hanya memerlukan sekitar sepuluh perbandingan, sedangkan penelusuran lurus dapat mencapai seribu kali. Harga yang dibayarkan adalah keharusan mengurutkan lebih dahulu, dan biayanya jauh lebih besar daripada satu pencarian. Karena itu, pasangan fungsi ini menguntungkan apabila pencarian dilakukan berkali-kali atas data yang sama.

Keluarga strtol: konversi yang aman#

Fungsi atoi yang dipakai pada alat statistik memang singkat, tetapi tidak melaporkan error apa pun: masukan yang tidak berisi angka diperlakukan sebagai 0, dan nilai yang melampaui batas int berupa perilaku tak terdefinisi. Keluarga strtol menawarkan konversi yang dapat diperiksa. Fungsi ini menerima teks, alamat pointer penanda akhir, dan basis bilangan, lalu mengembalikan nilai long. Pointer penanda akhir (end pointer) diisi alamat karakter pertama yang tidak termasuk angka, sehingga sisa teksnya tetap dapat diproses.

c konversi-aman.c
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    const char *teks = "  250 gram";
    char *akhir;

    errno = 0;
    long nilai = strtol(teks, &akhir, 10);
    if (akhir == teks) {
        printf("Tidak ada angka yang terbaca\n");
    } else {
        printf("Angka terbaca: %ld\n", nilai);
        printf("Sisa teks    : %s\n", akhir);
    }

    printf("Basis 16 : %ld\n", strtol("1A", NULL, 16));
    printf("Basis 2  : %ld\n", strtol("1011", NULL, 2));
    printf("Basis 0  : %ld\n", strtol("0x2F", NULL, 0));

    errno = 0;
    long besar = strtol("99999999999999999999", NULL, 10);
    if (errno == ERANGE) {
        printf("Meluap, dijepit ke %ld\n", besar);
    }
    return 0;
}
text Keluaran
Angka terbaca: 250
Sisa teks    :  gram
Basis 16 : 26
Basis 2  : 11
Basis 0  : 47
Meluap, dijepit ke 9223372036854775807

Pemanggilan pertama memperlihatkan tiga perilaku sekaligus, yaitu spasi di awal teks dilewati, pembacaan berhenti pada karakter yang bukan angka, dan akhir menunjuk sisa teksnya. Basis 16 dan basis 2 membaca heksadesimal serta biner, sedangkan basis 0 mendeteksi sendiri awalan 0x atau 0. Kegagalan meluap ditandai errno yang bernilai ERANGE, dan nilai kembaliannya dijepit ke LONG_MAX atau LONG_MIN. Perbandingan kedua cara konversi dirangkum pada tabel berikut.

Aspekatoistrtol
Deteksi errortidak adamelalui errno == ERANGE
Masukan tanpa angkamenghasilkan 0 tanpa tandaakhir sama dengan teks awal
Pilihan basisselalu 102 sampai 36, atau 0 untuk deteksi otomatis
Tipe kembaliintlong

Keluarga ini mencakup strtoul untuk bilangan tak bertanda serta strtoll dan strtoull untuk bilangan lebar long long, dan seluruhnya mengikuti pola parameter yang sama. Untuk masukan yang bersumber dari pengguna atau berkas, konversi dengan keluarga strtol menjadi pilihan bawaan, sedangkan atoi cukup untuk teks yang formatnya sudah dijamin.

Membaca variabel lingkungan dengan getenv#

Variabel lingkungan (environment variable) adalah pasangan nama dan nilai yang diwariskan shell kepada program yang dijalankannya, misalnya PATH dan HOME. Fungsi getenv menerima nama variabelnya dan mengembalikan pointer ke string nilainya, atau NULL apabila variabel itu tidak diatur. Konfigurasi semacam ini mengubah perilaku program tanpa mengompilasi ulang apa pun.

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

int main(void) {
    const char *modus = getenv("MODUS");
    if (modus == NULL) {
        printf("MODUS tidak diatur, program memakai mode bawaan\n");
    } else {
        printf("MODUS berisi %s\n", modus);
    }

    const char *tujuan = getenv("TUJUAN_LOG");
    printf("TUJUAN_LOG = %s\n", (tujuan != NULL) ? tujuan : "(tidak diatur)");
    return 0;
}
text Keluaran
MODUS tidak diatur, program memakai mode bawaan
TUJUAN_LOG = (tidak diatur)

Program di atas dijalankan tanpa variabel tambahan, sehingga kedua variabelnya jatuh ke jalur NULL. Nilai variabel lingkungan dapat diatur hanya untuk satu pemanggilan program dengan menaruh penugasan di depan perintahnya.

bash
# mengompilasi program pembaca lingkungan
gcc lingkungan.c -o lingkungan

# menjalankannya dengan dua variabel lingkungan
MODUS=uji TUJUAN_LOG=/tmp/log-aplikasi ./lingkungan
text Keluaran
MODUS berisi uji
TUJUAN_LOG = /tmp/log-aplikasi

String yang dikembalikan getenv dimiliki oleh lingkungan, bukan oleh program, sehingga string itu tidak boleh diubah isinya maupun dibebaskan dengan free. Apabila nilainya perlu dipertahankan melintasi pemanggilan yang dapat mengubah lingkungan, salin string itu ke penyimpanan sendiri dengan strdup atau penugasan ke array.

Variabel lingkungan lazim dipakai untuk hal yang berbeda antarlingkungan, misalnya jalur berkas log, alamat server, atau pilihan mode uji. Nilainya selalu berupa string, sehingga nilai bertipe bilangan perlu dikonversi, dan konversinya dapat memakai keluarga strtol yang baru dibahas. Kombinasi keduanya membentuk pola konfigurasi yang ringan, yaitu program membaca nama yang disepakati, memeriksa NULL, lalu jatuh ke nilai bawaan bila tidak diatur.

Kesalahan umum#

  • Memakai atoi pada masukan yang tidak terkendali. Teks tanpa angka senyap menjadi 0 dan nilai yang meluap berupa perilaku tak terdefinisi, sehingga sumber error-nya tidak terlihat. Gunakan strtol beserta pemeriksaan errno untuk masukan dari pengguna atau berkas.
  • Menulis pembanding qsort dengan selisih langsung. Kembalian x - y meluap pada pasangan nilai yang berjauhan ekstrem, dan pembanding yang tidak konsisten membuat urutan hasil menjadi tidak pasti. Pakai idiom (x > y) - (x < y) dan pastikan pembandingnya selalu memberi jawaban yang sama untuk pasangan yang sama.
  • Memanggil bsearch pada data yang belum terurut. Pencarian biner hanya benar atas data terurut, sehingga hasilnya tidak terdefinisi apabila prasyarat itu dilanggar. Urutkan dengan qsort menggunakan pembanding yang sama sebelum mencari.
  • Mengubah string hasil getenv. Pointer yang dikembalikan menunjuk memori milik lingkungan, dan penugasan ke string itu berupa perilaku tak terdefinisi yang tidak selalu terdeteksi. Salin nilainya ke array atau memori sendiri apabila isinya perlu dimodifikasi, dan uji program dengan lingkungan yang sengaja diatur agar kedua jalurnya ikut terverifikasi.

Ringkasan#

  • qsort mengurutkan array bertipe apa pun melalui pointer fungsi pembanding, dan bsearch mencari pada data terurut dengan pembanding yang sama.
  • Keluarga strtol mengonversi teks ke angka dengan deteksi error melalui errno, penanda sisa teks, dan pilihan basis, sedangkan atoi tidak melaporkan apa pun.
  • getenv membaca variabel lingkungan sebagai string milik lingkungan yang tidak boleh diubah atau dibebaskan.
  • Konversi masukan yang bersumber dari luar kode sebaiknya selalu melalui keluarga strtol, bukan atoi.

Referensi#