Warum bekomme ich einen Segmentierungserrors, wenn ich in einen String schreibe, der mit “char * s” initialisiert wurde, aber nicht mit “char s “?

Der folgende Code empfängt seg-Fehler in Zeile 2:

char *str = "string"; str[0] = 'z'; printf("%s\n", str); 

Das klappt zwar ganz gut:

 char str[] = "string"; str[0] = 'z'; printf("%s\n", str); 

Getestet mit MSVC und GCC.

Siehe die C FAQ, Frage 1.32

F : Was ist der Unterschied zwischen diesen Initialisierungen?
char a[] = "string literal";
char *p = "string literal";
Mein Programm stürzt ab, wenn ich p[i] einen neuen Wert zuweisen p[i] .

A : Ein String-Literal (der formale Ausdruck für eine doppelt zitierte Zeichenfolge in C-Quelle) kann auf zwei leicht unterschiedliche Arten verwendet werden:

  1. Als Initialisierer für ein Array von Zeichen, wie in der Deklaration von char a[] , gibt es die Anfangswerte der Zeichen in diesem Array (und, falls erforderlich, seine Größe) an.
  2. Überall sonst wird es zu einem unbenannten, statischen Array von Zeichen, und dieses unbenannte Array kann im Nur-Lese-Speicher gespeichert werden und kann daher nicht unbedingt modifiziert werden. In einem Ausdruck-Kontext wird das Array wie üblich in einen pointers umgewandelt (siehe Abschnitt 6), so initialisiert die zweite Deklaration p, um auf das erste Element des unbenannten Arrays zu zeigen.

Einige Compiler haben einen Schalter, der steuert, ob String-Literale beschreibbar sind oder nicht (zum Kompilieren von altem Code), und einige können Optionen haben, String-Literale formell als Arrays von const char zu behandeln (zum besseren Fehlerabfangen).

Normalerweise werden Zeichenfolgenliterale im schreibgeschützten Speicher gespeichert, wenn das Programm ausgeführt wird. Dies verhindert, dass Sie versehentlich eine Zeichenfolgenkonstante ändern. In Ihrem ersten Beispiel wird "string" im schreibgeschützten Speicher gespeichert und *str zeigt auf das erste Zeichen. Der Fehler tritt auf, wenn Sie versuchen, das erste Zeichen in 'z' zu ändern.

Im zweiten Beispiel wird der String "string" vom Compiler von seinem schreibgeschützten Home in das Array str[] kopiert . Dann ist das Ändern des ersten Zeichens erlaubt. Sie können dies überprüfen, indem Sie die Adresse für jede Adresse ausdrucken:

 printf("%p", str); 

Außerdem zeigt Ihnen das Drucken der Größe von str im zweiten Beispiel, dass der Compiler 7 Byte dafür reserviert hat:

 printf("%d", sizeof(str)); 

Die meisten dieser Antworten sind richtig, aber nur um etwas mehr Klarheit zu geben …

Der “Nur-Lese-Speicher”, auf den sich die Leute beziehen, ist das Textsegment in ASM-Begriffen. Es ist der gleiche Ort im Speicher, an dem die statementen geladen werden. Dies ist nur aus offensichtlichen Gründen wie Sicherheit schreibgeschützt. Wenn Sie ein mit einer Zeichenfolge initialisiertes char * erstellen, werden die Zeichenfolgendaten in das Textsegment kompiliert, und das Programm initialisiert den pointers, um in das Textsegment zu zeigen. Also, wenn Sie versuchen, es zu ändern, Kaboom. Segfault.

Wenn der Compiler als ein Array geschrieben wird, platziert er stattdessen die initialisierten String-Daten in dem Datensegment, welches derselbe Ort ist, an dem Ihre globalen Variablen und dergleichen leben. Dieser Speicher ist veränderbar, da im Datensegment keine statementen vorhanden sind. Dieses Mal, wenn der Compiler das Zeichenarray (das immer noch nur ein Zeichen * ist) initialisiert, zeigt es in das Datensegment und nicht in das Textsegment, das Sie zur Laufzeit ändern können.

Im ersten Code ist “string” eine String-Konstante, und String-Konstanten sollten niemals geändert werden, da sie oft in den Nur-Lese-Speicher gestellt werden. “str” ​​ist ein pointers, der verwendet wird, um die Konstante zu modifizieren.

Im zweiten Code ist “string” ein Array-Initialisierer, eine Art Short-Hand für

 char str[7] = { 's', 't', 'r', 'i', 'n', 'g', '\0' }; 

“str” ​​ist ein Array auf dem Stack und kann frei modifiziert werden.

Warum bekomme ich beim Schreiben in eine Zeichenfolge einen Segmentierungserrors?

C99 N1256 Entwurf

Es gibt zwei völlig unterschiedliche Verwendungen von Array-Literalen:

  1. Initialisiere char[] :

     char c[] = "abc"; 

    Dies ist “mehr Magie” und wird unter 6.7.8 / 14 “Initialisierung” beschrieben :

    Ein Array von Zeichentypen kann durch ein Zeichenkettenliteral initialisiert werden, das optional in geschweifte Klammern eingeschlossen ist. Nachfolgende Zeichen des Zeichenkettenliterals (einschließlich des abschließenden Nullzeichens, wenn Platz vorhanden ist oder wenn das Feld eine unbekannte Größe hat) initialisieren die Elemente des Felds.

    Das ist also nur eine Abkürzung für:

     char c[] = {'a', 'b', 'c', '\0'}; 

    Wie jedes andere reguläre Array kann c modifiziert werden.

  2. Überall sonst: es erzeugt ein:

    • ungenannt
    • Array von char Was ist der Typ von String-Literalen in C und C ++?
    • mit statischem Speicher
    • das gibt UB, wenn geändert

    Also wenn du schreibst:

     char *c = "abc"; 

    Das ist ähnlich zu:

     /* __unnamed is magic because modifying it gives UB. */ static char __unnamed[] = "abc"; char *c = __unnamed; 

    Beachten Sie die implizite Umwandlung von char[] nach char * , was immer zulässig ist.

    Wenn Sie dann c[0] ändern, ändern Sie auch __unnamed , was UB ist.

    Dies ist in 6.4.5 “String-Literale” dokumentiert:

    5 In der Übersetzungsphase 7 wird an jede Multibyte-Zeichenfolge, die aus einem String-Literal oder Literalen resultiert, ein Byte oder ein Code mit dem Wert Null angehängt. Die Multibyte-Zeichensequenz wird dann verwendet, um ein Array von Dauer und Länge des statischen Speichers zu initialisieren, das gerade ausreicht, um die Sequenz zu enthalten. Bei String-Literalen haben die Array-Elemente den Typ char und werden mit den einzelnen Bytes der Multibyte-Zeichenfolge initialisiert. […]

    6 Es ist nicht spezifiziert, ob diese Arrays unterschiedlich sind, vorausgesetzt, ihre Elemente haben die entsprechenden Werte. Wenn das Programm versucht, ein solches Array zu ändern, ist das Verhalten nicht definiert.

6.7.8 / 32 “Initialisierung” gibt ein direktes Beispiel:

Beispiel 8: Die Deklaration

 char s[] = "abc", t[3] = "abc"; 

definiert “plain” char-Array-Objekte s und t deren Elemente mit Zeichenkettenliteralen initialisiert werden.

Diese Deklaration ist identisch mit

 char s[] = { 'a', 'b', 'c', '\0' }, t[] = { 'a', 'b', 'c' }; 

Die Inhalte der Arrays sind modifizierbar. Auf der anderen Seite die Erklärung

 char *p = "abc"; 

definiert p mit dem Typ “pointer to char” und initialisiert es so, dass es auf ein Objekt vom Typ “array of char” mit der Länge 4 zeigt, dessen Elemente mit einem Zeichenkettenliteral initialisiert werden. Wenn versucht wird, p zu verwenden, um den Inhalt des Arrays zu ändern, ist das Verhalten nicht definiert.

GCC 4,8 x86-64 Linux-Implementierung

Lassen Sie uns sehen, warum diese Implementierung defaults.

Programm:

 #include  int main() { char *s = "abc"; printf("%s\n", s); return 0; } 

Kompilieren und dekompilieren:

 gcc -ggdb -std=c99 -c main.c objdump -Sr main.o 

Ausgabe enthält:

  char *s = "abc"; 8: 48 c7 45 f8 00 00 00 movq $0x0,-0x8(%rbp) f: 00 c: R_X86_64_32S .rodata 

Daher wird die Zeichenfolge im Abschnitt .rodata gespeichert.

Dann:

 readelf -l a.out 

Enthält (vereinfacht):

 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x0000000000000704 0x0000000000000704 RE 200000 Section to Segment mapping: Segment Sections... 02 .text .rodata 

Dies bedeutet, dass das Standard-Linker-Skript sowohl .text als auch .rodata in ein Segment .rodata , das ausgeführt, aber nicht geändert werden kann ( Flags = RE ). Der Versuch, ein solches Segment zu ändern, führt zu einem segfault in Linux.

Wenn wir das gleiche für char[] tun:

  char s[] = "abc"; 

wir erhalten:

 17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp) 

%rbp wird es im Stack gespeichert (relativ zu %rbp ) und wir können es natürlich ändern.

Weil der Typ von "whatever" im Kontext des ersten Beispiels const char * (selbst wenn Sie es einem nicht-const char * zuweisen), was bedeutet, dass Sie nicht versuchen sollten, darauf zu schreiben.

Der Compiler hat dies erzwungen, indem er die Zeichenfolge in einen Nur-Lese-Teil des Speichers geschrieben hat, so dass das Schreiben in ihn einen Segfault erzeugt.

Um diesen Fehler oder dieses Problem zu verstehen, sollten Sie zunächst den Unterschied zwischen dem pointers und dem Array kennen, also hier zuerst habe ich Ihnen Unterschiede b / w sie erklären

Zeichenfolgenarray

  char strarray[] = "hello"; 

In Speicher-Array ist in kontinuierlichen Speicherzellen gespeichert, gespeichert als [h][e][l][l][o][\0] =>[] ist 1 Char Byte-Größe Speicherzelle, und diese kontinuierliche Speicherzellen können sein Zugriff durch den Namen namens strarray here.so hier string Array strarray selbst enthält alle Zeichen der string initialisiert werden. In diesem Fall hier "hello" so können wir leicht ändern seinen Speicherinhalt durch den Zugriff auf jedes Zeichen durch seinen Indexwert

 `strarray[0]='m'` it access character at index 0 which is 'h'in strarray 

und sein Wert änderte sich in 'm' so dass der strarray-Wert auf "mello" geändert wurde;

Beachten Sie, dass wir den Inhalt des String-Arrays ändern können, indem wir Zeichen für Zeichen ändern, aber andere Zeichenfolgen nicht direkt initialisieren können wie strarray="new string" ist ungültig

pointers

Da wir alle pointers auf den Speicherort im Speicher wissen, zeigt der nicht initialisierte pointers auf einen zufälligen Speicherort, so dass er nach der Initialisierung auf einen bestimmten Speicherort zeigt

 char *ptr = "hello"; 

Hier wird der pointers ptr auf die Zeichenfolge "hello" initialisiert, die eine konstante Zeichenfolge ist, die im Nur-Lese-Speicher (ROM) gespeichert ist, so dass "hello" nicht geändert werden kann, wie es im ROM gespeichert ist

und ptr wird im Stack-Bereich gespeichert und zeigt auf den konstanten String "hello"

Daher ist ptr [0] = ‘m’ ungültig, da Sie nicht auf den Nur-Lese-Speicher zugreifen können

Aber ptr kann direkt mit einem anderen String-Wert initialisiert werden, da es nur ein pointers ist, so dass es auf jede Speicheradresse der Variablen seines Datentyps zeigen kann

 ptr="new string"; is valid 
 char *str = "string"; 

Das obige setzt str , um auf den Literalwert "string" zu zeigen, der in dem binären Bild des Programms fest codiert ist, das wahrscheinlich als schreibgeschützt im Speicher gekennzeichnet ist.

So versucht str[0]= in den schreibgeschützten Code der Anwendung zu schreiben. Ich würde vermuten, dass dies wahrscheinlich compilerabhängig ist.

 char *str = "string"; 

weist einen pointers auf ein Zeichenfolgenliteral zu, das der Compiler in einen nicht veränderbaren Teil der ausführbaren Datei einfügt;

 char str[] = "string"; 

weist ein lokales Array zu und initialisiert es, das veränderbar ist

Die C-FAQ, die @matli verlinkt, erwähnt es, aber niemand anderes hat es noch, also zur Verdeutlichung: Wenn ein String-Literal (String in doppelten Anführungszeichen in Ihrer Quelle) irgendwo anders verwendet wird als ein Zeichen-Array zu initialisieren (zB: @ Marks zweites Beispiel, das korrekt funktioniert), dieser String wird vom Compiler in einer speziellen statischen String-Tabelle gespeichert, was dem Erstellen einer globalen statischen Variable (nur lesbar) entspricht, die im Wesentlichen anonym ist (hat keine Variable “name “). Der schreibgeschützte Teil ist der wichtige Teil und deshalb wird das erste Codebeispiel des @ Marks segregiert.

Das

  char *str = "string"; 

Zeile definiert einen pointers und verweist auf eine literale Zeichenfolge. Die Literal-Zeichenfolge ist nicht beschreibbar. Wenn Sie also Folgendes tun:

  str[0] = 'z'; 

Sie bekommen einen Seg Fehler. Auf einigen Plattformen befindet sich das Literal möglicherweise in beschreibbarem Speicher, so dass Sie keinen segfault sehen, aber es ist ein ungültiger Code (was zu undefiniertem Verhalten führt).

Die Linie:

 char str[] = "string"; 

weist ein Array von Zeichen zu und kopiert die literale Zeichenfolge in dieses Array, das vollständig beschreibbar ist, so dass die nachfolgende Aktualisierung kein Problem darstellt.

String-Literale wie “string” werden wahrscheinlich im Adressraum Ihrer ausführbaren Datei als schreibgeschützte Daten zugewiesen (geben oder übernehmen Sie Ihren Compiler). Wenn du anfängst, es zu berühren, flippt es aus, dass du in seinem Badeanzugbereich bist und dich mit einem seg-Fehler wissen lässt.

In Ihrem ersten Beispiel erhalten Sie einen pointers auf diese const Daten. In Ihrem zweiten Beispiel initialisieren Sie ein Array aus 7 Zeichen mit einer Kopie der const-Daten.

 // create a string constant like this - will be read only char *str_p; str_p = "String constant"; // create an array of characters like this char *arr_p; char arr[] = "String in an array"; arr_p = &arr[0]; // now we try to change a character in the array first, this will work *arr_p = 'E'; // lets try to change the first character of the string contant *str_p = 'G'; // this will result in a segmentation fault. Comment it out to work. /*----------------------------------------------------------------------------- * String constants can't be modified. A segmentation fault is the result, * because most operating systems will not allow a write * operation on read only memory. *-----------------------------------------------------------------------------*/ //print both strings to see if they have changed printf("%s\n", str_p); //print the string without a variable printf("%s\n", arr_p); //print the string, which is in an array. 

An erster Stelle ist str ein pointers, der auf "string" . Der Compiler darf String-Literale an Stellen im Speicher ablegen, die nicht beschrieben werden können, aber nur gelesen werden können. (Dies hätte eigentlich eine Warnung auslösen sollen, da Sie einem const char * ein const char * zuweisen. Haben Sie Warnungen deaktiviert oder haben Sie sie einfach ignoriert?)

An zweiter Stelle erstellen Sie ein Array, auf das Sie vollen Zugriff haben, und initialisieren es mit "string" . Du erstellst ein char[7] (sechs für die Buchstaben, eins für das abschließende ‘\ 0’), und du machst, was du willst.

Erstens ist eine konstante Zeichenfolge, die nicht geändert werden kann. Zweitens ist ein Array mit initialisiertem Wert, so dass es modifiziert werden kann.

Der Segmentierungserrors wird verursacht, wenn Sie versuchen, auf den Speicher zuzugreifen, auf den nicht zugegriffen werden kann.

char *str ist ein pointers auf eine Zeichenfolge, die nicht geändert werden kann (der Grund für seg Fehler) ..

während char str[] ist ein Array und kann modifizierbar sein ..