Saat ini saya sedang mengerjakan perpustakaan yang ditulis dalam C. Banyak fungsi perpustakaan ini mengharapkan string sebagai char*
atau const char*
dalam argumen mereka. Saya mulai dengan fungsi-fungsi itu selalu mengharapkan panjang string size_t
sehingga null-termination tidak diperlukan. Namun, saat menulis tes, ini mengakibatkan sering digunakan strlen()
, seperti:
const char* string = "Ugh, strlen is tedious";
libFunction(string, strlen(string));
Memercayai pengguna untuk mengirimkan string yang diakhiri dengan benar akan menyebabkan kode menjadi kurang aman, tetapi lebih ringkas dan (menurut saya):
libFunction("I hope there's a null-terminator there!");
Jadi, apa praktik yang masuk akal di sini? Membuat API lebih rumit untuk digunakan, tetapi memaksa pengguna untuk memikirkan input mereka, atau mendokumentasikan persyaratan untuk string yang diakhiri dengan nol dan mempercayai penelepon?
CreateFile
mengambilLPTCSTR lpFileName
parameter sebagai input. Panjang string tidak diharapkan dari penelepon. Bahkan, penggunaan string yang diakhiri dengan NUL begitu mendarah daging sehingga dokumentasi bahkan tidak menyebutkan bahwa nama file harus diakhiri dengan NUL (tapi tentu saja harus).LPSTR
tipe mengatakan bahwa string dapat diakhiri dengan NUL, dan jika tidak , itu akan ditunjukkan dalam spesifikasi yang terkait. Jadi kecuali jika secara khusus ditunjukkan sebaliknya, string seperti itu di Win32 diharapkan akan dihentikan NUL.StringCbCat
misalnya, hanya tujuan yang memiliki buffer maksimum, yang masuk akal. The sumber masih merupakan NUL-dihentikan string C biasa. Mungkin Anda bisa meningkatkan jawaban Anda dengan mengklarifikasi perbedaan antara parameter input dan parameter output . Parameter output harus selalu memiliki panjang buffer maksimum; parameter input biasanya diakhiri NUL (ada pengecualian, tapi jarang dalam pengalaman saya).Dalam C, idiomnya adalah bahwa string karakter diakhiri NUL, jadi masuk akal untuk mematuhi praktik umum - sebenarnya relatif tidak mungkin bahwa pengguna perpustakaan akan memiliki string yang diakhiri non-NUL (karena ini membutuhkan kerja ekstra untuk mencetak menggunakan printf dan gunakan dalam konteks lain). Menggunakan string jenis apa pun tidak wajar dan mungkin relatif jarang.
Selain itu, dalam keadaan ini, pengujian Anda terlihat sedikit aneh bagi saya, karena berfungsi dengan benar (menggunakan strlen), Anda mengasumsikan string yang diakhiri NUL. Anda harus menguji kasus string non-NUL jika Anda bermaksud perpustakaan Anda untuk bekerja dengannya.
sumber
Argumen "keamanan" Anda tidak benar-benar berlaku. Jika Anda tidak memercayai pengguna untuk memberikan Anda string yang diakhiri dengan nol saat itu yang Anda dokumentasikan (dan apa itu "norma" untuk plain C), Anda tidak bisa mempercayai panjang yang mereka berikan kepada Anda (yang akan mereka berikan) mungkin dapatkan dengan menggunakan
strlen
sama seperti yang Anda lakukan jika mereka tidak memiliki itu berguna, dan yang akan gagal jika "string" bukan string di tempat pertama).Ada alasan yang sah untuk membutuhkan panjang: jika Anda ingin fungsi Anda bekerja pada substring, mungkin jauh lebih mudah (dan efisien) untuk melewati panjang daripada meminta pengguna melakukan beberapa sihir penyalin bolak-balik untuk mendapatkan byte nol di tempat yang tepat (dan risiko kesalahan satu per satu di sepanjang jalan).
Mampu menangani penyandian di mana null byte tidak diakhiri, atau mampu menangani string yang telah menyematkan nulls (dengan sengaja) dapat berguna dalam beberapa keadaan (tergantung pada apa tepatnya fungsi Anda lakukan).
Mampu menangani data non-null yang dihentikan (array dengan panjang tetap) juga berguna.
Singkatnya: tergantung pada apa yang Anda lakukan di perpustakaan Anda, dan jenis data apa yang Anda harapkan akan ditangani oleh pengguna Anda.
Mungkin juga ada aspek kinerja untuk ini. Jika fungsi Anda perlu mengetahui panjang string terlebih dahulu, dan Anda berharap pengguna Anda setidaknya sudah tahu informasi itu, meminta mereka melewatinya (daripada menghitungnya) dapat mencukur beberapa siklus.
Tetapi jika perpustakaan Anda mengharapkan string teks ASCII biasa, dan Anda tidak memiliki kendala kinerja yang luar biasa dan pemahaman yang sangat baik tentang bagaimana pengguna Anda akan berinteraksi dengan perpustakaan Anda, menambahkan parameter panjang tidak terdengar seperti ide yang bagus. Jika string tidak diakhiri dengan benar, kemungkinan parameter panjangnya akan sama palsu. Saya tidak berpikir Anda akan mendapatkan banyak dengan itu.
sumber
Tidak. String selalu diakhiri dengan nol menurut definisi, panjang string berlebihan.
Data karakter non-null yang dihentikan tidak boleh disebut "string". Memprosesnya (dan melemparkan panjang sekitar) biasanya harus dikemas dalam perpustakaan, dan bukan bagian dari API. Membutuhkan panjang sebagai parameter hanya untuk menghindari panggilan tunggal strlen () sepertinya adalah Pengoptimalan Dini.
Memercayai penelepon fungsi API tidak aman ; perilaku tidak terdefinisi adalah sangat baik jika prasyarat yang didokumentasikan tidak terpenuhi.
Tentu saja, API yang dirancang dengan baik seharusnya tidak mengandung jebakan dan membuatnya mudah untuk digunakan dengan benar. Dan ini hanya berarti harus sesederhana dan sejelas mungkin, menghindari redudansi dan mengikuti konvensi bahasa.
sumber
Anda harus selalu menjaga jarak. Untuk satu, pengguna Anda mungkin ingin mengandung NULL di dalamnya. Dan kedua, jangan lupa bahwa
strlen
O (N) dan perlu menyentuh seluruh cache by-bye cache. Dan ketiga, membuatnya lebih mudah untuk melewati subset-misalnya, mereka bisa memberi kurang dari panjang sebenarnya.sumber
strlen
dalam tes loop.)Anda harus membedakan antara melewatkan string dan melewati buffer .
Dalam C, string secara tradisional diakhiri dengan NUL. Sangat masuk akal untuk mengharapkan ini. Oleh karena itu biasanya tidak perlu melewati panjang tali; itu dapat dihitung dengan
strlen
jika perlu.Ketika melewati buffer , terutama yang ditulis untuk, maka Anda harus benar-benar melewati ukuran buffer. Untuk buffer tujuan, ini memungkinkan callee untuk memastikan bahwa buffer tidak meluap. Untuk buffer input, ini memungkinkan callee untuk menghindari membaca melewati akhir, terutama jika buffer input berisi data acak yang berasal dari sumber yang tidak dipercaya.
Mungkin ada beberapa kebingungan karena baik string dan buffer bisa jadi
char*
dan karena banyak fungsi string menghasilkan string baru dengan menulis ke buffer tujuan. Beberapa orang kemudian menyimpulkan bahwa fungsi string harus mengambil panjang string. Namun, ini adalah kesimpulan yang tidak akurat. Praktek memasukkan ukuran dengan buffer (apakah buffer digunakan untuk string, array integer, struktur, apa pun) adalah mantra yang lebih berguna dan lebih umum.(Dalam hal membaca string dari sumber yang tidak terpercaya (misalnya soket jaringan), penting untuk memasok panjang karena input mungkin tidak diakhiri dengan NUL. Namun , Anda tidak boleh menganggap input sebagai string. Anda harus memperlakukannya sebagai buffer data sewenang-wenang yang mungkin berisi string (tetapi Anda tidak tahu sampai Anda benar-benar memvalidasinya), jadi ini masih mengikuti prinsip bahwa buffer harus memiliki ukuran terkait dan bahwa string tidak memerlukannya.)
sumber
Jika fungsi terutama digunakan dengan string literal, rasa sakit berurusan dengan panjang eksplisit dapat diminimalkan dengan mendefinisikan beberapa makro. Misalnya, diberi fungsi API:
seseorang dapat mendefinisikan makro:
dan kemudian aktifkan seperti yang ditunjukkan pada:
Meskipun dimungkinkan untuk membuat hal-hal "kreatif" untuk melewati makro yang akan dikompilasi tetapi tidak akan benar-benar berfungsi, penggunaan
""
di kedua sisi string dalam evaluasi "sizeof" harus menangkap upaya tak disengaja untuk menggunakan karakter pointer selain string literal yang diurai [jika tidak ada string itu""
, upaya untuk melewatkan pointer karakter akan keliru memberikan panjang sebagai ukuran pointer, minus satu.Pendekatan alternatif dalam C99 akan mendefinisikan tipe struktur "pointer dan panjang" dan mendefinisikan makro yang mengubah string literal menjadi senyawa majemuk dari tipe struktur itu. Sebagai contoh:
Perhatikan bahwa jika seseorang menggunakan pendekatan seperti itu, ia harus melewati struktur tersebut dengan nilai daripada melewati alamat mereka. Kalau tidak, sesuatu seperti:
mungkin gagal karena masa pakai majemuk literal akan berakhir pada akhir pernyataan terlampir mereka.
sumber