Der folgende Code empfängt einen Seg-Fehler in Zeile 2:
char *str = "string";
str[0] = 'z'; // could be also written as *str = 'z'
printf("%s\n", str);
Während dies perfekt funktioniert:
char str[] = "string";
str[0] = 'z';
printf("%s\n", str);
Getestet mit MSVC und GCC.
c
segmentation-fault
c-strings
Markus
quelle
quelle
Antworten:
Siehe die C-FAQ, Frage 1.32
quelle
mprotect
der Nur-Lese-Schutz verwendet wird (siehe hier) ).Normalerweise werden String-Literale beim Ausführen des Programms im Nur-Lese-Speicher gespeichert. Dies soll verhindern, dass Sie versehentlich eine Zeichenfolgenkonstante ändern. In Ihrem ersten Beispiel
"string"
wird im Nur-Lese-Speicher gespeichert und*str
zeigt auf das erste Zeichen. Der Segfault tritt auf, wenn Sie versuchen, das erste Zeichen in zu ändern'z'
.Im zweiten Beispiel wird die Zeichenfolge
"string"
wird kopiert vom Compiler aus seiner schreibgeschützten Heimat diestr[]
Array. Dann ist das Ändern des ersten Zeichens zulässig. Sie können dies überprüfen, indem Sie jeweils die Adresse ausdrucken:Wenn Sie
str
im zweiten Beispiel die Größe von drucken, wird angezeigt, dass der Compiler 7 Byte zugewiesen hat:quelle
%zu
zu druckensize_t
Die meisten dieser Antworten sind richtig, aber nur um ein bisschen mehr Klarheit zu schaffen ...
Der "Nur-Lese-Speicher", auf den sich die Benutzer beziehen, ist das Textsegment in ASM-Begriffen. Es ist dieselbe Stelle im Speicher, an der die Anweisungen geladen werden. Dies ist aus offensichtlichen Gründen wie der Sicherheit schreibgeschützt. Wenn Sie ein Zeichen * erstellen, das für eine Zeichenfolge initialisiert wurde, werden die Zeichenfolgendaten in das Textsegment kompiliert und das Programm initialisiert den Zeiger so, dass er in das Textsegment zeigt. Also, wenn Sie versuchen, es zu ändern, Kaboom. Segfault.
Wenn der Compiler als Array geschrieben wird, platziert er die initialisierten Zeichenfolgendaten stattdessen im Datensegment. Dies ist derselbe Ort, an dem Ihre globalen Variablen und dergleichen leben. Dieser Speicher ist veränderbar, da das Datensegment keine Anweisungen enthält. Wenn der Compiler dieses Mal das Zeichenarray initialisiert (das immer noch nur ein Zeichen * ist), zeigt er auf das Datensegment und nicht auf das Textsegment, das Sie zur Laufzeit sicher ändern können.
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
.Wenn wir dasselbe tun für
char[]
:wir erhalten:
so wird es im Stapel gespeichert (relativ zu
%rbp
).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:
quelle
Im ersten Code ist "string" eine String-Konstante, und String-Konstanten sollten niemals geändert werden, da sie häufig im Nur-Lese-Speicher abgelegt werden. "str" ist ein Zeiger, mit dem die Konstante geändert wird.
Im zweiten Code ist "string" ein Array-Initialisierer, eine Art Abkürzung für
"str" ist ein Array, das dem Stapel zugewiesen ist und frei geändert werden kann.
quelle
str
global oderstatic
.Weil der Typ
"whatever"
im Kontext des 1. Beispiels istconst char *
(auch wenn Sie ihn einem nicht konstanten Zeichen * zuweisen), sollten Sie nicht versuchen, darauf zu schreiben.Der Compiler hat dies erzwungen, indem er die Zeichenfolge in einen schreibgeschützten Teil des Speichers gestellt hat. Daher wird durch das Schreiben in die Zeichenfolge ein Segfault generiert.
quelle
Um diesen Fehler oder dieses Problem zu verstehen, sollten Sie zuerst den Unterschied zwischen Zeiger und Array kennen. Hier habe ich Ihnen zunächst die Unterschiede zwischen ihnen erklärt
String-Array
Das Speicherarray wird in fortlaufenden Speicherzellen gespeichert, die als
[h][e][l][l][o][\0] =>[]
Speicherzelle mit einer Größe von 1 Zeichenbyte gespeichert sind, und auf diese fortlaufenden Speicherzellen kann hier mit dem Namen strarray zugegriffen werden. Hier enthält das Zeichenfolgenarraystrarray
selbst alle Zeichen der darauf initialisierten Zeichenfolge In diesem Fall"hello"
können wir den Speicherinhalt leicht ändern, indem wir auf jedes Zeichen über seinen Indexwert zugreifenund sein Wert wurde geändert,
'm'
so dass sich der Strarray-Wert in änderte"mello"
;Ein Punkt, der hier zu beachten ist, dass wir den Inhalt des String-Arrays ändern können, indem wir Zeichen für Zeichen ändern, aber andere Zeichenfolgen nicht direkt wie folgt initialisieren können
strarray="new string"
es ungültig istZeiger
Wie wir alle wissen, zeigt der Zeiger auf den Speicherort im Speicher, der nicht initialisierte Zeiger auf den zufälligen Speicherort und nach der Initialisierung auf einen bestimmten Speicherort
Hier wird der Zeiger ptr auf einen String initialisiert, bei
"hello"
dem es sich um einen konstanten String handelt, der im Nur-Lese-Speicher (ROM)"hello"
gespeichert ist und daher nicht geändert werden kann, da er im ROM gespeichert istund ptr wird im Stapelabschnitt gespeichert und zeigt auf eine konstante Zeichenfolge
"hello"
Daher ist ptr [0] = 'm' ungültig, da Sie nicht auf den Nur-Lese-Speicher zugreifen können
Ptr kann jedoch direkt mit einem anderen Zeichenfolgenwert initialisiert werden, da es nur ein Zeiger ist, sodass es auf eine beliebige Speicheradresse einer Variablen seines Datentyps verweisen kann
quelle
Die obigen
str
Angaben verweisen auf den Literalwert"string"
der im Binärbild des Programms fest codiert ist und im Speicher wahrscheinlich als schreibgeschützt gekennzeichnet ist.Es
str[0]=
wird also versucht, in den schreibgeschützten Code der Anwendung zu schreiben. Ich würde vermuten, dass dies wahrscheinlich vom Compiler abhängig ist.quelle
weist einem Zeichenfolgenliteral einen Zeiger zu, den der Compiler in einen nicht modifizierbaren Teil Ihrer ausführbaren Datei einfügt;
weist ein lokales Array zu und initialisiert es, das geändert werden kann
quelle
int *b = {1,2,3)
wie wir schreibenchar *s = "HelloWorld"
?In den C-FAQs, mit denen @matli verknüpft ist, wird es erwähnt, aber noch niemand hier. Zur Verdeutlichung: Wenn ein Zeichenfolgenliteral (Zeichenfolge in doppelten Anführungszeichen in Ihrer Quelle) an einer anderen Stelle als zum Initialisieren eines Zeichenarrays verwendet wird (dh: @ Marks zweites Beispiel, das korrekt funktioniert, diese Zeichenfolge wird vom Compiler in einer speziellen statischen Zeichenfolgentabelle gespeichert , die dem Erstellen einer globalen statischen Variablen (natürlich schreibgeschützt) ähnelt, die im Wesentlichen anonym ist (keinen Variablennamen hat) "). Der schreibgeschützte Teil ist der wichtige Teil, weshalb das erste Codebeispiel von @ Mark fehlerhaft ist.
quelle
int *b = {1,2,3)
wie wir schreibenchar *s = "HelloWorld"
?Das
line definiert einen Zeiger und zeigt auf eine Literalzeichenfolge. Die Literalzeichenfolge ist nicht beschreibbar, wenn Sie Folgendes tun:
Sie erhalten einen Seg-Fehler. Auf einigen Plattformen befindet sich das Literal möglicherweise im beschreibbaren Speicher, sodass kein Segfault angezeigt wird, der Code jedoch ungültig ist (was zu undefiniertem Verhalten führt).
Die Linie:
Weist ein Array von Zeichen zu und kopiert die Literalzeichenfolge in dieses Array, das vollständig beschreibbar ist, sodass die nachfolgende Aktualisierung kein Problem darstellt.
quelle
int *b = {1,2,3)
wie wir schreibenchar *s = "HelloWorld"
?String-Literale wie "string" werden wahrscheinlich im Adressraum Ihrer ausführbaren Datei als schreibgeschützte Daten zugewiesen (geben oder nehmen Sie Ihren Compiler). Wenn Sie es berühren, wird Ihnen klar, dass Sie sich im Badeanzugbereich befinden, und Sie werden über einen Seg-Fehler informiert.
In Ihrem ersten Beispiel erhalten Sie einen Zeiger auf diese konstanten Daten. In Ihrem zweiten Beispiel initialisieren Sie ein Array mit 7 Zeichen mit einer Kopie der const-Daten.
quelle
quelle
In erster Linie
str
ist ein Zeiger, der auf zeigt"string"
. Der Compiler darf Zeichenfolgenliterale an Stellen im Speicher ablegen, in die Sie nicht schreiben, sondern nur lesen können. (Dies hätte wirklich eine Warnung auslösen sollen, da Sie aconst char *
einem zuweisenchar *
. Hatten Sie Warnungen deaktiviert oder haben Sie sie einfach ignoriert?)Zweitens erstellen Sie ein Array, bei dem es sich um Speicher handelt, auf den Sie vollen Zugriff haben, und initialisieren es mit
"string"
. Sie erstellen einechar[7]
(sechs für die Buchstaben, eine für die abschließende '\ 0') und machen damit, was Sie wollen.quelle
const
Präfix machen Variablen schreibgeschütztchar [N]
nichtconst char [N]
, daher gibt es keine Warnung. (Sie können das in gcc zumindest durch Passieren ändern-Wwrite-strings
.)Angenommen, die Zeichenfolgen sind,
Im ersten Fall ist das Literal zu kopieren, wenn 'a' in den Geltungsbereich kommt. Hier ist 'a' ein Array, das auf einem Stapel definiert ist. Dies bedeutet, dass die Zeichenfolge auf dem Stapel erstellt wird und ihre Daten aus dem Code- (Text-) Speicher kopiert werden, der normalerweise schreibgeschützt ist (dies ist implementierungsspezifisch, ein Compiler kann diese schreibgeschützten Programmdaten auch im schreibgeschützten Speicher ablegen ).
Im zweiten Fall ist p ein Zeiger, der auf dem Stapel definiert ist (lokaler Bereich) und auf ein Zeichenfolgenliteral (Programmdaten oder Text) verweist, das an anderer Stelle gespeichert ist. Normalerweise wird das Ändern eines solchen Speichers weder empfohlen noch empfohlen.
quelle
Erstens ist eine konstante Zeichenfolge, die nicht geändert werden kann. Zweitens ist ein Array mit initialisiertem Wert, so dass es geändert werden kann.
quelle
Ein Segmentierungsfehler wird verursacht, wenn Sie versuchen, auf den Speicher zuzugreifen, auf den nicht zugegriffen werden kann.
char *str
ist ein Zeiger auf eine Zeichenfolge, die nicht geändert werden kann (der Grund für den Erhalt von Segfault).wohingegen
char str[]
ist ein Array und kann modifizierbar sein ..quelle