Ist es möglich, eine Zeichenfolge in C zu ändern?

80

Ich habe einige Stunden lang mit allen möglichen C-Tutorials und Büchern zu Zeigern zu kämpfen, aber ich möchte wirklich wissen, ob es möglich ist, einen Zeichenzeiger nach seiner Erstellung zu ändern.

Folgendes habe ich versucht:

Gibt es also eine Möglichkeit, die Werte in den Zeichenfolgen anstelle der Zeigeradressen zu ändern?

Matthew Stopa
quelle

Antworten:

157

Wenn Sie eine "Zeichenfolge" in Ihren Quellcode schreiben, wird diese direkt in die ausführbare Datei geschrieben, da dieser Wert beim Kompilieren bekannt sein muss (es stehen Tools zur Verfügung, mit denen Sie Software auseinander ziehen und alle darin enthaltenen Nur-Text-Zeichenfolgen finden können). Wenn Sie schreiben char *a = "This is a string", befindet sich der Speicherort von "Dies ist eine Zeichenfolge" in der ausführbaren Datei, und der Speicherort, auf den averweist, befindet sich in der ausführbaren Datei. Die Daten im ausführbaren Image sind schreibgeschützt.

Was Sie tun müssen (wie die anderen Antworten gezeigt haben), ist, diesen Speicher an einem Ort zu erstellen, der nicht schreibgeschützt ist - auf dem Heap oder im Stapelrahmen. Wenn Sie ein lokales Array deklarieren, wird für jedes Element dieses Arrays Speicherplatz auf dem Stapel erstellt, und das Zeichenfolgenliteral (das in der ausführbaren Datei gespeichert ist) wird in diesen Speicherplatz im Stapel kopiert.

Sie können diese Daten auch manuell kopieren, indem Sie Speicher auf dem Heap zuweisen und dann strcpy()ein Zeichenfolgenliteral in diesen Bereich kopieren.

Wenn Sie Speicherplatz zuweisen, malloc()denken Sie daran, den Anruf zu tätigen, free()wenn Sie damit fertig sind (lesen Sie: Speicherverlust).

Grundsätzlich müssen Sie nachverfolgen, wo sich Ihre Daten befinden. Jedes Mal , wenn Sie eine Zeichenfolge in Ihrer Quelle zu schreiben, dass Zeichenfolge nur gelesen wird (sonst würden Sie möglicherweise das Verhalten der ausführbaren Datei zu ändern werden - sich vorstellen , wenn Sie geschrieben char *a = "hello";und dann geändert , a[0]um 'c'dann woanders geschrieben. printf("hello");Wenn Sie durften das erste ändern. Zeichen von "hello", und Ihr Compiler hat es nur einmal gespeichert (es sollte), dann printf("hello");würde ausgegeben cello!)

Carson Myers
quelle
12
Der letzte Abschnitt hat mir viel erklärt, warum das nur gelesen werden muss. Vielen Dank.
CDR
1
-1: sagt nicht, dass const char * verwendet werden soll, und nichts garantiert, dass Literalzeichenfolgen im ausführbaren Speicher gespeichert werden.
Bastien Léonard
Ich weiß nicht, dass Sie für die beiden von mir angegebenen Lösungen keine Konstante benötigen - auch wenn die Zeichenfolge zur Kompilierungszeit bekannt und in die ausführbare Datei kompiliert ist - wo sonst würde sie gespeichert werden? Wenn ich in gcc entweder char * a = "hallo" schreibe; oder char b [] = "Hallo"; dann gibt die Assembly "LC0: .ascii" Hallo. \ 0 "LC1: .ascii" Hallo. \ 0 "" beide befinden sich im ausführbaren Speicher ... Wann ist es nicht ?
Carson Myers
1
Gerade mit GCC 4.4 versucht, setzt es wörtliche Zeichenfolgen in .rodata (schreibgeschützte Daten). Ich habe mit objdump und der Baugruppenliste nachgesehen. Ich glaube nicht, dass der Standard verlangt, dass wörtliche Zeichenfolgen schreibgeschützt sind, daher denke ich, dass sie sogar in .data eingefügt werden können.
Bastien Léonard
Ich sehe auch keinen Vorteil darin, den Zeiger nicht als const zu qualifizieren. Es kann Fehler verbergen, wenn Sie später entscheiden, die Zeichenfolge zu ändern.
Bastien Léonard
29

Nein, Sie können es nicht ändern, da die Zeichenfolge im Nur-Lese-Speicher gespeichert werden kann. Wenn Sie es ändern möchten, können Sie stattdessen ein Array verwenden, z

Alternativ können Sie Speicher mit malloc zuweisen, z

Jonathan Maddison
quelle
5
Für die Vervollständigung des Codes ist es gut, wenn Sie auch den Aufruf free () hinzufügen können.
Naveen
15

Viele Leute sind verwirrt über den Unterschied zwischen char * und char [] in Verbindung mit String-Literalen in C. Wenn Sie schreiben:

... Sie zeigen foo tatsächlich auf einen konstanten Speicherblock (tatsächlich ist das, was der Compiler in diesem Fall mit "Hallo Welt" macht, implementierungsabhängig.)

Wenn Sie stattdessen char [] verwenden, wird dem Compiler mitgeteilt, dass Sie ein Array erstellen und mit dem Inhalt "Hallo Welt" füllen möchten. foo ist der a-Zeiger auf den ersten Index des char-Arrays. Beide sind Zeichenzeiger, aber nur char [] zeigt auf einen lokal zugewiesenen und veränderlichen Speicherblock.

Jeff Ober
quelle
7

Der Speicher für a & b wird von Ihnen nicht zugewiesen. Dem Compiler steht es frei, einen Nur-Lese-Speicherort zum Speichern der Zeichen auszuwählen. Wenn Sie also versuchen, dies zu ändern, kann dies zu einem Seg-Fehler führen. Ich empfehle Ihnen daher, selbst ein Zeichenarray zu erstellen. Etwas wie:char a[10]; strcpy(a, "Hello");

Naveen
quelle
1
Das Problem mit Zeichenarrays besteht darin, dass ich einen Zeiger eines char-Arrays an eine Funktion übergebe, damit ich dort eine Zeichenfolge bearbeiten und sie dann erneut versenden kann. Sieht so aus, als müsste ich leider malloc benutzen.
Matthew Stopa
1
Nein, Sie können das auf dem Stapel zugewiesene Objekt weiterhin verwenden. Zum Beispiel, wenn Sie eine Funktion void f (char * p) haben; dann können Sie von main () f (a) übergeben. Dadurch wird die Adresse des ersten Zeichens an die Funktion übergeben. Wenn Sie sich für malloc () entscheiden, vergessen Sie nicht, den Speicher mit free () freizugeben.
Naveen
5

Es scheint, als ob Ihre Frage beantwortet wurde, aber jetzt fragen Sie sich vielleicht, warum char * a = "String" im Nur-Lese-Speicher gespeichert ist. Nun, es wird vom c99-Standard tatsächlich nicht definiert, aber die meisten Compiler wählen es auf diese Weise für Instanzen wie:

c99-Standard (pdf) [Seite 130, Abschnitt 6.7.8]:

Die Erklärung:

definiert "einfache" char-Array-Objekte s und t, deren Elemente mit Zeichenfolgenliteralen initialisiert werden. Diese Erklärung ist identisch mit char

Der Inhalt der Arrays kann geändert werden. Auf der anderen Seite die Erklärung

definiert p mit dem Typ "Zeiger auf Zeichen" und initialisiert es so, dass es auf ein Objekt mit dem Typ "Array von Zeichen" mit der Länge 4 zeigt, dessen Elemente mit einem Zeichenfolgenliteral initialisiert werden. Wenn versucht wird, mit p den Inhalt des Arrays zu ändern, ist das Verhalten undefiniert.

Sweeney
quelle
4

Sie könnten auch verwenden strdup:

Für Sie Beispiel:

Maxime Chéramy
quelle
Nicht die Antwort auf die Frage, aber dennoch eine sehr praktische Funktion, danke!
Mknaf
1
+1 für das Unterrichten von mir strdup. Ich bin mir nicht sicher, wann ich es verwenden möchte.
Z Boson
Wenn Sie etwas wie tun var = malloc(strlen(str) + 1); strcpy(var, str);, sollten Sie wahrscheinlich strdupstattdessen verwenden.
Maxime Chéramy
3

Alle sind gute Antworten, die erklären, warum Sie Zeichenfolgenliterale nicht ändern können, weil sie im Nur-Lese-Speicher abgelegt sind. Wenn es jedoch darauf ankommt, gibt es eine Möglichkeit, dies zu tun. Schauen Sie sich dieses Beispiel an:

Ich habe dies als Teil meiner etwas tieferen Gedanken zur Konstanzkorrektheit geschrieben , die Sie vielleicht interessant finden (ich hoffe :)).

Ich hoffe es hilft. Viel Glück!


quelle
Beachten Sie, dass das Ändern eines Zeichenfolgenliteral ein undefiniertes Verhalten ist.
Steohan
0

Sie müssen die Zeichenfolge in einen anderen, nicht schreibgeschützten Speicherpuffer kopieren und dort ändern. Verwenden Sie strncpy () zum Kopieren der Zeichenfolge, strlen () zum Erkennen der Zeichenfolgenlänge, malloc () und free () zum dynamischen Zuweisen eines Puffers für die neue Zeichenfolge.

Zum Beispiel (C ++ wie Pseudocode):

scharfer Zahn
quelle
0
Nathan Fellman
quelle
6
Das Malloc benötigt 1 weiteres Byte. Vergessen Sie nicht das NULL-Abschlusszeichen, das strcpy erwartet und das auch kopiert wird. Dies ist ein allzu häufiger Fehler.
Xcramps