Warum erhalte ich einen Segmentierungsfehler, wenn ich in ein „char *s“ schreibe, das mit einem Zeichenfolgenliteral initialisiert wurde, aber nicht in „char s[]”?

Lesezeit: 9 Minuten

Benutzeravatar von Markus
Markus

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.

  • Es ist lustig – aber dies wird tatsächlich kompiliert und läuft perfekt, wenn Windows Compiler (cl) an einer Visual Studio Developer-Eingabeaufforderung verwendet wird. Hat mich kurz verwirrt…

    – Maverick Erdmännchen

    13. September 2016 um 8:30 Uhr

Benutzeravatar von matli
Matli

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 Zeichenfolgenliteral (der formale Begriff für eine Zeichenfolge in doppelten Anführungszeichen in C-Quellen) kann auf zwei leicht unterschiedliche Arten verwendet werden:

  1. Als Initialisierer für ein char-Array, wie in der Deklaration von char a[] gibt es die Anfangswerte der Zeichen in diesem Array an (und, falls erforderlich, seine Größe).
  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 geändert werden. In einem Ausdruckskontext wird das Array wie üblich sofort in einen Zeiger umgewandelt (siehe Abschnitt 6), sodass die zweite Deklaration p so initialisiert, dass es auf das erste Element des unbenannten Arrays zeigt.

Einige Compiler haben einen Schalter, der steuert, ob String-Literale schreibbar sind oder nicht (zum Kompilieren von altem Code), und einige haben möglicherweise Optionen, um zu bewirken, dass String-Literale formal als Arrays von const char behandelt werden (für eine bessere Fehlererkennung).

  • Einige andere Punkte: (1) der Segfault tritt wie beschrieben auf, aber sein Auftreten ist eine Funktion der Laufumgebung; Wenn sich derselbe Code in einem eingebetteten System befand, hat das Schreiben möglicherweise keine Auswirkung oder ändert das s möglicherweise tatsächlich in ein z. (2) Da Zeichenfolgenliterale nicht schreibbar sind, kann der Compiler Platz sparen, indem er zwei Instanzen von „Zeichenfolge“ an derselben Stelle platziert; oder wenn Sie an einer anderen Stelle im Code “eine andere Zeichenfolge” haben, könnte ein Teil des Speichers beide Literale unterstützen. Wenn es dem Code dann erlaubt wäre, diese Bytes zu ändern, könnten natürlich seltsame und schwierige Fehler auftreten.

    – Gregor

    26. August 2011 um 23:24 Uhr

  • @greggo: Guter Punkt. Es gibt auch eine Möglichkeit, dies auf Systemen mit MMU zu tun, indem Sie verwenden mprotect Nur-Lese-Schutz aufzuheben (vgl hier).

    Benutzer405725

    2. Mai 2013 um 13:40 Uhr

  • Also erstellt char *p=”blah” tatsächlich ein temporäres Array?seltsam.

    – Rahul Tyagi

    3. Dezember 2014 um 13:35 Uhr

  • Und nach 2 Jahren Schreiben in C++…TIL

    – Zeboidlund

    28. Dezember 2014 um 0:41 Uhr

  • @rahul tyagi, kein temporäres Array. Ganz im Gegenteil, es ist das langlebigste aller Arrays. Es wird vom Compiler erstellt und in der ausführbaren Datei selbst gefunden. Was Sie aus dem Obigen verstanden haben sollten, ist, dass es sich um ein handelt geteilt Array, das behandelt werden muss schreibgeschützt (und kann tatsächlich schreibgeschützt sein).

    – Ikegami

    15. November 2019 um 10:03 Uhr


Benutzeravatar von Greg Hewgill
Greg Hewgill

Normalerweise werden Zeichenfolgenliterale im Nur-Lese-Speicher gespeichert, wenn das Programm ausgeführt wird. Damit soll verhindert werden, dass Sie versehentlich eine String-Konstante ändern. In deinem 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 die Zeichenfolge "string" ist kopiert durch den Compiler von seiner schreibgeschützten Heimat in die str[] Reihe. Dann ist das Ändern des ersten Zeichens erlaubt. 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 reserviert hat:

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

  • Wann immer Sie »%p« auf printf verwenden, sollten Sie den Zeiger auf void * umwandeln, wie in printf(“%p”, (void *)str); Wenn Sie ein size_t mit printf drucken, sollten Sie “%zu” verwenden, wenn Sie den neuesten C-Standard (C99) verwenden.

    – Chris Jung

    3. Oktober 2008 um 7:44 Uhr

  • Außerdem werden die Klammern mit sizeof nur benötigt, wenn die Größe eines Typs genommen wird (das Argument sieht dann wie ein Cast aus). Denken Sie daran, dass sizeof ein Operator und keine Funktion ist.

    – abschalten

    25. November 2008 um 8:45 Uhr

  • und verwenden %zu zu drucken size_t

    – phuklv

    11. April 2017 um 15:36 Uhr

  • Warnung: Unbekanntes Umwandlungstypzeichen „z“ im Format [-Wformat=] :/

    – John

    26. Februar 2021 um 10:02 Uhr

Die meisten dieser Antworten sind richtig, aber nur um ein wenig mehr Klarheit zu schaffen …

Der “Nur-Lese-Speicher”, auf den sich die Leute beziehen, ist das Textsegment in ASM-Begriffen. Es ist derselbe Ort im Speicher, an dem die Anweisungen geladen werden. Dies ist aus offensichtlichen Gründen wie Sicherheit schreibgeschützt. Wenn Sie ein mit einem String initialisiertes char* erstellen, werden die Stringdaten in das Textsegment kompiliert und das Programm initialisiert den Zeiger so, dass er auf das Textsegment zeigt. Also, wenn Sie versuchen, es zu ändern, kaboom. Segfault.

Wenn sie als Array geschrieben werden, platziert der Compiler stattdessen die initialisierten Zeichenfolgendaten im Datensegment, das derselbe Ort ist, an dem sich Ihre globalen Variablen und dergleichen befinden. Dieser Speicher ist änderbar, da es keine Anweisungen im Datensegment gibt. Dieses Mal, wenn der Compiler das Zeichenarray initialisiert (das immer noch nur ein char* ist), zeigt es auf das Datensegment und nicht auf das Textsegment, das Sie zur Laufzeit sicher ändern können.

  • Aber ist es nicht wahr, dass es Implementierungen geben kann, die es ermöglichen, den “Nur-Lese-Speicher” zu modifizieren?

    – Schrittmacher

    21. September 2013 um 5:07 Uhr

  • Wenn sie als Array geschrieben werden, platziert der Compiler die initialisierten Zeichenfolgendaten im Datensegment, wenn sie statisch oder global sind. Andernfalls (zB für ein normales automatisches Array) legt es auf dem Stack, im Stackframe der Funktion main ab. Richtig?

    – SE

    4. Dezember 2019 um 2:15 Uhr

  • @SE Ja, ich würde mir vorstellen, dass Bob Somers sich beim Schreiben von “dem Datensegment” sowohl auf den Stapel als auch auf den Heap und die Statik (einschließlich statischer und globaler Variablen) bezieht. Und ein lokales Array wird auf den Stapel gelegt, also sind Sie hier richtig 🙂

    – Olov

    27. Dezember 2020 um 1:44 Uhr

  • Entschuldigung, aber Sie haben hier wahrscheinlich Recht. Das Datensegment ist der Teil des Speichers, der für initialisierte globale oder statische Variablen bestimmt ist, aber das Array könnte auch auf den Stapel gelegt werden, wenn es lokal ist, wie Sie geschrieben haben.

    – Olov

    27. Dezember 2020 um 1:53 Uhr

Ciro Santilli Benutzeravatar von OurBigBook.com
Ciro Santilli OurBigBook.com

Warum erhalte ich beim Schreiben in eine Zeichenfolge einen Segmentierungsfehler?

C99 N1256-Entwurf

Es gibt zwei verschiedene Verwendungen von Zeichenfolgenliteralen:

  1. Initialisieren char[]:

    char c[] = "abc";      
    

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

    Ein Array vom Zeichentyp kann durch ein Zeichenfolgenliteral initialisiert werden, das optional in geschweiften Klammern eingeschlossen ist. Aufeinanderfolgende Zeichen des Zeichenfolgenliterals (einschließlich des abschließenden Nullzeichens, wenn Platz vorhanden ist oder das Array eine unbekannte Größe hat) 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 können geändert werden.

  2. Überall sonst: es erzeugt ein:

    • unbenannt
    • array of char Was ist der Typ von String-Literalen in C und C++?
    • mit statischer Lagerung
    • das gibt UB, wenn es modifiziert wird

    Also wenn du schreibst:

    char *c = "abc";
    

    Dies ist ähnlich wie:

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

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

    Dann, wenn Sie ändern c[0]ändern Sie auch __unnameddas ist UB.

    Dies ist unter 6.4.5 “String-Literale” dokumentiert:

    5 In Übersetzungsphase 7 wird an jede Multibyte-Zeichenfolge, die aus einem oder mehreren String-Literalen resultiert, ein Byte oder Code mit dem Wert Null angehängt. Die Mehrbyte-Zeichenfolge wird dann verwendet, um ein Array von statischer Speicherdauer und -länge zu initialisieren, die gerade ausreicht, um die Folge zu enthalten. Bei Zeichenkettenliteralen sind die Array-Elemente vom 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 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 Zeichenkettenliteralen initialisiert werden.

Diese Deklaration ist identisch mit

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

Der Inhalt der Arrays ist modifizierbar. Andererseits die Deklaration

char *p = "abc";

definiert p vom Typ „pointer to char“ und initialisiert es so, dass es auf ein Objekt vom Typ „array of char“ der Länge 4 zeigt, dessen Elemente mit einem Zeichenfolgenliteral initialisiert werden. Wenn versucht wird, zu verwenden 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 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

Fazit: GCC-Stores char* es hinein .rodata Abschnitt, nicht drin .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)

so wird es im Stack gespeichert (relativ zu %rbp).

Beachten Sie jedoch, dass das standardmäßige Linker-Skript puts .rodata und .text im selben Segment, das Ausführungs-, aber keine Schreibberechtigung hat. 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 oft im Nur-Lese-Speicher abgelegt werden. “str” ​​ist ein Zeiger, der verwendet wird, um die Konstante zu modifizieren.

Im zweiten Code ist “String” ein Array-Initialisierer, eine Art Abkürzung für

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

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

  • Auf dem Stack oder dem Datensegment if str ist global bzw static.

    – Gauthier

    24. März 2016 um 9:16 Uhr

Denn die Art von "whatever" im Rahmen des 1. Beispiels ist const char * (selbst wenn Sie es einem nicht konstanten Zeichen* zuweisen), was bedeutet, dass Sie nicht versuchen sollten, darauf zu schreiben.

Der Compiler hat dies erzwungen, indem er die Zeichenfolge in einen schreibgeschützten Teil des Speichers gestellt hat, sodass das Schreiben darauf einen Segfault erzeugt.

  • Auf dem Stack oder dem Datensegment if str ist global bzw static.

    – Gauthier

    24. März 2016 um 9:16 Uhr

Benutzeravatar von Bence Kaulics
Bence Kaulics

char *str = "string";  

Die oben genannten Sätze str auf den Literalwert zeigen "string" das im Binärbild des Programms fest codiert ist und wahrscheinlich im Speicher als schreibgeschützt 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.

1427220cookie-checkWarum erhalte ich einen Segmentierungsfehler, wenn ich in ein „char *s“ schreibe, das mit einem Zeichenfolgenliteral initialisiert wurde, aber nicht in „char s[]”?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy