Jakie zalety literałów łańcuchowych jako tylko do odczytu uzasadniają (-ies / -ied):
To kolejny sposób na zastrzelenie się w stopę
char *foo = "bar"; foo[0] = 'd'; /* SEGFAULT */
Niemożność eleganckiego zainicjowania tablicy słów do odczytu i zapisu w jednym wierszu:
char *foo[] = { "bar", "baz", "running out of traditional placeholder names" }; foo[1][2] = 'n'; /* SEGFAULT */
Komplikowanie samego języka.
char *foo = "bar"; char var[] = "baz"; some_func(foo); /* VERY DANGEROUS! */ some_func(var); /* LESS DANGEROUS! */
Zapisujesz pamięć? Czytałem gdzieś (nie mogłem znaleźć źródła) już dawno temu, kiedy brakowało pamięci RAM, kompilatory próbowały zoptymalizować wykorzystanie pamięci, łącząc podobne ciągi.
Na przykład „more” i „regex” stają się „moregex”. Czy nadal tak jest w dzisiejszych czasach w erze cyfrowych filmów Blu-ray? Rozumiem, że systemy wbudowane nadal działają w środowisku o ograniczonych zasobach, ale ilość dostępnej pamięci dramatycznie wzrosła.
Problemy ze zgodnością? Zakładam, że starszy program, który próbowałby uzyskać dostęp do pamięci tylko do odczytu, zawiesiłby się lub kontynuował z nieodkrytym błędem. W związku z tym żaden starszy program nie powinien próbować uzyskać dostępu do literału łańcuchowego, a zatem zezwolenie na zapis do literału łańcuchowego nie zaszkodzi prawidłowym, niehackalnym, przenośnym starszym programom.
Czy są jakieś inne powody? Czy moje rozumowanie jest nieprawidłowe? Czy rozsądne byłoby rozważenie zmiany literałów ciągowych do odczytu i zapisu w nowych standardach C lub przynajmniej dodanie opcji do kompilatora? Czy rozważano to wcześniej, czy też moje „problemy” były zbyt małe i nieistotne, aby komuś przeszkadzać?
Odpowiedzi:
Historycznie (być może przez przepisanie jej części) było odwrotnie. Na pierwszych komputerach z początku lat 70. (być może PDP-11 ) z prototypowym embrionalnym C (być może BCPL ) nie było MMU ani ochrony pamięci (która istniała na większości starszych komputerów mainframe IBM / 360 ). Więc każdy bajt pamięci (w tym obsługi dosłowne ciągi lub kodu maszynowego) może być zastąpiony przez błędną programu (wyobrazić program zmieniający niektóre
%
się/
w printf () 3 ciąg formatu). Stąd dosłowne ciągi znaków i stałe były zapisywalne.Jako nastolatek w 1975 r. Kodowałem w paryskim muzeum Palais de la Découverte na starych komputerach z lat 60. bez ochrony pamięci: IBM / 1620 miał tylko pamięć rdzeniową - którą można zainicjować za pomocą klawiatury, więc trzeba było wpisać kilkadziesiąt cyfr do odczytu początkowego programu na perforowanych taśmach; CAB / 500 miał magnetyczną pamięć bębna; można wyłączyć zapisywanie niektórych ścieżek za pomocą przełączników mechanicznych w pobliżu bębna.
Później komputery otrzymały jakąś formę jednostki zarządzania pamięcią (MMU) z pewną ochroną pamięci. Było urządzenie zabraniające CPU nadpisywania jakiejś pamięci. Tak więc niektóre segmenty pamięci, w szczególności segment kodu (inaczej
.text
segment) stały się tylko do odczytu (z wyjątkiem systemu operacyjnego, który załadował je z dysku). Kompilator i linker w naturalny sposób umieścili literalne ciągi w tym segmencie kodu, a ciągi literalne stały się tylko do odczytu. Gdy twój program próbował je zastąpić, było to złe, niezdefiniowane zachowanie . A posiadanie segmentu kodu tylko do odczytu w pamięci wirtualnej daje znaczącą przewagę: kilka procesów uruchomionych w tym samym programie ma tę samą pamięć RAM ( pamięć fizycznastron) dla tego segmentu kodu (zobaczMAP_SHARED
flagę dla mmap (2) w systemie Linux).Obecnie tanie mikrokontrolery mają pamięć tylko do odczytu (np. Flash lub ROM) i przechowują tam swój kod (oraz dosłowne ciągi znaków i inne stałe). A prawdziwe mikroprocesory (takie jak ten na tablecie, laptopie lub komputerze stacjonarnym) mają wyrafinowaną jednostkę zarządzania pamięcią i mechanizm pamięci podręcznej wykorzystywany do wirtualnej pamięci i stronicowania . Tak więc segment kodu programu wykonywalnego (np. W ELF ) jest mapowany w pamięci jako segment tylko do odczytu, współdzielony i wykonywalny (przez mmap (2) lub execve (2) w Linuksie; BTW możesz podać dyrektywy dla ldaby uzyskać zapisywalny segment kodu, jeśli naprawdę tego chcesz). Pisanie lub nadużywanie jest ogólnie błędem segmentacji .
Tak więc standard C jest barokowy: prawnie (tylko z powodów historycznych) dosłowne ciągi znaków nie są
const char[]
tablicami, a jedyniechar[]
tablicami, których nadpisywanie jest zabronione.BTW, kilka obecnych języków pozwala na zastąpienie literałów ciągów (nawet Ocaml, który historycznie - i źle - miał napisalne ciągi literalne, zmienił to zachowanie ostatnio w 4.02, a teraz ma ciągi tylko do odczytu).
Obecne kompilatory C są w stanie zoptymalizować i mieć
"ions"
i"expressions"
współużytkować swoje ostatnie 5 bajtów (w tym kończący bajt zerowy).Spróbuj skompilować kod C w pliku
foo.c
zgcc -O -fverbose-asm -S foo.c
i zajrzeć do wnętrza wygenerowanego pliku asemblerafoo.s
przez GCCW końcu semantyka języka C jest wystarczająco złożona (czytaj więcej o CompCert i Frama-C, które próbują go uchwycić), a dodanie zapisywalnych ciągów literałów sprawi, że będzie jeszcze bardziej tajemny, a programy słabsze i jeszcze mniej bezpieczne (i przy mniej zdefiniowane zachowanie), więc jest bardzo mało prawdopodobne, aby przyszłe standardy C akceptowały zapisywalne ciągi literalne. Być może wręcz przeciwnie,
const char[]
stworzą z nich tablice takie, jakie powinny być moralnie.Zauważ też, że z wielu powodów zmienne dane są trudniejsze do obsługi przez komputer (spójność pamięci podręcznej), do kodowania, do zrozumienia przez programistę, niż stałe dane. Dlatego lepiej, aby większość twoich danych (a zwłaszcza ciągów literalnych) pozostała niezmienna . Przeczytaj więcej o paradygmacie programowania funkcjonalnego .
W dawnych czasach Fortran77 na IBM / 7094 błąd mógł nawet zmienić stałą: jeśli ty
CALL FOO(1)
i jeśliFOO
zdarzy ci się zmodyfikować jego argument przekazany przez odniesienie do 2, implementacja może zmienić inne wystąpienia 1 na 2, a to było naprawdę niegrzeczny robak, dość trudny do znalezienia.źródło
const
w standardzie ( stackoverflow.com/questions/2245664/... )?1
nagle zachowują się jak te2
i taka zabawa ...let 2 = 3
działał). Spowodowało to oczywiście dużo ZABAWY (w definicji słowa Dwarf Fortress). Nie mam pojęcia, jak zaprojektowano tłumacza, że na to pozwala, ale tak było.Kompilatory nie mógł połączyć
"more"
i"regex"
, ponieważ pierwszy ma zerowy bajt poe
drugi natomiast max
, ale wiele kompilatorów łączyłby literały ciągów znaków, które idealnie dopasowane, a niektóre również dopasować literały ciągów że mieli wspólnego ogon. Kod, który zmienia literał łańcucha, może zatem zmienić inny literał łańcucha, który jest używany do zupełnie innych celów, ale zawiera te same znaki.Podobny problem pojawiłby się w FORTRAN przed wynalezieniem C. Argumenty zawsze przekazywane były przez adres, a nie przez wartość. Procedura dodawania dwóch liczb byłaby zatem równoważna z:
W przypadku, gdy chcemy przekazać stałą wartość (np. 4.0)
sum
, kompilator utworzy zmienną anonimową i zainicjuje ją4.0
. Jeśli ta sama wartość zostałaby przekazana do wielu funkcji, kompilator przekazałby ten sam adres do wszystkich z nich. W rezultacie, jeśli funkcja, która zmodyfikowała jeden z jej parametrów, otrzyma stałą zmiennoprzecinkową, wartość tej stałej w innym miejscu programu może zostać w wyniku tego zmieniona, co prowadzi do powiedzenia „Zmienne nie będą; stałe nie są „t”.źródło