c - dumped - sigsegv segmentation fault




Warum bekomme ich einen Segmentierungsfehler, wenn ich in einen String schreibe, der mit "char*s" initialisiert wurde, aber nicht mit "char s[]"? (11)

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

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

Das klappt zwar ganz gut:

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

Getestet mit MSVC und GCC.


Warum bekomme ich beim Schreiben in eine Zeichenfolge einen Segmentierungsfehler?

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:

    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 <stdio.h>

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  R E    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.


An erster Stelle ist str ein Zeiger, 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.


Der 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 seg Fehler) ..

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


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.


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


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 Zeiger, 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.


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 Zeiger 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).


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 Zeiger auf diese const Daten. In Ihrem zweiten Beispiel initialisieren Sie ein Array aus 7 Zeichen mit einer Kopie der const-Daten.


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.


// 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. 

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.





c-strings