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 (zobacz MAP_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 jedynie char[]
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
z gcc -O -fverbose-asm -S foo.c
i zajrzeć do wnętrza wygenerowanego pliku asemblera foo.s
przez GCC
W 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śli FOO
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.