Setelah berhasil membaca satu baris penuh dari standard input, kita sering perlu memecah baris tersebut menjadi potongan-potongan yang lebih kecil. Sebagai contoh, file konfigurasi baris nama=Budi;umur=25 perlu dipecah menjadi pasangan key-value, atau path URL /api/users/123 perlu dipecah berdasarkan karakter /. Standard library C menyediakan strtok untuk pekerjaan ini. Sayangnya, strtok punya dua keanehan yang sering menjebak pemula: (1) strtok memodifikasi string input secara in-place dengan mengganti delimiter menjadi '\0'; (2) strtok menyimpan state internal (static variable) sehingga tidak aman dipakai bersamaan dari dua titik di kode atau di multi-threaded program. Pada chapter ini kita akan menelusuri kedua karakter ini, lalu melihat strtok_r sebagai versi reentrant yang lebih aman.
strtok Dasar #
strtok(str, delim) memecah str menjadi token berdasarkan himpunan karakter delimiter di delim. Pada pemanggilan pertama, kita lewatkan string asli sebagai argumen pertama. Pada pemanggilan berikutnya, kita lewatkan NULL untuk melanjutkan dari posisi terakhir.
#include <stdio.h>
#include <string.h>
int main(void) {
char kalimat[] = "apel,jeruk,mangga,pepaya";
const char *pemisah = ",";
/* strtok memecah string menjadi token berdasarkan delimiter.
Catatan: strtok MEMODIFIKASI string asli (mengganti delimiter dengan '\0'). */
char *token = strtok(kalimat, pemisah);
while (token != NULL) {
printf("Token: %s\n", token);
token = strtok(NULL, pemisah); /* NULL = lanjut dari posisi terakhir */
}
printf("\nIsi kalimat setelah strtok:\n");
for (size_t i = 0; i < 24; i++) {
if (kalimat[i] == '\0') {
printf("\\0");
} else {
printf("%c", kalimat[i]);
}
}
printf("\n");
return 0;
}Output yang Dihasilkan:
Token: apel
Token: jeruk
Token: mangga
Token: pepaya
Isi kalimat setelah strtok:
apel\0jeruk\0mangga\0pepayaMari kita telaah baris-baris kode di atas:
- Baris 6 (
char kalimat[] = "apel,jeruk,mangga,pepaya";): string disimpan sebagai arraychardi stack (dapat dimutasi). Kita sengaja menyebutkan ukuran loopi < 24untuk mencetak isi array setelahstrtokselesai. - Baris 11 (
char *token = strtok(kalimat, pemisah);): pemanggilan pertamastrtokmelewatkan string asli.strtokmencari karakter non-delimiter pertama ('a'di indeks 0), menandai posisi awal token, lalu mencari delimiter pertama (,di indeks 4), dan mengganti delimiter tersebut dengan'\0'. Fungsi mengembalikan pointer ke awal token"apel". - Baris 12-15 (loop
while): setiap iterasi mencetak token saat ini, lalu memanggilstrtok(NULL, pemisah)untuk mendapatkan token berikutnya.NULLdi argumen pertama memberi sinyal: lanjutkan dari posisi internal yang disimpan dari pemanggilan sebelumnya. - Baris 17-25 (loop mencetak isi array): setelah
strtokselesai, setiap koma,di array asli sudah diganti dengan'\0'. Loop mencetak isi byte per byte:apel\0jeruk\0mangga\0pepaya(di mana\0adalah null terminator pengganti koma). Inilah bukti visual bahwastrtokmemodifikasi string in-place.
Banyak Delimiter Sekaligus #
strtok menerima string delimiter multi-karakter. Setiap karakter di string delimiter diperlakukan sebagai delimiter individual. Sebagai contoh, strtok(s, " ,;|") akan memecah string berdasarkan salah satu dari spasi, koma, titik koma, atau pipe.
#include <stdio.h>
#include <string.h>
/* Beberapa delimiter sekaligus */
int main(void) {
char data[] = "Budi;Siti,Andi|Joko, Maya";
/* strtok menerima himpunan delimiter multi-karakter */
char *token = strtok(data, " ,;|");
int nomor = 1;
while (token != NULL) {
printf("%d. %s\n", nomor++, token);
token = strtok(NULL, " ,;|");
}
return 0;
}Output yang Dihasilkan:
1. Budi
2. Siti
3. Andi
4. Joko
5. MayaPada output di atas, string "Budi;Siti,Andi|Joko, Maya" dipecah menjadi 5 token: Budi, Siti, Andi, Joko, Maya. Delimiter yang dipakai adalah ;, ,, |, dan spasi. Perhatikan bahwa delimiter berturut-turut (seperti , diikuti spasi pada Joko, Maya) tidak menghasilkan token kosong—strtok melewati delimiter berturut-turut seolah-olah satu delimiter.
strtok_r: Versi Reentrant #
Karena strtok menyimpan state internal di variabel static, fungsi ini tidak aman untuk pemakaian di dua kasus: (1) nested tokenization (memecah token di dalam loop strtok yang sedang berjalan), dan (2) program multi-threaded. Versi strtok_r (reentrant) meminta pemanggil menyediakan pointer context yang dipakai menyimpan state.
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <string.h>
/* strtok_r: versi reentrant (thread-safe) dengan context pointer */
int main(void) {
char teks[] = "nama=Budi;umur=25;kota=Bandung";
char *saveptr = NULL;
char *pasangan = strtok_r(teks, ";", &saveptr);
while (pasangan != NULL) {
/* Pecah setiap pasangan key=value */
char *simpan2 = NULL;
char *kunci = strtok_r(pasangan, "=", &simpan2);
char *nilai = strtok_r(NULL, "=", &simpan2);
printf("Kunci: %-6s Nilai: %s\n",
kunci ? kunci : "(kosong)",
nilai ? nilai : "(kosong)");
pasangan = strtok_r(NULL, ";", &saveptr);
}
return 0;
}Output yang Dihasilkan:
Kunci: nama Nilai: Budi
Kunci: umur Nilai: 25
Kunci: kota Nilai: BandungMari kita telaah baris-baris kode di atas:
- Baris 1 (
#define _POSIX_C_SOURCE 200809L):strtok_radalah fungsi POSIX, bukan standar C murni. Macro ini memberi sinyal ke header bahwa kita ingin POSIX. Tanpa macro ini, compiler tidak akan mendeklarasikanstrtok_rdan kita akan mendapat implicit declaration error. - Baris 9 (
char *saveptr = NULL;): variabel context yang akan dipakaistrtok_runtuk menyimpan posisi internal. Pemanggil yang bertanggung jawab atas lifetime variabel ini—tidak ada variabel static global di dalamstrtok_r. - Baris 10 (
strtok_r(teks, ";", &saveptr)): pemanggilan pertama melewatkan string asli, delimiter";", dan alamat context pointer. Return value adalah token pertama"nama=Budi". - Baris 13 (
char *simpan2 = NULL;): di dalam loop, kita memakai context pointer keduasimpan2untuk pemecahan level kedua. Inilah keunggulanstrtok_r: kita dapat melakukan tokenization bertingkat tanpa konflik karena setiap level punya context sendiri. - Baris 14-15 (
strtok_r(pasangan, "=", &simpan2)danstrtok_r(NULL, "=", &simpan2)): memecahpasangan(misalnya"nama=Budi") menjadi kunci"nama"dan nilai"Budi". - Baris 19 (
pasangan = strtok_r(NULL, ";", &saveptr);): melanjutkan pemecahan level pertama.NULLmemberi sinyal: lanjutkan dari posisi yang disimpan disaveptr.
Ringkasan Konsep #
strtok memecah string menjadi token berdasarkan himpunan karakter delimiter. Fungsi ini memodifikasi string input in-place dengan mengganti delimiter menjadi '\0', sehingga string asli tidak dapat dipakai lagi sebagai string utuh setelah strtok dipanggil. Pemanggilan pertama melewatkan string asli; pemanggilan berikutnya melewatkan NULL untuk melanjutkan dari posisi internal. Karena strtok menyimpan state di variabel static, fungsi ini tidak aman untuk pemakaian nested atau multi-threaded. Untuk kasus tersebut, gunakan strtok_r (POSIX) yang menerima context pointer eksplisit dari pemanggil. Selalu pastikan string input adalah array char yang dapat dimutasi—bukan literal di memori read-only. Pada chapter berikutnya kita akan membahas konversi string ke angka dengan atoi, strtol, dan strtod.