Warum bekomme ich einen Segmentierungsfehler beim Schreiben in eine mit "char *s" initiierte Zeichenfolge, aber nicht "char S []?

StackOverflow https://stackoverflow.com/questions/164194

  •  03-07-2019
  •  | 
  •  

Frage

Der folgende Code empfängt den 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 vollkommen gut funktioniert:

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

Getestet mit MSVC und GCC.

War es hilfreich?

Lösung

Siehe die C -FAQ, Frage 1.32

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

EIN: Ein Saitenliteral (der formale Begriff für eine doppelt zitierte Zeichenfolge in C-Quelle) kann auf zwei leicht unterschiedliche Arten verwendet werden:

  1. Als Initialisierer für eine Reihe von Char, wie in der Erklärung von char a[] Es gibt die Anfangswerte der Zeichen in diesem Array (und gegebenenfalls seine Größe) an.
  2. Überall sonst wird es zu einem unbenannten, statischen Array von Zeichen, und dieses unbenannte Array kann im schreibgeschützten Speicher gespeichert werden und das daher nicht unbedingt geändert werden kann. In einem Ausdruckskontext wird das Array wie gewohnt sofort in einen Zeiger konvertiert (siehe Abschnitt 6), sodass die zweite Deklaration P initialisiert, um auf das erste Element des ungenannten Arrays zu verweisen.

Einige Compiler haben einen Schalter, der steuert, ob String -Literale beschreibbar sind oder nicht (zum Kompilieren alter Code), und einige haben möglicherweise Optionen, um String -Literale formell als Arrays von const char zu behandeln (für ein besseres Fehlerfang).

Andere Tipps

Normalerweise werden String-Literale im schreibgeschützten Speicher gespeichert, wenn das Programm ausgeführt wird. Dies soll Sie daran hindern, eine Saitenkonstante versehentlich zu ändern. In Ihrem ersten Beispiel, "string" wird im schreibgeschützten Speicher gespeichert und *str zeigt auf den ersten Charakter. Der Segfault tritt auf, wenn Sie versuchen, den ersten Charakter an zu ändern 'z'.

Im zweiten Beispiel die Zeichenfolge "string" ist kopiert durch den Compiler von seiner schreibgeschützten Heimat bis zur str[] Array. Dann ist das Ändern des ersten Charakters zulässig. Sie können dies überprüfen, indem Sie die Adresse von jedem drucken:

printf("%p", str);

Auch das Drucken der Größe von str Im zweiten Beispiel zeigt Ihnen, dass der Compiler 7 Bytes dafür zugewiesen hat:

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

Die meisten dieser Antworten sind korrekt, aber nur um etwas mehr Klarheit hinzuzufügen ...

Die "Nur -Speicher -Speicher", auf die sich Menschen beziehen, ist das Textsegment in ASM -Begriffen. Es ist der gleiche Ort im Speicher, an dem die Anweisungen geladen werden. Dies ist nur schreibgeschützt aus offensichtlichen Gründen wie Sicherheit. Wenn Sie eine in einer Zeichenfolge initiierte Zeichen erstellen, werden die Zeichenfolgedaten in das Textsegment zusammengestellt und das Programm initialisiert den Zeiger, um in das Textsegment zu verweisen. Wenn Sie also versuchen, es zu ändern, Kaboom. Segfault.

Wenn der Compiler als Array geschrieben wurde, platziert er die initialisierten Zeichenfolgedaten stattdessen in das Datensegment. Dieser Speicher ist veränderlich, da es im Datensegment keine Anweisungen gibt. Diesmal, wenn der Compiler das Zeichenarray initialisiert (das immer noch nur ein Zeichen*ist), zeigt es in das Datensegment und nicht in das Textsegment, das Sie zur Laufzeit sicher ändern können.

Warum bekomme ich beim Schreiben in eine Zeichenfolge einen Segmentierungsfehler?

C99 N1256 Entwurf

Es gibt zwei verschiedene Verwendungszwecke von Charakter -String -Literalen:

  1. Initialisieren char[]:

    char c[] = "abc";      
    

    Dies ist "mehr Magie" und beschrieben bei 6.7.8/14 "Initialisierung":

    Ein Array von Charaktertyp kann durch eine Zeichenstringliteral initialisiert werden, die optional in Zahnspangen eingeschlossen ist. Aufeinanderfolgende Zeichen des Charakters String Literal (einschließlich des terminierenden Nullzeichen, wenn Platz vorhanden ist oder wenn das Array von unbekannter Größe ist) initialisieren die Elemente des Arrays.

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

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

    Wie jedes andere reguläre Array, c kann geändert werden.

  2. Überall sonst: Es erzeugt ein:

    Also, wenn du schreibst:

    char *c = "abc";
    

    Dies ähnelt:

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

    Beachten Sie die implizite Besetzung von char[] zu char *, was immer legal ist.

    Dann, wenn Sie ändern c[0], Sie ändern auch __unnamed, was ub ist.

    Dies ist bei 6.4.5 "String Literals" dokumentiert:

    5 In der Translationsphase 7 wird ein Byte oder ein Wert von Wert Null an jede Multibyte -Zeichensequenz angehängt, die aus einem String -Literal oder Literalen resultiert. Die Multibyte -Zeichensequenz wird dann verwendet, um ein Array von statischen Speicherdauer und Länge zu initialisieren, die ausreichend ausreichen, um die Sequenz zu enthalten. Für Zeichenstring -Literale haben die Array -Elemente Typen -Zeichen und werden mit den einzelnen Bytes der Multibyte -Zeichensequenz initialisiert [...

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

6.7.8/32 "Initialisierung" gibt ein direktes Beispiel:

Beispiel 8: Die Deklaration

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

definiert "einfache" char Array -Objekte s und t deren Elemente mit Charakter -String -Literalen initialisiert werden.

Diese Erklärung ist identisch mit

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

Der Inhalt der Arrays ist verändert. Andererseits die Erklärung

char *p = "abc";

Definiert p Mit dem Typ "Zeiger auf char" und initialisiert es so, dass sie auf ein Objekt mit Typ "Array von char" mit Länge 4 verweist, dessen Elemente mit einem Zeichen für Zeichenfolge initialisiert werden. Wenn ein Versuch unternommen wird, p Um den Inhalt des Arrays zu ändern, ist das Verhalten undefiniert.

GCC 4,8 X86-64 ELF-Implementierung

Programm:

#include <stdio.h>

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

Kompilieren und zersetzen:

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

Schlussfolgerung: GCC Stores char* es in .rodata Abschnitt nicht in .text.

Wenn wir dasselbe für tun char[]:

 char s[] = "abc";

wir erhalten:

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

Also wird es im Stapel gespeichert (relativ zu %rbp).

Beachten Sie jedoch, dass das Standard -Linker -Skript einsetzt .rodata und .text im selben Segment, der ausgeführt hat, aber keine Schreibberechtigung. Dies kann beobachtet werden mit:

readelf -l a.out

was beinhaltet:

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata

Im ersten Code ist "String" eine String -Konstante, und String -Konstanten sollten niemals geändert werden, da sie häufig in das Les -Nur -Speicher gelegt werden. "Str" ist ein Zeiger, der verwendet wird, um die Konstante zu modifizieren.

Im zweiten Code ist "String" ein Array -Initialisierer, eine Art kurze Hand für

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

"Str" ist ein Array, das auf dem Stapel zugewiesen wurde und frei geändert werden kann.

Weil die Art von "whatever" Im Kontext des 1. Beispiels ist const char * (Auch wenn Sie es einem nicht konstanten Char*zuweisen), was bedeutet, dass Sie nicht versuchen sollten, daran zu schreiben.

Der Compiler hat dies erzwungen, indem der Zeichenfolge in einen schreibgeschützten Teil des Speichers gestellt wird und damit ein Segfault geschrieben wird.

Um diesen Fehler oder Problem zu verstehen, sollten Sie zuerst den Unterschied b/w der Zeiger und Array kennen. Zuerst habe ich Ihnen die Unterschiede b/w sie erklären

String -Array

 char strarray[] = "hello";

Im Speicherarray wird in kontinuierlichen Gedächtniszellen gespeichert, gespeichert als [h][e][l][l][o][\0] =>[] IS 1 char Byte Größengedächtniszelle, und diese kontinuierlichen Speicherzellen können hier mit dem Namen Strarrray zugreifen. So hier String -Array strarray selbst enthält alle Zeichen der String initialisiert. In diesem Fall hier "hello"So können wir seinen Speicherinhalt leicht ändern, indem wir auf jeden Charakter mit seinem Indexwert zugreifen

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

und sein Wert änderte sich in 'm' Also änderte sich der Strarrray -Wert in "mello";

Ein Punkt, den wir hier beachten können, dass wir den Inhalt des String -Arrays durch Ändern des Zeichens durch Zeichen ändern können, aber andere Zeichenfolge nicht direkt darauf initialisieren können, wie strarray="new string" ist ungültig

Zeiger

Wie wir alle wissen, zeigt Zeiger auf den Speicherort im Speicher, nicht initialisierte Zeiger verweist auf den zufälligen Speicherort so und nach der Initialisierungspunkte auf einen bestimmten Speicherort

char *ptr = "hello";

Hier wird Zeiger PTR auf String initialisiert "hello" Dies ist konstante Zeichenfolge, die in read nur Speicher (ROM) gespeichert ist "hello" kann nicht geändert werden, da es in ROM gespeichert ist

und PTR wird im Stapelabschnitt gespeichert und zeigt auf eine konstante Zeichenfolge "hello"

PTR [0] = 'M' ist also ungültig, da Sie nicht auf den nur Speicher auf den Lese -Lese -Speicher zugreifen können

PTR kann jedoch direkt auf einen anderen String -Wert initialisiert werden

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

Die obigen Sätze str auf den wörtlichen Wert hinweisen "string" Das ist im Binärbild des Programms hart codiert, das wahrscheinlich als schreibgeschützt im Speicher gekennzeichnet ist.

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

char *str = "string";

Zuweilt einen Zeiger einem String-Literal, den der Compiler einen nicht modifizierbaren Teil Ihrer ausführbaren Datei einsetzt.

char str[] = "string";

Zuweist und initialisiert ein lokales Array, das veränderbar ist

Die C-FAQ, die @matli mit der Erwähnung verknüpft hat, aber noch niemand hier hat es noch zur Klärung: Wenn ein String buchstäblich (doppelt zitierte Zeichenfolge in Ihrer Quelle) überall verwendet wird außer So initialisieren Statische String -Tabelle, das ähnelt der Erstellung einer globalen statischen Variablen (natürlich schreibgeschützt), die im Wesentlichen anonym ist (hat keinen variablen "Namen"). Das schreibgeschützt Teil ist der wichtige Teil und der Grund, warum der erste Code -Beispiel des @marks Segfaults.

Das

 char *str = "string";

Linie definiert einen Zeiger und zeigt ihn auf eine wörtliche Saite. Die wörtliche Saite ist nicht beschreibbar. Wenn Sie also:

  str[0] = 'z';

Sie bekommen einen SEG -Fehler. Auf einigen Plattformen befindet sich das wörtliche möglicherweise in einem beschreibbaren Speicher, sodass Sie keinen Segfault sehen, aber es ist unabhängig von einem ungültigen Code (was zu undefiniertem Verhalten führt).

Die Linie:

char str[] = "string";

verteilt eine Reihe von Charakteren und Kopien Die buchstäbliche Zeichenfolge in dieses Array, das vollständig beschreibbar ist, ist daher kein Problem.

String-Literale wie "String" werden wahrscheinlich im Adressraum Ihrer ausführbaren Datei als schreibgeschützte Daten zugeordnet (geben oder nehmen Sie Ihren Compiler an). Wenn Sie es berühren, wird es ausgeflippt, dass Sie sich im Badeanzugbereich befinden und Sie mit einem SEG -Fehler wissen.

In Ihrem ersten Beispiel erhalten Sie einen Zeiger auf diese Const -Daten. In Ihrem zweiten Beispiel initialisieren Sie ein Array von 7 Zeichen mit einer Kopie der CONT -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, str ist ein Zeiger, der auf zeigt "string". Der Compiler darf String -Literale an Stellen in Erinnerung stellen, an die Sie nicht schreiben können, aber nur lesen kann. (Dies hätte wirklich eine Warnung auslösen sollen, da Sie a zuweisen const char * zu einem char *. Hatten Sie Warnungen deaktiviert oder haben Sie sie nur ignoriert?)

An zweiter Stelle erstellen Sie ein Array, das Speicher ist, auf den Sie vollen Zugriff erhalten, und es initialisieren es mit "string". Sie erstellen eine char[7] (Sechs für die Buchstaben, eine für die Beendigung ' 0'), und du machst, was du damit willst.

Angenommen, die Saiten sind,

char a[] = "string literal copied to stack";
char *p  = "string literal referenced by p";

Im ersten Fall soll das Literal kopiert werden, wenn 'A' in den Geltungsbereich kommt. Hier ist 'a' ein Array, das auf Stack definiert ist. Dies bedeutet, dass die Zeichenfolge auf dem Stack erstellt wird und seine Daten aus dem Code (Text-) Speicher kopiert werden, der in der Regel nur schreibgeschützt ist (dies ist implementierungsspezifisch. Ein Compiler kann diese schreibgeschützten Programmdaten auch in read-schreibbarer Speicher platzieren. ).

Im zweiten Fall ist P ein Zeiger, der auf Stack (lokaler Bereich) definiert ist und ein String -Literal (Programmdaten oder Text) verweist, der wo sonst gespeichert ist. Normalerweise ist es weder eine gute Praxis noch ermutigt, ein solches Gedächtnis zu ändern.

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

Der Segmentierungsfehler wird verursacht, wenn Sie Tyr auf den Speicher zugreifen, der nicht zugänglich ist.

char *str ist ein Zeiger auf eine Zeichenfolge, die nicht veränderbar ist (der Grund für den SEG -Fehler).

wohingegen char str[] ist ein Array und kann verändert werden.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top