In C kann man ein String-Literal in einer Deklaration wie dieser verwenden:
char s[] = "hello";
oder so:
char *s = "hello";
Was ist der Unterschied? Ich möchte wissen, was tatsächlich in Bezug auf die Speicherdauer passiert, sowohl beim Kompilieren als auch zur Laufzeit.
Antworten:
Der Unterschied hier ist das
wird
"Hello world"
in den schreibgeschützten Teilen des Speichers platziert , unds
wenn Sie einen Zeiger darauf setzen, ist jede Schreiboperation in diesem Speicher unzulässig.Währenddessen:
Setzt die Literalzeichenfolge in den Nur-Lese-Speicher und kopiert die Zeichenfolge in den neu zugewiesenen Speicher auf dem Stapel. So machen
legal.
quelle
"Hello world"
befindet sich in beiden Beispielen in "schreibgeschützten Teilen des Speichers". Das Beispiel mit dem Array zeigt dort, das Beispiel mit dem Array kopiert die Zeichen in die Array-Elemente.char msg[] = "hello, world!";
die Zeichenfolge enthält, die im initialisierten Datenabschnitt landet. Wenn deklariertchar * const
, dass es im schreibgeschützten Datenbereich landet. gcc-4.5.3Zunächst einmal sind sie in Funktionsargumenten genau gleichwertig:
In anderen Kontexten
char *
wird ein Zeigerchar []
zugewiesen , während ein Array zugewiesen wird. Wohin geht die Saite im ersteren Fall, fragen Sie? Der Compiler weist heimlich ein statisches anonymes Array zu, das das String-Literal enthält. Damit:Beachten Sie, dass Sie niemals versuchen dürfen, den Inhalt dieses anonymen Arrays über diesen Zeiger zu ändern. Die Effekte sind undefiniert (was oft einen Absturz bedeutet):
Durch die Verwendung der Array-Syntax wird sie direkt einem neuen Speicher zugewiesen. Somit ist eine Änderung sicher:
Das Array lebt jedoch nur so lange wie sein kontanierender Bereich. Wenn Sie dies in einer Funktion tun, geben Sie keinen Zeiger auf dieses Array zurück oder verlieren Sie es - erstellen Sie stattdessen eine Kopie mit
strdup()
oder ähnlichem. Wenn das Array im globalen Bereich zugeordnet ist, ist dies natürlich kein Problem.quelle
Diese Erklärung:
Erstellt ein Objekt - ein
char
Array der Größe 6, dass
mit den Werten initialisiert wird'h', 'e', 'l', 'l', 'o', '\0'
. Wo dieses Array im Speicher zugeordnet ist und wie lange es lebt, hängt davon ab, wo die Deklaration angezeigt wird. Wenn sich die Deklaration innerhalb einer Funktion befindet, bleibt sie bis zum Ende des Blocks bestehen, in dem sie deklariert ist, und wird mit ziemlicher Sicherheit auf dem Stapel zugewiesen. Wenn es sich außerhalb einer Funktion befindet, wird es wahrscheinlich in einem "initialisierten Datensegment" gespeichert, das beim Ausführen des Programms aus der ausführbaren Datei in den beschreibbaren Speicher geladen wird.Auf der anderen Seite diese Erklärung:
Erstellt zwei Objekte:
char
s, das die Werte enthält'h', 'e', 'l', 'l', 'o', '\0'
, keinen Namen hat und eine statische Speicherdauer hat (was bedeutet, dass es für die gesamte Lebensdauer des Programms gültig ist); unds
Zeichen , aufgerufen , die mit der Position des ersten Zeichens in diesem unbenannten schreibgeschützten Array initialisiert wird.Das unbenannte schreibgeschützte Array befindet sich normalerweise im "Text" -Segment des Programms. Dies bedeutet, dass es zusammen mit dem Code selbst von der Festplatte in den Nur-Lese-Speicher geladen wird. Die Position der
s
Zeigervariablen im Speicher hängt davon ab, wo die Deklaration angezeigt wird (genau wie im ersten Beispiel).quelle
char s[] = "hello"
Fall ist das"hello"
nur ein Initialisierer, der dem Compiler mitteilt, wie das Array initialisiert werden soll. Dies kann zu einer entsprechenden Zeichenfolge im Textsegment führen oder nicht. Wenn beispielsweises
eine statische Speicherdauer vorliegt, ist es wahrscheinlich, dass sich die einzige Instanz von"hello"
im initialisierten Datensegment befindet - dem Objekts
selbst. Selbst wenns
die Speicherdauer automatisch beträgt, kann sie durch eine Folge von Literalspeichern und nicht durch eine Kopie (z. B.movl $1819043176, -6(%ebp); movw $111, -2(%ebp)
) initialisiert werden ..rodata
das das Linker-Skript dann in dasselbe Segment wie.text
. Siehe meine Antwort .char s[] = "Hello world";
die Literalzeichenfolge im Nur-Lese-Speicher abgelegt und die Zeichenfolge in den neu zugewiesenen Speicher auf dem Stapel kopiert wird. Ihre Antwort spricht jedoch nur über die wörtliche Zeichenfolge, die im Nur-Lese-Speicher abgelegt ist, und überspringt den zweiten Teil des Satzes, der besagt :copies the string to newly allocated memory on the stack
. Ist Ihre Antwort unvollständig, weil Sie den zweiten Teil nicht angegeben haben?char s[] = "Hellow world";
nur ein Initialisierer und wird nicht unbedingt als separate schreibgeschützte Kopie gespeichert. Wenns
eine statische Speicherdauer vorliegt, befindet sich die einzige Kopie der Zeichenfolge wahrscheinlich in einem Lese- / Schreibsegment an der Position vons
, und selbst wenn dies nicht der Fall ist, kann der Compiler das Array mit Anweisungen zum sofortigen Laden oder ähnlichem initialisieren, anstatt zu kopieren von einer schreibgeschützten Zeichenfolge. Der Punkt ist, dass in diesem Fall die Initialisierungszeichenfolge selbst keine Laufzeitpräsenz hat.Angesichts der Erklärungen
Nehmen Sie die folgende hypothetische Speicherkarte an:
Das String-Literal
"hello world"
ist ein 12-Elemente-Array vonchar
(const char
in C ++) mit statischer Speicherdauer. Dies bedeutet, dass der Speicher dafür beim Start des Programms zugewiesen wird und bis zum Beenden des Programms zugewiesen bleibt. Der Versuch, den Inhalt eines Zeichenfolgenliteral zu ändern, führt zu undefiniertem Verhalten.Die Linie
definiert
s0
als Zeiger aufchar
mit automatischer Speicherdauer (dh die Variables0
existiert nur für den Bereich, in dem sie deklariert ist) und kopiert die Adresse des Zeichenfolgenliteral (0x00008000
in diesem Beispiel) in diese. Beachten Sie, dass das0
Punkte zu einem Stringliteral, ist es nicht als Argument für jede Funktion verwendet werden soll , die es zu ändern versuchen würden ( zum Beispielstrtok()
,strcat()
,strcpy()
, etc.).Die Linie
definiert
s1
als ein 12-Elemente-Array vonchar
(Länge wird aus dem Zeichenfolgenliteral entnommen) mit automatischer Speicherdauer und kopiert den Inhalt des Literal in das Array. Wie Sie der Speicherzuordnung entnehmen können, haben wir zwei Kopien der Zeichenfolge"hello world"
. Der Unterschied besteht darin, dass Sie die in enthaltene Zeichenfolge ändern könnens1
.s0
unds1
sind in den meisten Kontexten austauschbar; Hier sind die Ausnahmen:Sie können die Variable neu zuweisen
s0
, um auf ein anderes Zeichenfolgenliteral oder eine andere Variable zu verweisen. Sie können die Variable nicht neu zuweisens1
, um auf ein anderes Array zu verweisen.quelle
C99 N1256 Entwurf
Es gibt zwei verschiedene Verwendungen von Zeichenkettenliteralen:
Initialisieren
char[]
:Dies ist "mehr Magie" und wird unter 6.7.8 / 14 "Initialisierung" beschrieben:
Dies ist also nur eine Abkürzung für:
c
Kann wie jedes andere reguläre Array geändert werden.Überall sonst: es erzeugt ein:
Also, wenn Sie schreiben:
Dies ist ähnlich wie:
Beachten Sie die implizite Besetzung von
char[]
bischar *
, die immer legal ist.Wenn Sie dann ändern
c[0]
, ändern Sie auch__unnamed
, was UB ist.Dies ist unter 6.4.5 "String-Literale" dokumentiert:
6.7.8 / 32 "Initialisierung" gibt ein direktes Beispiel:
GCC 4.8 x86-64 ELF-Implementierung
Programm:
Kompilieren und dekompilieren:
Die Ausgabe enthält:
Fazit: GCC speichert
char*
es in.rodata
Abschnitt, nicht in.text
.Beachten Sie jedoch , dass der Standard Linker - Skript setzt
.rodata
und.text
im gleichen Segment , das auszuführen hat , aber keine Schreibrechte. Dies kann beobachtet werden mit:was beinhaltet:
Wenn wir dasselbe tun für
char[]
:wir erhalten:
so wird es im Stapel gespeichert (relativ zu
%rbp
).quelle
deklariert
s
, dass es sich um ein Array handelt,char
das lang genug ist, um den Initialisierer (5 + 1char
s) aufzunehmen, und initialisiert das Array durch Kopieren der Elemente des angegebenen Zeichenfolgenliteral in das Array.deklariert
s
, ein Zeiger auf ein oder mehrere (in diesem Fall mehrere)char
s zu sein und zeigt direkt auf eine feste (schreibgeschützte) Stelle, die das Literal enthält"hello"
.quelle
s
ist ein Zeiger aufconst char
.Hier
s
ist eine Reihe von Zeichen, die auf Wunsch überschrieben werden können.Ein Zeichenfolgenliteral wird verwendet, um diese Zeichenblöcke irgendwo im Speicher zu erstellen, auf den dieser Zeiger
s
zeigt. Wir können hier das Objekt, auf das es zeigt, neu zuweisen, indem wir es ändern, aber solange es auf ein Zeichenfolgenliteral zeigt, kann der Zeichenblock, auf den es zeigt, nicht geändert werden.quelle
Beachten Sie außerdem, dass Sie für schreibgeschützte Zwecke die Verwendung von beiden identisch sind. Sie können auf ein Zeichen zugreifen, indem Sie entweder mit
[]
oder im*(<var> + <index>)
Format indizieren :Und:
Offensichtlich, wenn Sie versuchen, dies zu tun
Sie werden wahrscheinlich einen Segmentierungsfehler erhalten, wenn Sie versuchen, auf den Nur-Lese-Speicher zuzugreifen.
quelle
x[1] = 'a';
dem, bei dem auch ein Fehler auftritt (natürlich abhängig von der Plattform).Nur um hinzuzufügen: Sie erhalten auch unterschiedliche Werte für ihre Größen.
Wie oben erwähnt, wird für ein Array
'\0'
das letzte Element zugewiesen.quelle
Die obigen Sätze str zeigen auf den Literalwert "Hello", der im Binärbild des Programms fest codiert ist, das im Speicher als schreibgeschützt gekennzeichnet ist. Dies bedeutet, dass jede Änderung in diesem String-Literal unzulässig ist und Segmentierungsfehler verursachen würde.
kopiert die Zeichenfolge in den neu zugewiesenen Speicher auf dem Stapel. Änderungen daran sind daher zulässig und legal.
ändert die str in "Mello".
Für weitere Details gehen Sie bitte die ähnliche Frage durch:
Warum erhalte ich einen Segmentierungsfehler, wenn ich in eine Zeichenfolge schreibe, die mit "char * s", aber nicht mit "char s []" initialisiert wurde?
quelle
Im Falle des:
x ist ein l-Wert - er kann zugewiesen werden. Aber im Fall von:
x ist kein l-Wert, es ist ein r-Wert - Sie können ihm keinen Wert zuweisen.
quelle
x
ist ein nicht modifizierbarer Wert. In fast allen Kontexten wird jedoch ein Zeiger auf sein erstes Element ausgewertet, und dieser Wert ist ein r-Wert.quelle
In Anbetracht der Kommentare hier sollte es offensichtlich sein, dass: char * s = "hallo"; Ist eine schlechte Idee und sollte in sehr engem Umfang verwendet werden.
Dies könnte eine gute Gelegenheit sein, darauf hinzuweisen, dass "konstante Korrektheit" eine "gute Sache" ist. Wann und wo immer Sie können, verwenden Sie das Schlüsselwort "const", um Ihren Code vor "entspannten" Anrufern oder Programmierern zu schützen, die normalerweise am "entspanntesten" sind, wenn Zeiger ins Spiel kommen.
Genug Melodram, hier ist, was man erreichen kann, wenn man Zeiger mit "const" schmückt. (Hinweis: Sie müssen Zeigerdeklarationen von rechts nach links lesen.) Hier sind die drei verschiedenen Möglichkeiten, sich beim Spielen mit Zeigern zu schützen:
- das heißt, das DBJ-Objekt kann nicht über p geändert werden.
- Das heißt, Sie können das DBJ-Objekt über p ändern, aber Sie können den Zeiger p selbst nicht ändern.
- Das heißt, Sie können weder den Zeiger p selbst noch das DBJ-Objekt über p ändern.
Die Fehler im Zusammenhang mit versuchten Konstantenmutationen werden zur Kompilierungszeit abgefangen. Es gibt keinen Laufzeitraum oder Geschwindigkeitsverlust für const.
(Angenommen, Sie verwenden natürlich den C ++ - Compiler?)
- DBJ
quelle