Menyalin string dari satu buffer ke buffer lain adalah operasi yang sangat sering diperlukan—mulai dari menyimpan hasil input, mengirim teks antar fungsi, hingga mengisi field di struct. Standard library C menyediakan dua fungsi utama untuk ini: strcpy dan strncpy. Fungsi strcpy menyalin seluruh string sumber ke buffer tujuan termasuk null terminator, tetapi tidak memiliki parameter ukuran sehingga rentan buffer overflow bila buffer tujuan lebih kecil dari sumber. Fungsi strncpy menerima parameter batas n sehingga lebih aman, tetapi punya jebakan tersendiri: bila sumber lebih panjang dari n, strncpy tidak menambahkan null terminator ke buffer tujuan. Pada chapter ini kita akan menelusuri kedua fungsi ini dan membangun pola aman yang bisa dipakai dalam kode produksi.
strcpy: Salin Utuh tanpa Batas #
strcpy(dst, src) menyalin seluruh isi src (termasuk null terminator) ke dst. Fungsi ini mengembalikan pointer ke dst. Pemakaian yang aman mengasumsikan kita tahu bahwa dst cukup besar untuk menampung seluruh src.
#include <stdio.h>
#include <string.h>
int main(void) {
char src[] = "Halo Budi";
char dst[20]; /* buffer tujuan, harus cukup besar */
/* strcpy: salin seluruh src ke dst termasuk null terminator */
strcpy(dst, src);
printf("src = %s\n", src);
printf("dst = %s\n", dst);
/* Buktikan dst dan src independen */
dst[0] = 'J';
printf("Setelah dst[0]='J':\n");
printf(" src = %s\n", src);
printf(" dst = %s\n", dst);
/* strcpy mengembalikan pointer ke dst */
char *r = strcpy(dst, "Reset");
printf("Hasil return strcpy = %s\n", r);
return 0;
}Output yang Dihasilkan:
src = Halo Budi
dst = Halo Budi
Setelah dst[0]='J':
src = Halo Budi
dst = Jalo Budi
Hasil return strcpy = ResetMari kita telaah baris-baris kode di atas:
- Baris 7 (
char dst[20];): kita menyediakan buffer tujuan sebesar 20 byte. Buffer harus lebih besar dari panjang sumber (9 byte untuk"Halo Budi"+ null terminator) untuk menghindari overflow. - Baris 10 (
strcpy(dst, src);):strcpymenyalin byte demi byte darisrckedsthingga null terminator disrcikut tersalin. Setelah pemanggilan,dstberisi"Halo Budi\0"(10 byte terisi, 10 byte sisanya tidak dimodifikasi). - Baris 14 (
dst[0] = 'J';): kita memutasi byte pertamadst. Karenadstadalah array independen di stack, mutasi tidak memengaruhisrc. - Baris 21 (
char *r = strcpy(dst, "Reset");):strcpymengembalikan pointer kedst. Kita menyimpan return value kerdan mencetaknya—rmenunjuk ke byte pertamadst, yang sekarang berisi"Reset".
strncpy: Salin dengan Batas, tapi Hati-hati Null Terminator #
strncpy(dst, src, n) menyalin maksimal n byte dari src ke dst. Perilaku null terminator bergantung pada panjang sumber relatif terhadap n:
- Bila
srclebih pendek darinbyte (termasuk null terminator),strncpymenyalin null terminator dan mengisi byte sisanya dengan'\0'. - Bila
srclebih panjang atau sama dengannbyte,strncpymenyalinnbyte tanpa menambahkan null terminator. Buffer tujuan menjadi tidak null-terminated.
Diagram berikut membandingkan dua skenario strncpy pada buffer tujuan 5 byte: kasus sumber pendek (yang menambahkan null terminator) dan kasus sumber panjang (yang tidak menambahkan null terminator):
Pada diagram di atas, strncpy(dst, src, n) berperilaku berbeda tergantung panjang sumber. Pada skenario 1, src hanya berisi 4 byte termasuk null terminator; strncpy menyalin seluruh 4 byte dan mengisi byte kelima dengan '\0'—buffer tujuan menjadi null-terminated dengan benar. Pada skenario 2, src berisi 11 byte sedangkan batas n hanya 5; strncpy menyalin 5 byte pertama ('A'..'E') tanpa menambahkan null terminator—buffer tujuan tidak null-terminated dan pemakaian fungsi seperti printf("%s", dst) akan terus membaca memori melewati batas. Pola aman setelah strncpy adalah selalu menambahkan null terminator secara eksplisit: dst[sizeof dst - 1] = '\0';.
#include <stdio.h>
#include <string.h>
int main(void) {
char src[] = "ABCDEFGHIJ"; /* panjang 10 */
char dst[5];
/* strncpy: copy maksimal n byte; jika src >= n, null terminator TIDAK ditambah */
strncpy(dst, src, sizeof dst);
/* dst berisi 5 byte pertama tapi tidak ada '\0' */
printf("Isi dst byte per byte: ");
for (size_t i = 0; i < sizeof dst; i++) {
printf("[%zu]=%c(%d) ", i, dst[i] ? dst[i] : '0', dst[i]);
}
printf("\n");
/* JANGAN pakai printf %s langsung pada dst: UB (tanpa null terminator) */
/* Tambahkan null terminator manual di posisi terakhir agar aman */
dst[sizeof dst - 1] = '\0';
printf("Setelah null terminator manual: [%s]\n", dst);
/* Jika dst lebih besar dari src: sisanya di-pad dengan '\0' */
char besar[10];
strncpy(besar, "AB", sizeof besar);
printf("besar bytes: ");
for (size_t i = 0; i < sizeof besar; i++) {
printf("%d ", besar[i]);
}
printf("\n");
return 0;
}Output yang Dihasilkan:
Isi dst byte per byte: [0]=A(65) [1]=B(66) [2]=C(67) [3]=D(68) [4]=E(69)
Setelah null terminator manual: [ABCD]
besar bytes: 65 66 0 0 0 0 0 0 0 0 Pada contoh di atas, src berisi "ABCDEFGHIJ" (10 karakter, tanpa null terminator di sini karena tidak dihitung). Kita menyalin ke dst[5]. strncpy menyalin 5 byte pertama (A, B, C, D, E) tanpa menambahkan null terminator. Bila kita mencoba mencetak dst dengan printf("%s", dst), printf akan terus membaca memori melewati dst[4] sampai kebetulan menemui byte 0—UB. Solusinya: kita paksa null terminator di posisi dst[sizeof dst - 1], menghasilkan string valid "ABCD" (4 karakter, byte ke-5 di-overwrite jadi '\0'). Untuk kasus buffer lebih besar dari sumber (besar[10] menampung "AB"), strncpy menyalin A, B, '\0', lalu mengisi tujuh byte sisanya dengan '\0'—output menunjukkan 65 66 0 0 0 0 0 0 0 0.
Pola Aman: Pembungkus strncpy #
Untuk menghindari kesalahan null terminator, kita dapat membangun fungsi pembungkus salin_aman yang menggabungkan strncpy dengan paksaan null terminator di byte terakhir. Pola ini cocok dipakai di seluruh kodebase untuk memastikan string hasil salinan selalu valid.
#include <stdio.h>
#include <string.h>
/* Pola aman: strncpy lalu paksa null terminator di byte terakhir */
void salin_aman(char *dst, size_t ukuran, const char *src) {
if (ukuran == 0) return;
strncpy(dst, src, ukuran);
dst[ukuran - 1] = '\0'; /* jamin string selalu diakhiri null */
}
int main(void) {
char sumber[] = "Teks yang sangat panjang sekali";
char tujuan[10];
salin_aman(tujuan, sizeof tujuan, sumber);
printf("Hasil salin (terpotong): %s\n", tujuan);
printf("Panjang tujuan: %zu\n", strlen(tujuan));
return 0;
}Output yang Dihasilkan:
Hasil salin (terpotong): Teks yang
Panjang tujuan: 9Mari kita telaah baris-baris kode di atas:
- Baris 5 (
void salin_aman(char *dst, size_t ukuran, const char *src)): fungsi menerima tiga parameter: buffer tujuan, ukuran buffer, dan string sumber. Fungsi ini void (tidak mengembalikan nilai) karena pemanggil sudah tahu lokasidst. - Baris 6 (
if (ukuran == 0) return;): penjaga khusus bilaukuran= 0 untuk menghindari aksesdst[-1]yang UB. - Baris 7 (
strncpy(dst, src, ukuran);): menyalin maksimalukuranbyte darisrckedst. Bilasrclebih pendek dariukuran, sisanya otomatis di-pad'\0'. Bilasrclebih panjang, hanyaukuranbyte yang disalin tanpa null terminator. - Baris 8 (
dst[ukuran - 1] = '\0';): kita paksa byte terakhir buffer menjadi'\0'. Langkah ini menjamin string hasil selalu null-terminated, walau ada pemotongan. Trade-off: bilasrcsebenarnya muat, byte terakhir yang seharusnya berisi karakter juga tertimpa'\0'—namun kerugian ini lebih kecil dibanding bahaya UB. - Baris 15 (
salin_aman(tujuan, sizeof tujuan, sumber);): memanggil pembungkus dengan buffer 10 byte dan sumber 31 karakter. Hasilnya:tujuanberisi 9 karakter"Teks yang"+ null terminator di byte ke-10.
Ringkasan Konsep #
strcpy dan strncpy adalah dua fungsi dasar untuk menyalin string di C. strcpy(dst, src) menyalin seluruh src (termasuk null terminator) ke dst tanpa parameter ukuran—berbahaya bila buffer tujuan lebih kecil dari sumber. strncpy(dst, src, n) membatasi salinan ke n byte, tetapi punya dua jebakan: (1) bila src lebih panjang dari n, null terminator tidak ditambahkan sehingga buffer tujuan tidak null-terminated; (2) bila src lebih pendek dari n, byte-byte sisa di-pad dengan '\0' (overhead yang tidak perlu). Pola aman yang direkomendasikan: panggil strncpy lalu paksa dst[sizeof dst - 1] = '\0' untuk menjamin null terminator selalu ada. Pada chapter berikutnya kita akan membahas strcat dan strncat—fungsi untuk menggabungkan string dengan semantik serupa.