Seperti halnya saya suka C dan C ++, saya tidak bisa tidak menggaruk-garuk kepala saya pada pilihan string yang diakhiri null:
- String awalan panjang (yaitu Pascal) ada sebelum C
- String awalan panjang membuat beberapa algoritma lebih cepat dengan memungkinkan pencarian panjang waktu konstan.
- String awalan panjang membuatnya lebih sulit untuk menyebabkan kesalahan buffer overrun.
- Bahkan pada mesin 32 bit, jika Anda membiarkan string menjadi ukuran memori yang tersedia, string awalan panjang hanya tiga byte lebih lebar dari string yang diakhiri null. Pada mesin 16 bit ini adalah satu byte. Pada mesin 64 bit, 4GB adalah batas panjang string yang masuk akal, tetapi bahkan jika Anda ingin memperluasnya ke ukuran kata mesin, mesin 64 bit biasanya memiliki memori yang cukup membuat tambahan tujuh byte byte semacam argumen nol. Saya tahu standar C asli ditulis untuk mesin yang sangat buruk (dalam hal memori), tetapi argumen efisiensi tidak menjual saya di sini.
- Hampir semua bahasa lainnya (yaitu Perl, Pascal, Python, Java, C #, dll) menggunakan string awalan panjang. Bahasa-bahasa ini biasanya mengalahkan C dalam benchmark manipulasi string karena mereka lebih efisien dengan string.
- C ++ memperbaikinya sedikit dengan
std::basic_string
templat, tetapi array karakter biasa yang mengharapkan string yang diakhiri null masih menyebar. Ini juga tidak sempurna karena membutuhkan alokasi tumpukan. - String yang diakhiri Null harus mencadangkan karakter (yaitu, null), yang tidak dapat ada dalam string, sementara string awalan panjang dapat berisi embedded nulls.
Beberapa dari hal-hal ini muncul lebih baru daripada C, jadi masuk akal bagi C untuk tidak mengetahuinya. Namun, beberapa jelas sebelum C terjadi. Mengapa string null yang diakhiri telah dipilih alih-alih awalan panjang yang jelas superior?
EDIT : Karena beberapa meminta fakta (dan tidak suka yang sudah saya berikan) pada poin efisiensi saya di atas, mereka berasal dari beberapa hal:
- Concat menggunakan null terminated string membutuhkan O (n + m) kompleksitas waktu. Awalan panjang seringkali hanya membutuhkan O (m).
- Panjang menggunakan string terminasi nol membutuhkan O (n) kompleksitas waktu. Panjang awalannya adalah O (1).
- Panjang dan concat sejauh ini merupakan operasi string yang paling umum. Ada beberapa kasus di mana string null yang diakhiri dapat lebih efisien, tetapi ini terjadi jauh lebih jarang.
Dari jawaban di bawah, ini adalah beberapa kasus di mana string null yang diakhiri lebih efisien:
- Ketika Anda harus memotong awal string dan harus meneruskannya ke beberapa metode. Anda tidak dapat benar-benar melakukan ini dalam waktu yang konstan dengan awalan panjang bahkan jika Anda diizinkan untuk menghancurkan string asli, karena awalan panjang mungkin perlu mengikuti aturan penyelarasan.
- Dalam beberapa kasus di mana Anda hanya mengulang-ulang karakter string dengan karakter Anda mungkin dapat menyimpan register CPU. Perhatikan bahwa ini hanya berfungsi jika Anda belum mengalokasikan string secara dinamis (Karena itu Anda harus membebaskannya, mengharuskan menggunakan register CPU yang Anda simpan untuk memegang pointer yang Anda dapatkan dari malloc dan teman-teman).
Tak satu pun dari yang di atas hampir umum seperti panjang dan concat.
Ada satu lagi yang ditegaskan dalam jawaban di bawah ini:
- Anda harus memotong ujung tali
tapi yang ini salah - jumlah waktu yang sama untuk string yang diakhiri nol dan panjang awalan. (String diakhiri Null hanya menempel nol di mana Anda ingin akhir baru menjadi, awalan panjang hanya mengurangi dari awalan.)
sumber
Jawaban:
Dari mulut kuda
Dennis M Ritchie, Pengembangan Bahasa C.
sumber
C tidak memiliki string sebagai bagian dari bahasa. 'String' dalam C hanyalah sebuah pointer ke char. Jadi mungkin Anda mengajukan pertanyaan yang salah.
"Apa alasan untuk tidak menggunakan tipe string" mungkin lebih relevan. Untuk itu saya akan menunjukkan bahwa C bukan bahasa berorientasi objek dan hanya memiliki tipe nilai dasar. Sebuah string adalah konsep level yang lebih tinggi yang harus diimplementasikan dengan cara menggabungkan nilai dari tipe lain. C berada pada tingkat abstraksi yang lebih rendah.
mengingat badai mengamuk di bawah ini:
Saya hanya ingin menunjukkan bahwa saya tidak berusaha mengatakan ini adalah pertanyaan bodoh atau buruk, atau bahwa cara C mewakili string adalah pilihan terbaik. Saya mencoba mengklarifikasi bahwa pertanyaan akan lebih ringkas jika Anda memperhitungkan fakta bahwa C tidak memiliki mekanisme untuk membedakan string sebagai tipe data dari array byte. Apakah ini pilihan terbaik mengingat kekuatan pemrosesan dan memori komputer saat ini? Mungkin tidak. Tapi kalau dipikir-pikir selalu 20/20 dan semua itu :)
sumber
char *temp = "foo bar";
adalah pernyataan yang valid dalam bahasa C ... hei! bukankah itu sebuah string? bukankah itu nol diakhiri?Pertanyaan diajukan sebagai hal
Length Prefixed Strings (LPS)
vszero terminated strings (SZ)
, tetapi sebagian besar mengekspos manfaat string awalan panjang. Itu mungkin tampak luar biasa, tetapi jujur saja kita juga harus mempertimbangkan kelemahan LPS dan kelebihan SZ.Seperti yang saya pahami, pertanyaan itu bahkan dapat dipahami sebagai cara yang bias untuk bertanya "apa keuntungan dari Zero Terminated Strings?".
Keuntungan (saya melihat) dari Zero Terminated Strings:
"this\0is\0valid\0C"
. Apakah ini sebuah string? atau empat senar? Atau banyak byte ...char a[3] = "foo";
valid C (bukan C ++) dan tidak akan menempatkan nol akhir dalam a.char*
. Yaitu untuk tidak mengembalikan alamat string, tetapi sebaliknya untuk mengembalikan data aktual.Yang mengatakan, tidak perlu mengeluh dalam kasus langka di mana string C standar memang tidak efisien. Lib tersedia. Jika saya mengikuti tren itu, saya harus mengeluh bahwa standar C tidak termasuk fungsi dukungan regex ... tapi benar-benar semua orang tahu itu bukan masalah karena ada perpustakaan yang tersedia untuk tujuan itu. Jadi ketika efisiensi manipulasi string diinginkan, mengapa tidak menggunakan perpustakaan seperti bstring ? Atau bahkan string C ++?
EDIT : Saya baru-baru melihat ke D string . Cukup menarik untuk melihat bahwa solusi yang dipilih bukanlah awalan ukuran, atau nol penghentian. Seperti dalam C, string literal yang dilampirkan dalam tanda kutip ganda hanya tulisan pendek untuk array char yang tidak dapat diubah, dan bahasa tersebut juga memiliki string kata kunci yang berarti (array char yang tidak dapat diubah).
Tapi array D jauh lebih kaya daripada array C. Dalam kasus panjang array statis diketahui pada saat run-time sehingga tidak perlu menyimpan panjangnya. Compiler memilikinya pada waktu kompilasi. Dalam kasus array dinamis, panjang tersedia tetapi dokumentasi D tidak menyatakan di mana disimpan. Sejauh yang kita ketahui, kompiler dapat memilih untuk menyimpannya dalam register, atau dalam variabel yang disimpan jauh dari data karakter.
Pada array char normal atau string non literal tidak ada nol akhir, maka programmer harus meletakkannya sendiri jika ia ingin memanggil beberapa fungsi C dari D. Dalam kasus string string literal tertentu, namun kompiler D masih menempatkan nol di akhir setiap string (untuk memungkinkan cast mudah ke string C untuk membuat lebih mudah memanggil fungsi C?), tetapi nol ini bukan bagian dari string (D tidak menghitungnya dalam ukuran string).
Satu-satunya hal yang agak mengecewakan saya adalah bahwa string seharusnya utf-8, tetapi panjang tampaknya masih mengembalikan sejumlah byte (setidaknya itu benar pada kompiler gdc saya) bahkan ketika menggunakan karakter multi-byte. Tidak jelas bagi saya apakah itu bug kompiler atau dengan sengaja. (OK, saya mungkin telah menemukan apa yang terjadi. Untuk mengatakan kepada D compiler sumber Anda menggunakan utf-8 Anda harus meletakkan beberapa tanda urutan byte bodoh di awal. Saya menulis bodoh karena saya tahu tidak editor melakukan itu, terutama untuk UTF- 8 yang seharusnya kompatibel dengan ASCII).
sumber
std::basic_string
dilakukannya.\0
di akhir ketika programmer menginginkannya daripada yang implisit. Panjang yang saling tergantung jauh lebih buruk.Saya pikir, ini memiliki alasan historis dan menemukan ini di wikipedia :
sumber
Calavera adalah benar , tetapi sebagai orang tampaknya tidak mendapatkan titik, saya akan memberikan beberapa contoh kode.
Pertama, mari kita pertimbangkan apa itu C: bahasa sederhana, di mana semua kode memiliki terjemahan langsung ke dalam bahasa mesin. Semua tipe masuk ke register dan di stack, dan tidak memerlukan sistem operasi atau perpustakaan run-time yang besar untuk dijalankan, karena itu dimaksudkan untuk menulis hal-hal ini (tugas yang sangat cocok, mengingat ada bahkan tidak menjadi pesaing hingga hari ini).
Jika C memiliki
string
tipe, sukaint
atauchar
, itu akan menjadi tipe yang tidak cocok dalam register atau di stack, dan akan membutuhkan alokasi memori (dengan semua infrastruktur pendukungnya) untuk ditangani dengan cara apa pun. Semua itu bertentangan dengan prinsip dasar C.Jadi, string dalam C adalah:
Jadi, mari kita asumsikan bahwa ini adalah awalan panjang. Mari kita menulis kode untuk menggabungkan dua string:
Alternatif lain akan menggunakan struct untuk mendefinisikan string:
Pada titik ini, semua manipulasi string akan membutuhkan dua alokasi yang harus dibuat, yang, dalam praktiknya, berarti Anda akan pergi melalui perpustakaan untuk melakukan penanganannya.
Lucunya ... struct seperti itu memang ada di C! Mereka hanya tidak digunakan untuk menampilkan pesan sehari-hari Anda ke penanganan pengguna.
Jadi, di sini adalah titik Calavera membuat: tidak ada tipe string di C . Untuk melakukan apa pun dengan itu, Anda harus mengambil pointer dan mendekode sebagai pointer ke dua jenis yang berbeda, dan kemudian menjadi sangat relevan dengan ukuran string, dan tidak bisa dibiarkan begitu saja sebagai "implementasi didefinisikan".
Sekarang, C dapat menangani memori dengan cara apapun, dan
mem
fungsi - fungsi di perpustakaan (di<string.h>
, bahkan!) Menyediakan semua alat yang Anda butuhkan untuk menangani memori sebagai sepasang penunjuk dan ukuran. Apa yang disebut "string" dalam C dibuat hanya untuk satu tujuan: menampilkan pesan dalam konteks penulisan sistem operasi yang ditujukan untuk terminal teks. Dan, untuk itu, penghentian nol sudah cukup.sumber
strlen
dan teman-teman. Adapun masalah dengan "menyerahkannya ke implementasi", Anda bisa mengatakan bahwa awalan adalah apa pun yangshort
ada di kotak target. Maka semua casting Anda akan tetap bekerja. 3. Saya bisa membuat skenario yang dibuat sepanjang hari yang membuat satu atau sistem lainnya terlihat buruk.short
efektif membatasi ukuran string, yang tampaknya merupakan satu hal yang tidak mereka sukai. Saya sendiri, setelah bekerja dengan string BASIC dan Pascal 8-bit, string COBOL ukuran tetap dan hal-hal serupa, menjadi penggemar berat string C ukuran tak terbatas dengan cepat. Saat ini, ukuran 32-bit akan menangani string praktis, tetapi menambahkan byte-byte tersebut sebelumnya bermasalah.string
tipe nyata : itu tidak menyadari karakter. Ini adalah array dari "char" ("char" dalam istilah mesin adalah karakter sebanyak "kata" adalah apa yang manusia sebut kata dalam sebuah kalimat). String karakter adalah konsep tingkat tinggi yang dapat diimplementasikan di atas arraychar
jika Anda memperkenalkan gagasan pengkodean.buf
membutuhkan alokasi), atau gunakanstruct string {int len; char buf[]};
dan alokasikan semuanya dengan satu alokasi sebagai anggota array yang fleksibel, dan bagikan sebagai: astring*
. (Atau Diperdebatkan,struct string {int capacity; int len; char buf[]};
untuk alasan kinerja yang jelas)Tentunya untuk kinerja dan keamanan, Anda harus menjaga panjang string saat Anda bekerja dengannya daripada berulang kali melakukan
strlen
atau setara di atasnya. Namun, menyimpan panjang di lokasi tetap sebelum konten string adalah desain yang sangat buruk. Seperti yang Jörgen tunjukkan dalam komentar pada jawaban Sanjit, itu menghalangi memperlakukan ekor string sebagai string, yang misalnya membuat banyak operasi umum sukapath_to_filename
ataufilename_to_extension
tidak mungkin tanpa mengalokasikan memori baru (dan menimbulkan kemungkinan kegagalan dan penanganan kesalahan) . Dan tentu saja ada masalah yang tak seorang pun dapat menyetujui berapa byte bidang panjang string yang harus ditempati (banyak "string Pascal" yang burukDesain C membiarkan programmer memilih jika / di mana / bagaimana menyimpannya jauh lebih fleksibel dan kuat. Tetapi tentu saja programmer harus pintar. C menghukum kebodohan dengan program yang macet, berhenti, atau memberi root musuh Anda.
sumber
Malas, mendaftar berhemat dan mudah dibawa mengingat nyali perakitan bahasa apa pun, terutama C yang merupakan satu langkah di atas perakitan (sehingga mewarisi banyak kode warisan perakitan). Anda akan setuju sebagai null char akan sia-sia di hari-hari ASCII, itu (dan mungkin sebaik char control EOF).
mari kita lihat dalam kode semu
total 1 penggunaan register
kasus 2
total 2 register digunakan
Itu mungkin tampak picik pada waktu itu, tetapi mengingat berhemat dalam kode dan register (yang PREMIUM pada waktu itu, waktu ketika Anda tahu, mereka menggunakan kartu punch). Dengan demikian menjadi lebih cepat (ketika kecepatan prosesor dapat dihitung dalam kHz), "Retasan" ini sangat bagus dan mudah dibawa ke prosesor yang tidak memiliki register dengan mudah.
Demi argumen saya akan menerapkan 2 operasi string umum
kompleksitas O (n) di mana dalam banyak kasus string PASCAL adalah O (1) karena panjang string dipra-pended ke struktur string (itu juga berarti bahwa operasi ini harus dilakukan pada tahap sebelumnya).
kompleksitas O (n) dan menambahkan panjang string tidak akan mengubah kompleksitas operasi, sementara saya akui itu akan memakan waktu 3 kali lebih sedikit.
Di sisi lain, jika Anda menggunakan string PASCAL Anda harus mendesain ulang API Anda untuk memperhitungkan panjang register dan bit-endianness, string PASCAL mendapatkan batasan 255 char (0xFF) yang terkenal karena panjangnya disimpan dalam 1 byte (8bits) ), dan jika Anda menginginkan string yang lebih panjang (16bits-> apa pun), Anda harus memperhitungkan arsitektur dalam satu lapisan kode Anda, yang pada umumnya berarti API string yang tidak kompatibel jika Anda menginginkan string yang lebih panjang.
Contoh:
Satu file ditulis dengan string api prepended Anda pada komputer 8 bit dan kemudian harus dibaca pada katakanlah komputer 32 bit, apa yang program malas menganggap bahwa 4bytes Anda adalah panjang string kemudian mengalokasikan banyak memori kemudian mencoba membaca banyak byte. Kasus lain adalah PPC 32 byte string membaca (little endian) ke x86 (big endian), tentu saja jika Anda tidak tahu bahwa satu ditulis oleh yang lain akan ada masalah. Panjang 1 byte (0x00000001) akan menjadi 16777216 (0x0100000) yaitu 16 MB untuk membaca string 1 byte. Tentu saja Anda akan mengatakan bahwa orang harus menyetujui satu standar tetapi bahkan 16bit unicode mendapat sedikit dan endianness besar.
Tentu saja C akan memiliki masalah juga tetapi, akan sangat sedikit dipengaruhi oleh masalah yang diangkat di sini.
sumber
O(m+n)
dengan string nullterm,O(n)
khas di tempat lain. PanjangO(n)
dengan string nullterm, diO(1)
mana saja. Bergabunglah:O(n^2)
dengan string nullterm, diO(n)
mana pun. Ada beberapa kasus di mana string null yang diakhiri lebih efisien (yaitu hanya menambahkan satu ke case pointer), tetapi concat dan panjangnya adalah operasi yang paling umum (panjang setidaknya diperlukan untuk memformat, output file, tampilan konsol, dll) . Jika Anda men-cache panjang untuk mengamortisasiO(n)
Anda , Anda hanya membuat poin saya bahwa panjang harus disimpan dengan string.Dalam banyak hal, C adalah primitif. Dan saya menyukainya.
Itu adalah langkah di atas bahasa rakitan, memberi Anda kinerja yang hampir sama dengan bahasa yang jauh lebih mudah untuk ditulis dan dipelihara.
Terminator nol sederhana dan tidak memerlukan dukungan khusus oleh bahasa.
Melihat ke belakang, sepertinya tidak nyaman. Tapi saya menggunakan bahasa assembly di tahun 80-an dan sepertinya sangat nyaman saat itu. Saya hanya berpikir perangkat lunak terus berkembang, dan platform dan alat terus-menerus semakin canggih.
sumber
Dengan asumsi sejenak bahwa C mengimplementasikan string dengan cara Pascal, dengan mengawali panjangnya: apakah string 7 char adalah DATA TYPE yang sama dengan string 3-char? Jika jawabannya adalah ya, lalu kode seperti apa yang harus dihasilkan oleh kompiler ketika saya menetapkan yang pertama ke yang terakhir? Haruskah string dipotong, atau secara otomatis diubah ukurannya? Jika diubah ukurannya, haruskah operasi itu dilindungi oleh kunci untuk membuatnya aman? Sisi pendekatan C melangkah semua masalah ini, suka atau tidak :)
sumber
Entah bagaimana saya memahami pertanyaan untuk menyiratkan tidak ada dukungan kompiler untuk string awalan panjang di C. Contoh berikut menunjukkan, setidaknya Anda dapat memulai perpustakaan string C Anda sendiri, di mana panjang string dihitung pada waktu kompilasi, dengan konstruksi seperti ini:
Ini tidak akan, bagaimanapun, datang tanpa masalah karena Anda harus berhati-hati ketika secara khusus membebaskan pointer string itu dan ketika itu dialokasikan secara statis (
char
array literal ).Sunting: Sebagai jawaban yang lebih langsung untuk pertanyaan, pandangan saya adalah ini adalah cara C dapat mendukung keduanya memiliki panjang string yang tersedia (sebagai konstanta waktu kompilasi), jika Anda memerlukannya, tetapi masih tanpa overhead memori jika Anda ingin menggunakan hanya pointer dan terminasi nol.
Tentu saja sepertinya bekerja dengan string tanpa-penghentian nol adalah praktik yang disarankan, karena pustaka standar secara umum tidak menggunakan panjang string sebagai argumen, dan karena mengekstraksi panjangnya tidak semudah kode sederhana
char * s = "abc"
, seperti yang ditunjukkan oleh contoh saya.sumber
char*
, banyak metode yang tidak mengharapkan pengakhiran null juga mengharapkan achar*
. Manfaat yang lebih signifikan dari pemisahan jenis akan berhubungan dengan perilaku Unicode. Mungkin bermanfaat bagi implementasi string untuk memelihara flag-flag untuk apakah string diketahui mengandung jenis karakter tertentu, atau diketahui tidak mengandung mereka [misalnya menemukan titik kode 999.990 dalam string jutaan karakter yang diketahui tidak mengandung setiap karakter di luar bidang multibahasa dasar akan menjadi perintah yang lebih cepat ...Pertama, tambahan 3 byte mungkin merupakan overhead yang cukup untuk string pendek. Secara khusus, string dengan panjang nol sekarang membutuhkan 4 kali lebih banyak memori. Beberapa dari kita menggunakan mesin 64-bit, jadi kita perlu 8 byte untuk menyimpan string panjang nol, atau format string tidak dapat mengatasi string terpanjang yang didukung platform.
Mungkin juga ada masalah keberpihakan yang harus dihadapi. Misalkan saya memiliki blok memori yang berisi 7 string, seperti "solo \ 0second \ 0 \ 0four \ 0five \ 0five \ 0 \ 0seventh". String kedua dimulai pada offset 5. Perangkat keras mungkin mengharuskan bilangan bulat 32-bit diluruskan pada alamat yang merupakan kelipatan dari 4, jadi Anda harus menambahkan bantalan, menambah biaya overhead lebih jauh. Representasi C sangat hemat memori dibandingkan. (Efisiensi memori baik; itu membantu kinerja cache, misalnya.)
sumber
Pengakhiran nol memungkinkan untuk operasi berbasis penunjuk cepat.
sumber
strlen
. Saya akan mengatakan itu sedikit kelemahan.Satu hal yang belum disebutkan: ketika C dirancang, ada banyak mesin di mana 'char' tidak delapan bit (bahkan saat ini ada platform DSP di tempat yang tidak). Jika seseorang memutuskan bahwa string harus awalan panjang, berapa awalan panjang nilai char 'harus digunakan? Menggunakan dua akan memaksakan batas buatan pada panjang string untuk mesin dengan 8-bit char dan 32-bit addressing space, sementara membuang ruang pada mesin dengan 16-bit char dan 16-bit addressing space.
Jika seseorang ingin membiarkan string panjang sewenang-wenang disimpan secara efisien, dan jika 'char' selalu 8-bit, seseorang dapat - untuk beberapa biaya dalam kecepatan dan ukuran kode - mendefinisikan skema adalah string yang diawali oleh angka genap N akan menjadi N / 2 byte panjang, sebuah string yang diawali dengan nilai ganjil N dan nilai genap M (membaca mundur) bisa menjadi ((N-1) + M * char_max) / 2, dll. Dan mensyaratkan bahwa setiap buffer yang klaim untuk menawarkan sejumlah ruang tertentu untuk menampung string harus memungkinkan byte yang cukup sebelum ruang itu untuk menangani panjang maksimum. Fakta bahwa 'char' tidak selalu 8 bit, bagaimanapun, akan menyulitkan skema seperti itu, karena jumlah 'char' yang dibutuhkan untuk memegang panjang string akan bervariasi tergantung pada arsitektur CPU.
sumber
sizeof(char)
.sizeof(char)
adalah satu. Selalu. Satu bisa memiliki awalan menjadi ukuran yang ditentukan implementasi, tetapi akan canggung. Lebih jauh lagi, tidak ada cara nyata untuk mengetahui ukuran "tepat" seharusnya. Jika seseorang memegang banyak string 4-karakter, zero-padding akan membebankan 25% overhead, sedangkan awalan panjang empat byte akan memberlakukan 100% overhead. Lebih lanjut, waktu yang dihabiskan untuk mengemas dan membongkar prefiks panjang empat byte dapat melebihi biaya pemindaian string 4-byte untuk byte nol.size_t
awalan (pemborosan memori terkutuk, itu akan menjadi sanest --- memungkinkan string dengan panjang berapa pun panjang yang mungkin bisa masuk ke dalam memori). Bahkan, itu semacam apa D tidak; array adalahstruct { size_t length; T* ptr; }
, dan string hanyalah arrayimmutable(char)
.Banyak keputusan desain seputar C berasal dari fakta bahwa ketika awalnya diimplementasikan, melewati parameter agak mahal. Diberi pilihan antara misalnya
melawan
yang terakhir akan sedikit lebih murah (dan karena itu lebih disukai) karena hanya diperlukan melewati satu parameter daripada dua. Jika metode yang dipanggil tidak perlu mengetahui alamat basis dari array atau indeks di dalamnya, melewati satu pointer yang menggabungkan keduanya akan lebih murah daripada melewati nilai-nilai secara terpisah.
Meskipun ada banyak cara yang masuk akal di mana C dapat menyandikan panjang string, pendekatan yang telah ditemukan hingga saat itu akan memiliki semua fungsi yang diperlukan yang harus dapat bekerja dengan bagian dari string untuk menerima alamat basis string dan indeks yang diinginkan sebagai dua parameter terpisah. Menggunakan terminasi nol byte memungkinkan untuk menghindari persyaratan itu. Meskipun pendekatan lain akan lebih baik dengan mesin saat ini (kompiler modern sering melewati parameter dalam register, dan memcpy dapat dioptimalkan dengan cara strcpy () - yang setara tidak dapat) kode produksi yang cukup menggunakan string terminasi nol-byte sehingga sulit untuk mengubah ke yang lain.
PS - Sebagai imbalan atas penalti kecepatan sedikit pada beberapa operasi, dan sedikit overhead tambahan pada string yang lebih panjang, akan mungkin untuk memiliki metode yang bekerja dengan string menerima pointer langsung ke string, buffer string yang diperiksa batas , atau struktur data yang mengidentifikasi substring dari string lain. Fungsi seperti "strcat" akan terlihat seperti [sintaks modern]
Sedikit lebih besar dari metode strcat K&R, tetapi ini akan mendukung pengecekan batas, yang mana metode K&R tidak. Lebih jauh, tidak seperti metode saat ini, akan mungkin untuk dengan mudah menggabungkan substring sewenang-wenang, misalnya
Perhatikan bahwa masa pakai string yang dikembalikan oleh temp_substring akan dibatasi oleh orang-orang dari
s
dansrc
, yang pernah lebih pendek (itulah sebabnya metode iniinf
harus diteruskan - jika itu lokal, itu akan mati ketika metode kembali).Dalam hal biaya memori, string dan buffer hingga 64 byte akan memiliki satu byte overhead (sama dengan string yang diakhiri nol); string yang lebih panjang akan memiliki sedikit lebih banyak (apakah satu diperbolehkan jumlah overhead antara dua byte dan maksimum yang diperlukan akan menjadi tradeoff waktu / ruang). Nilai khusus dari byte panjang / mode akan digunakan untuk menunjukkan bahwa fungsi string diberi struktur yang mengandung byte bendera, pointer, dan panjang buffer (yang kemudian dapat mengindeks secara sewenang-wenang ke string lain).
Tentu saja, K&R tidak menerapkan hal seperti itu, tetapi itu kemungkinan besar karena mereka tidak ingin menghabiskan banyak upaya untuk penanganan string - suatu daerah di mana bahkan hari ini banyak bahasa tampak agak anemia.
sumber
char* arr
dari menunjuk ke struktur formulirstruct { int length; char characters[ANYSIZE_ARRAY] };
atau serupa yang masih bisa dilewati sebagai parameter tunggal.str[n]
merujuk pada char yang tepat. Ini adalah hal-hal yang tidak dipikirkan orang-orang yang mendiskusikan hal ini .Menurut Joel Spolsky dalam posting blog ini ,
Setelah melihat semua jawaban lain di sini, saya yakin bahwa bahkan jika ini benar, itu hanya bagian dari alasan C memiliki "string" yang diakhiri dengan null. Posting itu cukup menjelaskan bagaimana hal-hal sederhana seperti string sebenarnya bisa sangat sulit.
sumber
.ASCIZ
hanyalah pernyataan assembler untuk membangun urutan byte, diikuti oleh0
. Ini hanya berarti bahwa nol string yang dihentikan adalah konsep mapan pada waktu itu. Ini tidak berarti bahwa string yang diakhiri nol adalah sesuatu yang terkait dengan arsitektur PDP- *, kecuali bahwa Anda dapat menulis loop ketat yang terdiri dariMOVB
(salin satu byte) danBNE
(cabang jika byte terakhir yang disalin bukan nol).Bukan Rasional tentu tapi tandingan panjang-disandikan
Bentuk-bentuk tertentu dari pengkodean panjang dinamis lebih unggul daripada pengkodean panjang statis sejauh menyangkut memori, semuanya tergantung pada penggunaan. Lihat saja UTF-8 sebagai bukti. Ini pada dasarnya adalah array karakter yang dapat diperluas untuk mengkodekan satu karakter. Ini menggunakan bit tunggal untuk setiap byte yang diperluas. Pengakhiran NUL menggunakan 8 bit. Panjang-awalan Saya pikir bisa disebut panjang tak terbatas juga dengan menggunakan 64 bit. Seberapa sering Anda menekan kasus bit ekstra Anda adalah faktor penentu. Hanya 1 string yang sangat besar? Siapa yang peduli jika Anda menggunakan 8 atau 64 bit? Banyak string kecil (Yaitu String kata-kata bahasa Inggris)? Maka biaya awalan Anda adalah persentase yang besar.
String dengan awalan panjang yang memungkinkan penghematan waktu bukanlah hal yang nyata . Apakah data Anda yang disediakan harus memiliki panjang yang disediakan, Anda menghitung pada waktu kompilasi, atau Anda benar-benar diberikan data dinamis yang harus Anda encode sebagai string. Ukuran ini dihitung pada beberapa titik dalam algoritma. Variabel terpisah untuk menyimpan ukuran string yang dihentikan nol dapat disediakan. Yang membuat perbandingan pada penghematan waktu diperdebatkan. Satu hanya memiliki NUL ekstra di akhir ... tetapi jika panjang encode tidak termasuk NUL itu maka secara harfiah tidak ada perbedaan antara keduanya. Tidak ada perubahan algoritmik yang diperlukan sama sekali. Hanya sebuah pre-pass Anda harus mendesain sendiri secara manual alih-alih membuat kompiler / runtime melakukannya untuk Anda. C sebagian besar tentang melakukan sesuatu secara manual.
Panjang-awalan menjadi opsional adalah nilai jual. Saya tidak selalu membutuhkan info tambahan untuk suatu algoritma sehingga diminta untuk melakukannya untuk setiap string membuat waktu komputasi + komputasi saya tidak pernah bisa turun di bawah O (n). (Yaitu hardware nomor acak generator 1-128. Saya dapat menarik dari "string tak terbatas". Katakan saja hanya menghasilkan karakter begitu cepat. Jadi panjang string kami berubah sepanjang waktu. Tetapi penggunaan data saya mungkin tidak peduli seberapa banyak byte acak yang saya miliki. Itu hanya ingin byte yang tidak terpakai berikutnya tersedia segera setelah itu bisa mendapatkannya setelah permintaan. Saya bisa menunggu di perangkat. Tapi saya juga bisa memiliki buffer karakter pra-baca. Perbandingan panjang adalah pemborosan perhitungan yang tidak perlu. Pemeriksaan nol lebih efisien.)
Panjang-awalan adalah pelindung yang baik terhadap buffer overflow? Begitu juga penggunaan fungsi dan implementasi perpustakaan secara waras. Bagaimana jika saya meneruskan data yang cacat? Buffer saya panjangnya 2 byte tapi saya bilang fungsinya 7! Mis: Jika get () dimaksudkan untuk digunakan pada data yang diketahui itu bisa saja memiliki pemeriksaan buffer internal yang menguji buffer yang terkompilasi dan malloc ()panggilan dan masih mengikuti spesifikasi. Jika itu dimaksudkan untuk digunakan sebagai pipa untuk STDIN yang tidak diketahui untuk sampai pada buffer yang tidak diketahui maka jelas seseorang tidak dapat mengetahui tentang ukuran buffer yang berarti panjang arg tidak ada gunanya, Anda perlu sesuatu yang lain di sini seperti cek kenari. Dalam hal ini, Anda tidak dapat awalan panjang beberapa aliran dan input, Anda hanya tidak bisa. Yang berarti pemeriksaan panjang harus dibangun ke dalam algoritma dan bukan bagian ajaib dari sistem pengetikan. TL; DR NUL yang diputus tidak pernah harus tidak aman, itu hanya berakhir seperti itu melalui penyalahgunaan.
counter-counter point: NUL-termination menjengkelkan pada biner. Anda juga perlu melakukan awalan panjang di sini atau mengubah byte NUL dengan beberapa cara: kode-lepas, range remapping, dll ... yang tentu saja berarti lebih banyak penggunaan memori / pengurangan-informasi / lebih banyak operasi-per-byte. Panjang-awalan sebagian besar memenangkan perang di sini. Satu-satunya terbalik untuk transformasi adalah bahwa tidak ada fungsi tambahan harus ditulis untuk menutupi string awalan panjang. Yang berarti pada rutinitas sub-O (n) yang lebih dioptimalkan, Anda dapat membuatnya secara otomatis bertindak sebagai padanan O (n) tanpa menambahkan kode lebih banyak. Kelemahannya, tentu saja, waktu / memori / limbah kompresi bila digunakan pada string NUL yang berat.Bergantung pada seberapa banyak perpustakaan Anda yang akhirnya Anda duplikasi untuk beroperasi pada data biner, mungkin masuk akal untuk bekerja hanya dengan string awalan panjang. Yang mengatakan orang juga bisa melakukan hal yang sama dengan string awalan panjang ... -1 panjang bisa berarti NUL-dihentikan dan Anda dapat menggunakan string NUL-dihentikan di dalam panjang-dihentikan.
Concat: "O (n + m) vs O (m)" Saya menganggap Anda merujuk ke m sebagai total panjang string setelah digabungkan karena mereka berdua harus memiliki jumlah operasi minimum (Anda tidak bisa hanya menangani -pada string 1, bagaimana jika Anda harus realokasi?). Dan saya berasumsi n adalah jumlah operasi mitos yang tidak perlu Anda lakukan lagi karena pre-compute. Jika demikian, maka jawabannya sederhana: pra-hitung. JikaAnda bersikeras Anda akan selalu memiliki cukup memori untuk tidak perlu realokasi dan itulah dasar dari notasi O-besar maka jawabannya bahkan lebih sederhana: melakukan pencarian biner pada memori yang dialokasikan untuk akhir string 1, jelas ada yang besar carikan nol tanpa batas setelah string 1 agar kita tidak khawatir tentang realokasi. Di sana, dengan mudah mendapat n untuk log (n) dan saya nyaris tidak mencoba. Yang jika Anda ingat log (n) pada dasarnya hanya sebesar 64 pada komputer nyata, yang pada dasarnya seperti mengatakan O (64 + m), yang pada dasarnya adalah O (m). (Dan ya, logika itu telah digunakan dalam analisis run-time dari struktur data nyata yang digunakan hari ini. Ini bukan omong kosong dari atas kepala saya.)
Concat () / Len () lagi : Memoize results. Mudah. Mengubah semua perhitungan menjadi pra-perhitungan jika memungkinkan / perlu. Ini adalah keputusan algoritmik. Ini bukan kendala bahasa yang dipaksakan.
Pengambilan string suffix lebih mudah / mungkin dengan terminasi NUL. Tergantung pada bagaimana awalan panjang diimplementasikan itu dapat merusak pada string asli dan kadang-kadang bahkan tidak mungkin Membutuhkan salinan dan lulus O (n) bukan O (1).
Argumen-passing / de-referencing kurang untuk awalan NUL versus panjang-awalan. Jelas karena Anda memberikan informasi yang lebih sedikit. Jika Anda tidak membutuhkan panjang, maka ini menghemat banyak jejak dan memungkinkan pengoptimalan.
Anda bisa curang. Benar-benar hanya sebuah pointer. Siapa bilang Anda harus membacanya sebagai string? Bagaimana jika Anda ingin membacanya sebagai karakter tunggal atau float? Bagaimana jika Anda ingin melakukan yang sebaliknya dan membaca pelampung sebagai string? Jika Anda berhati-hati, Anda dapat melakukan ini dengan penghentian NUL. Anda tidak dapat melakukan ini dengan awalan panjang, ini adalah tipe data yang berbeda dari pointer biasanya. Anda kemungkinan besar harus membangun string byte-by-byte dan mendapatkan panjangnya. Tentu saja jika Anda menginginkan sesuatu seperti seluruh float (mungkin memiliki NUL di dalamnya) Anda harus membaca byte-by-byte, tetapi rinciannya diserahkan kepada Anda untuk memutuskan.
TL; DR Apakah Anda menggunakan data biner? Jika tidak, maka pemutusan NUL memungkinkan lebih banyak kebebasan algoritmik. Jika ya, maka kuantitas kode vs kecepatan / memori / kompresi adalah perhatian utama Anda. Perpaduan dari dua pendekatan atau memoisasi mungkin yang terbaik.
sumber
Saya tidak membeli jawaban "C tidak punya string". Benar, C tidak mendukung tipe tingkat tinggi bawaan tetapi Anda masih bisa mewakili struktur data di C dan itulah string. Fakta bahwa sebuah string hanyalah sebuah penunjuk dalam C tidak berarti bahwa N byte pertama tidak dapat memiliki arti khusus sebagai panjangnya.
Pengembang Windows / COM akan sangat terbiasa dengan
BSTR
tipe yang persis seperti ini - string C yang diawali dengan panjang di mana data karakter sebenarnya dimulai bukan pada byte 0.Jadi sepertinya keputusan untuk menggunakan penghentian nol hanyalah apa yang disukai orang, bukan keharusan bahasa.
sumber
gcc menerima kode di bawah ini:
char s [4] = "abcd";
dan tidak masalah jika kita memperlakukannya sebagai array karakter tetapi bukan string. Yaitu, kita dapat mengaksesnya dengan s [0], s [1], s [2], dan s [3], atau bahkan dengan memcpy (dest, s, 4). Tapi kita akan mendapatkan karakter berantakan ketika kita mencoba dengan menempatkan (s), atau lebih buruk dengan strcpy (dest, s).
sumber