Welche Vorteile haben schreibgeschützte String-Literale, die Folgendes rechtfertigen (-ies / -ied):
Noch eine andere Art, sich in den Fuß zu schießen
char *foo = "bar"; foo[0] = 'd'; /* SEGFAULT */
Unfähigkeit, ein Lese-Schreib-Array von Wörtern in einer Zeile elegant zu initialisieren:
char *foo[] = { "bar", "baz", "running out of traditional placeholder names" }; foo[1][2] = 'n'; /* SEGFAULT */
Die Sprache selbst komplizieren.
char *foo = "bar"; char var[] = "baz"; some_func(foo); /* VERY DANGEROUS! */ some_func(var); /* LESS DANGEROUS! */
Speicher sparen? Ich habe irgendwo gelesen (konnte den Quellcode jetzt nicht finden), dass Compiler vor langer Zeit, als RAM knapp war, versuchten, die Speichernutzung durch Zusammenführen ähnlicher Zeichenfolgen zu optimieren.
Beispielsweise würden "more" und "regex" zu "moregex". Trifft dies auch heute noch zu, im Zeitalter digitaler Filme in Blu-ray-Qualität? Ich verstehe, dass eingebettete Systeme immer noch in Umgebungen mit eingeschränkten Ressourcen arbeiten, aber der verfügbare Speicher hat sich dramatisch erhöht.
Kompatibilitätsprobleme? Ich gehe davon aus, dass ein Legacy-Programm, das versucht, auf den Nur-Lese-Speicher zuzugreifen, entweder abstürzt oder mit einem unentdeckten Fehler fortfährt. Daher sollte kein Legacy-Programm versuchen, auf String-Literal zuzugreifen, und daher würde das Schreiben in String-Literal keinen Schaden für gültige, nicht-hackische, portable Legacy-Programme anrichten .
Gibt es noch andere Gründe? Ist meine Argumentation falsch? Wäre es sinnvoll, eine Änderung der Lese- / Schreib-Zeichenfolgenliterale in neuen C-Standards in Betracht zu ziehen oder dem Compiler zumindest eine Option hinzuzufügen? Wurde dies vorher in Betracht gezogen oder sind meine "Probleme" zu gering und unbedeutend, um jemanden zu stören?
Antworten:
Historisch (vielleicht durch Umschreiben von Teilen) war es das Gegenteil. Auf den allerersten Computern der frühen 1970er Jahre (möglicherweise PDP-11 ), auf denen ein prototypisches embryonales C (möglicherweise BCPL ) ausgeführt wurde, gab es keine MMU und keinen Speicherschutz (der auf den meisten älteren IBM / 360- Mainframes vorhanden war). Also jeder Byte Speicher (einschließlich der Handhabung Literalzeichenfolgen oder Maschinencode) könnte durch ein fehlerhaftes Programm überschrieben werden (man stelle mir ein Programm einige Wechsel
%
zu/
in einem printf (3) Format - String). Daher waren wörtliche Zeichenfolgen und Konstanten beschreibbar.Als Teenager habe ich 1975 im Museum Palais de la Découverte in Paris auf Computern ohne Speicherschutz aus den alten 1960er Jahren codiert: IBM / 1620 verfügte nur über einen Kernspeicher, den Sie über die Tastatur initialisieren konnten, sodass Sie mehrere Dutzend eingeben mussten von Ziffern zum Lesen des Anfangsprogramms auf Lochbändern; CAB / 500 hatte einen magnetischen Trommelspeicher; Sie könnten das Schreiben einiger Spuren durch mechanische Schalter in der Nähe der Trommel deaktivieren.
Später erhielten Computer eine Speicherverwaltungseinheit (Memory Management Unit, MMU) mit einem gewissen Speicherschutz. Es gab ein Gerät, das der CPU verbot, eine Art Speicher zu überschreiben. Einige Speichersegmente, insbesondere das Codesegment (auch bekannt als
.text
Segment), waren schreibgeschützt (mit Ausnahme des Betriebssystems, das sie von der Festplatte geladen hat). Es war für den Compiler und den Linker selbstverständlich, die Literalzeichenfolgen in dieses Codesegment einzufügen, und Literalzeichenfolgen wurden schreibgeschützt. Als Ihr Programm versuchte, sie zu überschreiben, war dies ein undefiniertes Verhalten . Ein schreibgeschütztes Codesegment im virtuellen Speicher bietet einen erheblichen Vorteil: Mehrere Prozesse, auf denen dasselbe Programm ausgeführt wird, teilen sich denselben RAM ( physischen Speicher)Seiten) für dieses Codesegment (sieheMAP_SHARED
Flag für mmap (2) unter Linux).Heutzutage haben billige Mikrocontroller einen Nur-Lese-Speicher (z. B. Flash oder ROM) und behalten dort ihren Code (und die wörtlichen Zeichenfolgen und anderen Konstanten). Reale Mikroprozessoren (wie der in Ihrem Tablet, Laptop oder Desktop) verfügen über eine ausgeklügelte Speicherverwaltungseinheit und Cache- Maschinen, die für virtuellen Speicher und Paging verwendet werden . Das Codesegment des ausführbaren Programms (z. B. in ELF ) ist also ein Speicher, der als schreibgeschütztes, gemeinsam nutzbares und ausführbares Segment abgebildet wird (gemäß mmap (2) oder execve (2) unter Linux; übrigens können Sie ld Anweisungen gebenum ein beschreibbares Codesegment zu erhalten, wenn Sie es wirklich wollten). Das Schreiben oder Missbrauchen ist im Allgemeinen ein Segmentierungsfehler .
Der C-Standard ist also barock: Literal-Strings sind legal (nur aus historischen Gründen) keine
const char[]
Arrays, sondern nurchar[]
Arrays, deren Überschreiben verboten ist.Übrigens erlauben nur wenige aktuelle Sprachen das Überschreiben von Zeichenfolgenliteralen (selbst Ocaml, das historisch - und schlecht - beschreibbare Zeichenfolgen hatte, hat dieses Verhalten kürzlich in 4.02 geändert und enthält jetzt schreibgeschützte Zeichenfolgen).
Aktuelle C - Compiler sind in der Lage zu optimieren und haben
"ions"
und"expressions"
ihre letzten 5 Bytes (einschließlich des abschließenden Null - Byte) teilen.Versuchen Sie, Ihren C-Code in der Datei
foo.c
mit zu kompilierengcc -O -fverbose-asm -S foo.c
und in derfoo.s
von GCC generierten Assembler-Datei nachzuschauenSchließlich ist die Semantik von C komplex genug (lesen Sie mehr über CompCert & Frama-C, die versuchen, sie zu erfassen), und das Hinzufügen von beschreibbaren Konstanten-Literal-Strings würde sie noch arkaner machen, während Programme schwächer und noch weniger sicher (und mit weniger Sicherheit) werden Daher ist es sehr unwahrscheinlich, dass zukünftige C-Standards beschreibbare wörtliche Zeichenfolgen akzeptieren. Vielleicht würden sie sie im Gegenteil zu
const char[]
Arrays machen, wie sie moralisch sein sollten.Beachten Sie auch, dass es aus vielen Gründen für den Computer schwieriger ist, veränderbare Daten zu handhaben (Cache-Kohärenz), für die der Entwickler Code schreiben kann, als für konstante Daten. Daher ist es vorzuziehen, dass die meisten Ihrer Daten (und insbesondere die wörtlichen Zeichenfolgen) unveränderlich bleiben . Lesen Sie mehr über funktionale Programmierung Paradigma .
In den alten Fortran77 Tage auf IBM / 7094 könnte ein Fehler sogar eine Konstante ändern: Wenn Sie
CALL FOO(1)
und wennFOO
passiert sein Argument durch Bezugnahme auf 2 geführt zu ändern, haben die Durchführung andere Vorkommen von 1 in 2 geändert könnten, und das war ein wirklich ungezogener Bug, ziemlich schwer zu finden.quelle
const
im Standard definiert sind ( stackoverflow.com/questions/2245664/… )?1
s plötzlich wie2
s verhalten und so viel Spaß machen ...let 2 = 3
. Dies führte natürlich zu viel SPASS (in der Zwergenfestungsdefinition des Wortes). Ich habe keine Ahnung, wie der Dolmetscher dafür ausgelegt war, aber es war so.Compiler konnte nicht kombinieren
"more"
und"regex"
, weil das erstere ein Null - Byte hat , nachdem diee
während letztere eine hatx
, aber viele Compiler würde Stringliterale , die perfekt aufeinander abgestimmt kombinieren, und einige würden auch Stringliterale übereinstimmen , die einen gemeinsamen Schwanz geteilt. Code, der ein Zeichenfolgenliteral ändert, kann daher ein anderes Zeichenfolgenliteral ändern, das für einen ganz anderen Zweck verwendet wird, aber zufällig dieselben Zeichen enthält.Ein ähnliches Problem trat in FORTRAN vor der Erfindung von C auf. Argumente wurden immer nach Adresse und nicht nach Wert übergeben. Eine Routine zum Hinzufügen von zwei Zahlen wäre also äquivalent zu:
Für den Fall, dass man einen konstanten Wert (zB 4.0) übergeben
sum
möchte, erstellt der Compiler eine anonyme Variable und initialisiert diese auf4.0
. Wenn derselbe Wert an mehrere Funktionen übergeben würde, würde der Compiler allen dieselbe Adresse übergeben. Wenn einer Funktion, die einen ihrer Parameter geändert hat, eine Gleitkommakonstante übergeben wird, kann sich folglich der Wert dieser Konstante an einer anderen Stelle im Programm ändern. Dies führt zu der Meldung "Variablen werden nicht; Konstanten sind nicht vorhanden" 't ".quelle