C string değişmezleri neden salt okunur?


29

Dize değişmezlerin salt okunur olmasının avantajları: (-ies / -ied):

  1. Kendini ayağından vurmanın başka bir yolu

    char *foo = "bar";
    foo[0] = 'd'; /* SEGFAULT */
  2. Okuma-yazma sözcük dizisini tek bir satırda zarif bir şekilde başlatamamak:

    char *foo[] = { "bar", "baz", "running out of traditional placeholder names" };
    foo[1][2] = 'n'; /* SEGFAULT */ 
  3. Dilin kendisini karmaşıklaştırmak.

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

Hafızadan tasarruf Bir yerde okudum (şimdi kaynağı bulamadım), çok uzun zaman önce, RAM az olduğu zaman, derleyiciler benzer dizeleri birleştirerek bellek kullanımını optimize etmeye çalıştılar.

Örneğin, "more" ve "regex", "moregex" olur. Dijital blu-ray kalitesinde filmler çağında bugün hala bu doğru mudur? Gömülü sistemlerin hala sınırlı kaynaklar ortamında çalıştığını biliyorum, ancak yine de kullanılabilir bellek miktarı önemli ölçüde arttı.

Uyumluluk sorunları? Salt okunur belleğe erişmeye çalışan eski bir programın çökeceğini veya keşfedilmemiş bir hatayla devam edeceğini varsayıyorum. Bu nedenle, hiçbir eski program dizgenin değişmezine erişmeye çalışmamalıdır ve bunun için dizginin değişmezine yazmaya izin vermek geçerli, kesilmeyen, taşınabilir eski programlara zarar vermeyecektir .

Başka sebepler var mı? Akıl yürütmem yanlış mı? Yeni C standartlarında okuma-yazma dizgisi değişmezlerinde bir değişiklik düşünmek veya en azından derleyiciye bir seçenek eklemek makul olur mu? Bu daha önce değerlendirilmiş miydi ya da "sorunlarım" hiç kimseyi rahatsız etmeyecek kadar küçük ve önemsiz mi?


12
Dize değişmezlerin derlenmiş kodda nasıl göründüğünü incelediğinizi varsayıyorum ?

2
Sağladığım bağlantının içerdiği düzene bak. Tam orada.

8
"Moregex" örneğiniz boş sonlandırma nedeniyle işe yaramaz.
dan04

4
Sabitleri yazmak istemiyorsunuz çünkü bu onların değerini değiştirecek. Bir dahaki sefere aynı sabit kullanmak istediğinizde farklı olurdu. Derleyici / çalışma zamanı, sabitleri bir yerden kaynaklamalıdır ve nerede olursanız olun değiştirmenize izin verilmemelidir.
Erik Eidt

1
“Dize değişmezleri RAM belleğinde değil program belleğinde saklanır ve arabellek taşması programın bozulmasına neden olur mu?” Program görüntüsü de RAM'de. Kesin olmak gerekirse, dize değişmezleri, program görüntüsünü saklamak için kullanılan aynı RAM segmentinde saklanır. Ve evet, dizgenin üzerine yazmak programı bozabilir. MS-DOS ve CP / M günlerinde hafıza koruması yoktu, böyle şeyler yapabilirdiniz ve bu genellikle korkunç sorunlara neden oluyordu. İlk PC virüsleri, programınızı değiştirmek için hileler kullanır, böylece çalıştırmayı denediğinizde sabit sürücünüzü biçimlendirirdi.
Charles E. Grant,

Yanıtlar:


40

Tarihsel (belki de bölümlerini yeniden yazarak), tam tersi oldu. Prototipik bir embriyonik C (belki BCPL ) çalıştıran 1970'lerin başındaki ilk bilgisayarlarda (belki de PDP-11 ) , MMU ve hafıza koruması yoktu (ki bunlar daha eski IBM / 360 ana bilgisayarlarında mevcuttu). (Literal dizeleri veya makine işleme kodu dahil) bellek her bayt hatalı program tarafından üzerine yazılır olabilir Yani (bazı değişen bir program hayal etmek bir de printf (3) biçim dizesi). Bu nedenle, değişmez dizeler ve sabitler yazılabilirdi.%/

1975’teki bir genç olarak, 1960’lı yılların başlarında Paris’teki Palais de la Découverte müzesinde hafıza koruması olmayan bilgisayarlarda kod yazdım: IBM / 1620’nin sadece bir çekirdek hafızası vardı; Delikli bantlar üzerindeki ilk programı okumak için rakam; CAB / 500 bir manyetik tambur belleğine sahipti; Tamburun yakınındaki mekanik anahtarlardan bazı parçalar yazmayı devre dışı bırakabilirsiniz.

Daha sonra, bilgisayarlar bir miktar hafıza korumasına sahip bir tür hafıza yönetim birimi (MMU) aldı. İşlemcinin bir tür belleğin üzerine yazmasını yasaklayan bir cihaz vardı. Bu nedenle, bazı hafıza bölümleri, özellikle de kod bölümü (aka .textsegment) salt okunur hale geldi (diskten yüklenen işletim sistemi hariç). Derleyici ve bağlayıcının hazırlayıcı dizeleri bu kod segmentine koyması doğaldı ve yalnızca hazırlayıcı diziler salt okunur hale geldi. Programınız üzerine yazmaya çalıştığında, tanımsız bir davranış olarak kötüydü . Ve sanal bellekte salt okunur bir kod parçasına sahip olmak önemli bir avantaj sağlar: aynı programı çalıştıran birkaç işlem aynı RAM’i paylaşır ( fiziksel bellekbu kod bölümü için sayfalar ( Linux'ta mmap (2)MAP_SHARED işaretine bakınız ).

Bugün, ucuz mikrodenetleyiciler bazı salt okunur belleğe (örn. Flash veya ROM) sahiptir ve kodlarını (ve değişmez dizeleri ve diğer sabitleri) orada tutarlar. Ve gerçek mikroişlemciler (tabletinizdeki, dizüstü bilgisayarınızdaki veya masaüstünüzdeki gibi) gelişmiş bir bellek yönetim birimine ve sanal bellek ve çağrı için kullanılan önbellek makinelerine sahiptir . Bu nedenle çalıştırılabilir programın (örneğin ELF'deki ) kod bölümü salt okunur, paylaşılabilir ve çalıştırılabilir bir bölüm olarak ( mmap (2) veya Linux'ta çalıştırma (2) ; BTW ld) için direktifler verebilirsiniz.eğer gerçekten istersen yazılabilir bir kod parçası almak için). Yazma veya kötüye kullanma genellikle bir segmentasyon hatasıdır .

Bu yüzden C standardı baroktur: yasal olarak (sadece tarihsel nedenlerden dolayı), edebi karakter dizileri const char[]diziler değildir , sadece char[]üzerine yazmak yasaktır.

BTW, birkaç mevcut dilde, dize değişmezlerinin üzerine yazılmasına izin verilir (tarihsel olarak -veya da kötü şekilde yazılabilir değişmez dizgelere sahip olan Ocaml bile, son zamanlarda 4.02'de bu davranışı değiştirmiştir ve şimdi salt-okunur dizeler vardır).

Mevcut C derleyicileri son 5 baytı (sonlandırıcı boş bayt dahil) en iyi duruma getirebilir "ions"ve "expressions"paylaşabilir.

Dosyasında C kodunu derlemek için deneyin foo.cile gcc -O -fverbose-asm -S foo.coluşturulan montajcı dosyası içinde ve görünüm foo.starafından GCC

Sonunda , C'nin semantiği yeterince karmaşıktır ( onu yakalamaya çalışan CompCert ve Frama-C hakkında daha fazla bilgi edinin ) ve yazılabilir sabit değişmez dizgelerin eklenmesi, programları daha zayıf ve daha az güvenli hale getirirken (daha az) tanımlanmış davranış), bu nedenle gelecekteki C standartlarının yazılabilir değişmez karakterleri kabul etmesi pek olası değildir. Belki de tam tersine, onları const char[]ahlaki olarak olması gerektiği gibi diziler haline getireceklerdir .

Ayrıca, birçok nedenden ötürü, değişken verilerin bilgisayar tarafından ele alınması (önbellek tutarlılığı), geliştirici tarafından anlaşılması için sabit verilerden daha zor olduğunu unutmayın. Bu nedenle, verilerinizin çoğunun (ve özellikle değişmez dizgelerin) değişmez kalması tercih edilir . İşlevsel programlama paradigması hakkında daha fazla bilgi edinin .

IBM / 7094'teki eski Fortran77 günlerinde, bir hata bir sabiti bile değiştirebilirdi: siz CALL FOO(1)ve eğer FOO2'ye atıfta bulunulan argümanı değiştirirseniz, uygulama 1'den 2'ye kadar olan diğer oluşumları değiştirmiş olabilirdi, ve bu gerçekten de bir gerçekti. yaramaz böcek, bulmak oldukça zor.


Bu, dizeleri sabit olarak korumak için mi? constStandart olarak tanımlanmadıkları halde ( stackoverflow.com/questions/2245664/… )?
Marius Macijauskas

İlk bilgisayarların salt okunur belleği olmadığından emin misin ? Ram'dan çok daha ucuz değil miydi? Ayrıca, onları RO-belleğe koymak UB'nin onları hatalı bir şekilde değiştirmeye çalışmasına neden olmaz, ancak OP'nin bunu yapmamasına ve bu güveni ihlal etmesine güvenmesine neden olur. Örneğin, tüm edebi 1kelimelerin aniden 2s gibi davrandığı Fortran programlarına bakın ...
Deduplicator

1
Bir müzede gençken, 1975'te eski IBM / 1620 ve CAB500 bilgisayarlarına kodladım. Ne herhangi ROM vardı: IBM / 1620 çekirdek bellek vardı ve CAB500 manyetik davul (bazı parçalar mekanik bir anahtar tarafından yazılabilir olması engelli olabilir) vardı
Basile Starynkevitch

2
Ayrıca dikkat edilmesi gerekenler: Hazır kodları kod segmentine koymak, programın birden çok kopyası arasında paylaşılabileceği anlamına gelir; çünkü başlatma, çalışma zamanı yerine derleme zamanında gerçekleşir.
Blrfl

@Deduplicator Eh, tamsayı sabitleri değiştirmenize izin veren bir BASIC varyantını çalıştıran bir makine gördüm (bunu yapmak için kandırmanız gerekip gerekmediğinden emin değilim, örneğin "byref" argümanlarını iletmek veya basit bir işlemden geçirilmişse let 2 = 3). Bu elbette birçok FUN (cüce Kalesi tanımı) ile sonuçlandı. Tercümanın buna izin verecek şekilde nasıl tasarlandığı hakkında hiçbir fikrim yok, ama öyleydi.
Luaan

2

Derleyiciler bir araya gelemedi "more"ve "regex"birincisi bir esüre sonra boş bir bayta sahip olduğundan, ikincisi bir süre geçirdi x, ancak birçok derleyici mükemmel eşleşen bir dizi hazır bilgiyi birleştirdi ve bazıları da ortak bir kuyruğu paylaşan dizgi hazır bilgisiyle eşleşecekti. Bir dize değişmezini değiştiren kod, bu nedenle, tamamen farklı bir amaç için kullanılan ancak aynı karakterleri içeren bir dize değişmezini değiştirebilir.

Benzer bir mesele, FORTRAN'da C'nin icadından önce ortaya çıkacaktı. Argümanlar her zaman değer yerine adrese iletildi. İki sayı eklemek için yapılan bir rutin bu nedenle:

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

Birinin sabit bir değer (örneğin 4.0) iletmek istemesi durumunda sum, derleyici isimsiz bir değişken yaratacak ve onu başlatacaktır 4.0. Aynı değer birden fazla fonksiyona geçirilirse, derleyici hepsine aynı adresi iletir. Sonuç olarak, parametrelerinden birini değiştiren bir işlev bir kayan nokta sabitinden geçirilirse, programın herhangi bir yerinde bu sabitin değeri sonuç olarak değişebilir ve “Değişkenler olmaz; 't".

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.