Mengapa literal string C hanya baca?

29

Apa keuntungan dari string literal menjadi read-just justify (-ies / -ied):

  1. Namun cara lain untuk menembak diri sendiri di kaki

    char *foo = "bar";
    foo[0] = 'd'; /* SEGFAULT */
    
  2. Ketidakmampuan untuk secara elegan menginisialisasi array kata-baca dalam satu baris:

    char *foo[] = { "bar", "baz", "running out of traditional placeholder names" };
    foo[1][2] = 'n'; /* SEGFAULT */ 
    
  3. Rumit bahasanya sendiri.

    char *foo = "bar";
    char var[] = "baz";
    some_func(foo); /* VERY DANGEROUS! */
    some_func(var); /* LESS DANGEROUS! */
    

Menyimpan memori? Saya sudah membaca di suatu tempat (tidak dapat menemukan sumbernya sekarang) sejak dahulu kala, ketika RAM langka, kompiler mencoba mengoptimalkan penggunaan memori dengan menggabungkan string yang serupa.

Misalnya, "lebih" dan "regex" akan menjadi "moregex". Apakah ini masih benar hari ini, di era film berkualitas blu-ray digital? Saya mengerti bahwa embedded system masih beroperasi di lingkungan sumber daya terbatas, tetapi tetap saja, jumlah memori yang tersedia telah meningkat secara dramatis.

Masalah kompatibilitas? Saya berasumsi bahwa program lawas yang akan mencoba mengakses memori read-only akan crash atau melanjutkan dengan bug yang belum ditemukan. Jadi, tidak ada program legacy yang harus mencoba mengakses string literal dan karenanya mengizinkan untuk menulis ke string literal tidak akan membahayakan program legacy portabel yang valid, tidak meretas, dan portabel .

Apakah ada alasan lain? Apakah alasan saya salah? Apakah masuk akal untuk mempertimbangkan perubahan untuk membaca-menulis string literal dalam standar C baru atau setidaknya menambahkan opsi ke kompiler? Apakah ini dianggap sebelumnya atau "masalah" saya terlalu kecil dan tidak signifikan untuk mengganggu siapa pun?

Marius Macijauskas
sumber
12
Saya berasumsi Anda telah melihat bagaimana string literal terlihat dalam kode yang dikompilasi ?
2
Lihatlah kumpulan yang berisi tautan yang saya berikan. Itu tepat di sana.
8
Contoh "moregex" Anda tidak akan berfungsi karena pemutusan nol.
dan04
4
Anda tidak ingin menulis konstanta karena itu akan mengubah nilainya. Lain kali Anda ingin menggunakan konstanta yang sama akan berbeda. Compiler / runtime harus sumber konstanta dari suatu tempat, dan di mana pun itu Anda tidak boleh memodifikasi.
Erik Eidt
1
'Jadi string literal disimpan dalam memori program, bukan RAM, dan buffer overflow akan mengakibatkan korupsi program itu sendiri?' Gambar program juga dalam RAM. Lebih tepatnya, string literal disimpan dalam segmen RAM yang sama yang digunakan untuk menyimpan gambar program. Dan ya, menimpa string dapat merusak program. Kembali pada zaman MS-DOS dan CP / M tidak ada perlindungan memori, Anda bisa melakukan hal-hal seperti ini, dan biasanya menyebabkan masalah yang mengerikan. Virus PC pertama akan menggunakan trik seperti itu untuk memodifikasi program Anda sehingga ia memformat hard drive Anda ketika Anda mencoba menjalankannya.
Charles E. Grant

Jawaban:

40

Secara historis (mungkin dengan menulis ulang bagian dari itu), itu adalah kebalikannya. Pada komputer pertama di awal tahun 1970-an (mungkin PDP-11 ) yang menjalankan prototipe embrionik C (mungkin BCPL ) tidak ada MMU dan tidak ada perlindungan memori (yang ada pada kebanyakan mainframe IBM / 360 yang lebih lama ). Jadi setiap byte memori (termasuk yang menangani string literal atau kode mesin) dapat ditimpa oleh program yang salah (bayangkan sebuah program mengubah beberapa %ke /dalam string format printf (3) ). Oleh karena itu, string dan konstanta literal dapat ditulis.

Sebagai seorang remaja pada tahun 1975, saya membuat kode di museum Palais de la Découverte di Paris pada komputer era 1960-an tanpa perlindungan memori: IBM / 1620 hanya memiliki memori inti - yang dapat Anda inisialisasi melalui keyboard, sehingga Anda harus mengetik beberapa lusinan digit untuk membaca program awal pada kaset berlubang; CAB / 500 memiliki memori drum magnetik; Anda dapat menonaktifkan penulisan beberapa trek melalui sakelar mekanis di dekat drum.

Kemudian, komputer mendapatkan beberapa bentuk unit manajemen memori (MMU) dengan beberapa perlindungan memori. Ada perangkat yang melarang CPU untuk menimpa beberapa jenis memori. Jadi beberapa segmen memori, terutama segmen kode (alias .textsegmen) menjadi hanya-baca (kecuali oleh sistem operasi yang memuatnya dari disk). Itu wajar bagi kompiler dan linker untuk meletakkan string literal dalam segmen kode itu, dan string literal menjadi hanya dibaca. Ketika program Anda mencoba menimpa mereka, itu buruk, perilaku yang tidak terdefinisi . Dan memiliki segmen kode read-only dalam memori virtual memberikan keuntungan yang signifikan: beberapa proses menjalankan program yang sama berbagi RAM yang sama ( memori fisikhalaman) untuk segmen kode itu (lihat MAP_SHAREDflag untuk mmap (2) di Linux).

Saat ini, mikrokontroler murah memiliki beberapa memori read-only (mis. Flash atau ROM), dan menyimpan kode mereka (dan string literal dan konstanta lainnya) di sana. Dan mikroprosesor nyata (seperti yang ada di tablet, laptop atau desktop) memiliki unit manajemen memori yang canggih dan mesin cache yang digunakan untuk memori & paging virtual . Jadi segmen kode dari program yang dapat dieksekusi (misalnya dalam ELF ) adalah memori yang dipetakan sebagai segmen read-only, shareable, dan executable (oleh mmap (2) atau execve (2) di Linux; BTW Anda bisa memberikan arahan ke lduntuk mendapatkan segmen kode yang dapat ditulis jika Anda benar-benar ingin). Menulis atau menyalahgunakannya umumnya merupakan kesalahan segmentasi .

Jadi standar C adalah barok: secara hukum (hanya untuk alasan historis), string literal bukan const char[]array, tetapi hanya char[]array yang dilarang untuk ditimpa.

BTW, beberapa bahasa saat ini mengizinkan string literal untuk ditimpa (bahkan Ocaml yang secara historis - dan buruk - memiliki string literal yang dapat ditulis telah mengubah perilaku itu baru-baru ini di 4.02, dan sekarang memiliki string read-only).

Kompiler C saat ini dapat mengoptimalkan dan memiliki "ions"dan "expressions"berbagi 5 byte terakhir (termasuk terminasi null byte).

Cobalah untuk mengkompilasi kode C Anda dalam file foo.cdengan gcc -O -fverbose-asm -S foo.cdan melihat ke dalam file assembler yang dihasilkan foo.soleh GCC

Akhirnya, semantik C cukup kompleks (baca lebih lanjut tentang CompCert & Frama-C yang mencoba menangkapnya) dan menambahkan string literal konstan yang dapat ditulis akan membuatnya lebih misterius saat membuat program lebih lemah dan bahkan kurang aman (dan dengan lebih sedikit perilaku yang didefinisikan), sehingga sangat tidak mungkin bahwa standar C di masa depan akan menerima string literal yang dapat ditulis. Mungkin sebaliknya mereka akan membuat const char[]array seperti yang seharusnya secara moral.

Perhatikan juga bahwa karena berbagai alasan, data yang dapat berubah lebih sulit ditangani oleh komputer (cache coherency), untuk dikodekan oleh, untuk dipahami oleh pengembang, daripada data konstan. Jadi lebih baik memiliki sebagian besar data Anda (dan terutama string literal) tetap tidak berubah . Baca lebih lanjut tentang paradigma pemrograman fungsional .

Di masa Fortran77 lama di IBM / 7094, bug bahkan dapat mengubah konstanta: jika Anda CALL FOO(1)dan jika FOOkebetulan memodifikasi argumennya dengan merujuk ke 2, implementasi mungkin telah mengubah kejadian lain dari 1 menjadi 2, dan itu benar-benar bug nakal, cukup sulit ditemukan.

Basile Starynkevitch
sumber
Apakah ini untuk melindungi string sebagai konstanta? Meskipun mereka tidak didefinisikan sebagai conststandar ( stackoverflow.com/questions/2245664/… )?
Marius Macijauskas
Apakah Anda yakin komputer pertama tidak memiliki memori hanya baca? Bukankah itu jauh lebih murah daripada ram? Juga, memasukkannya ke dalam memori-RO tidak menyebabkan UB mencoba salah memodifikasi mereka, tetapi mengandalkan OP tidak melakukan itu dan dia melanggar kepercayaan itu. Lihat misalnya program-Fortran di mana semua literal 1tiba-tiba berperilaku seperti 2dan menyenangkan ...
Deduplicator
1
Sebagai seorang remaja di sebuah museum, saya memberi kode pada tahun 1975 pada komputer IBM / 1620 dan CAB500 lama. Tidak ada yang memiliki ROM: IBM / 1620 memiliki memori inti, dan CAB500 memiliki drum magnetik (beberapa trek dapat dinonaktifkan agar dapat ditulis oleh sakelar mekanis)
Basile Starynkevitch
2
Juga patut ditunjukkan: Menempatkan literal dalam segmen kode berarti mereka dapat dibagi di antara banyak salinan program karena inisialisasi terjadi pada waktu kompilasi daripada waktu berjalan.
Blrfl
@Deduplicator Yah, saya telah melihat mesin menjalankan varian BASIC yang memungkinkan Anda untuk mengubah konstanta bilangan bulat (Saya tidak yakin apakah Anda perlu mengelabui itu untuk melakukannya, misalnya lewat argumen "byref" atau jika suatu cara sederhana let 2 = 3berhasil). Ini menghasilkan banyak FUN (dalam definisi kata Dwarf Fortress), tentu saja. Saya tidak tahu bagaimana penerjemah dirancang sehingga memungkinkan ini, tapi itu.
Luaan
2

Compiler tidak dapat menggabungkan "more"dan "regex", karena yang pertama memiliki byte nol setelah esementara yang terakhir memiliki x, tetapi banyak kompiler akan menggabungkan string literal yang cocok dengan sempurna, dan beberapa juga akan mencocokkan string literal yang memiliki ekor yang sama. Kode yang mengubah string string dengan demikian dapat mengubah string string yang berbeda yang digunakan untuk beberapa tujuan yang sama sekali berbeda tetapi kebetulan mengandung karakter yang sama.

Masalah serupa akan muncul dalam FORTRAN sebelum penemuan C. Argumen selalu disampaikan melalui alamat alih-alih berdasarkan nilai. Dengan demikian, rutin untuk menambahkan dua angka sama dengan:

float sum(float *f1, float *f2) { return *f1 + *f2; }

Jika seseorang ingin memberikan nilai konstan (misal 4.0) sum, kompiler akan membuat variabel anonim dan menginisialisasinya 4.0. Jika nilai yang sama diteruskan ke beberapa fungsi, kompiler akan meneruskan alamat yang sama ke semuanya. Sebagai konsekuensinya, jika suatu fungsi yang memodifikasi salah satu parameternya melewati konstanta floating-point, nilai konstanta itu di tempat lain dalam program dapat berubah sebagai hasilnya, sehingga mengarah pada pepatah "Variabel tidak akan; konstanta tidak 't ".

supercat
sumber