Історично (можливо, переписуючи його частини), було навпаки. На перших комп’ютерах початку 1970-х (можливо, PDP-11 ) з прототиповим ембріональним C (можливо, BCPL ) не було ММУ та захисту пам'яті (що існувало на більшості старих мейнфреймів IBM / 360 ). Таким чином , кожні байти пам'яті ( в тому числі і обробках буквених рядків або машинний код) можуть бути перезаписані по помилковою програмі (уявіть собі програму , змінюючи деякі , %
щоб /
в Е () 3 рядок формату). Отже, буквальні рядки та константи були записані.
Будучи підлітком 1975 року, я зашифрував у музеї Palais de la Découverte в Парижі на старих комп’ютерах епохи 1960-х років без захисту пам’яті: IBM / 1620 мав лише основну пам’ять, яку можна було ініціалізувати через клавіатуру, тому вам довелося набрати кілька десятків цифр для читання початкової програми на перфорованих стрічках; CAB / 500 мав магнітну барабанну пам'ять; ви можете відключити запис деяких треків через механічні вимикачі біля барабана.
Пізніше комп'ютери отримали певну форму управління пам'яттю (MMU) з деяким захистом пам'яті. Був пристрій, забороняючи ЦП перезаписувати якусь пам’ять. Так деякі сегменти пам'яті, зокрема сегмент коду (він же .text
сегмент), стали лише для читання (за винятком операційної системи, яка завантажила їх з диска). Було природно, що компілятор і лінкер розміщували прямолінійні рядки в цей кодовий сегмент, і буквальні рядки стали тільки для читання. Коли ваша програма намагалася їх перезаписати, це було погано, невизначеною поведінкою . І наявність у віртуальній пам'яті сегмента коду лише для читання дає значну перевагу: кілька процесів, що працюють в одній програмі, мають однакову оперативну пам'ять ( фізична пам'ятьсторінок) для цього кодового сегмента (див. MAP_SHARED
прапор для mmap (2) в Linux).
Сьогодні дешеві мікроконтролери мають деяку пам'ять, доступну лише для читання (наприклад, їх Flash або ROM) і зберігають там свій код (а також буквальні рядки та інші константи). А справжні мікропроцесори (як у вашому планшетному ПК, ноутбуці чи настільному комп’ютері) мають складний блок управління пам’яттю та кеш- машини, що використовується для віртуальної пам’яті та підкачки . Таким чином, сегмент коду виконуваної програми (наприклад, в ELF ) відображається в пам'яті як сегмент, доступний лише для читання, доступний для виконання, і виконується (за допомогою mmap (2) або execve (2) в Linux; BTW можна давати директиви ldщоб отримати кодовий сегмент, який можна записати, якщо ви цього дуже хотіли). Написання або зловживання ним, як правило, є помилкою сегментації .
Отже стандарт C є бароковим: юридично (лише з історичних причин), буквальні рядки - це не const char[]
масиви, а лише char[]
масиви, які заборонено перезаписувати.
До речі, декілька сучасних мов дозволяють перезаписати рядкові літерали (навіть Ocaml, який історично і погано мав записувані рядки, що записуються, змінив таку поведінку останнім часом у 4.02, і тепер має рядки лише для читання).
Поточні компілятори C здатні оптимізувати та мати "ions"
та "expressions"
ділити останні 5 байтів (включаючи кінцевий байт, що закінчується).
Спробуйте скомпілювати код C в файлі foo.c
з gcc -O -fverbose-asm -S foo.c
і зовнішнім виглядом всередині створеного файлу асемблера foo.s
по нке
Нарешті, семантика C достатньо складна (читайте докладніше про CompCert & Frama -C, які намагаються її захопити), а додавання константних літеральних рядків, що записуються, зробить її ще більш прихованою, зробивши програми слабшими та ще менш безпечними (і з меншою кількістю) визначена поведінка), тому малоймовірно, що майбутні стандарти C приймуть рядкові рядки, що записуються. Можливо, навпаки, вони склали б їх const char[]
масиви так, як вони повинні бути морально.
Зауважте також, що з багатьох причин змінні дані важче обробляються комп'ютером (когерентність кешу), кодувати, розуміти розробником, ніж постійні дані. Тому бажано, щоб більшість ваших даних (і, зокрема, буквальних рядків) залишалися незмінними . Детальніше про парадигму функціонального програмування .
За старих днів Fortran77 на IBM / 7094, помилка могла навіть змінити константу: якщо ви CALL FOO(1)
і, якщо FOO
трапилось, змінити свій аргумент, переданий посиланням на 2, реалізація, можливо, змінила б інші випадки 1 на 2, і це було справді неслухняний клоп, його досить важко знайти.