Absturz oder “Segmentierungsfehler”, wenn Daten in einen nicht initialisierten Zeiger kopiert/gescannt/gelesen werden

Lesezeit: 8 Minuten

Diese Frage soll als Referenz für alle häufig gestellten Fragen der Natur dienen:

Warum erhalte ich einen mysteriösen Absturz oder „Segmentierungsfehler“, wenn ich Daten an die Adresse kopiere/scanne, auf die ein nicht initialisierter Zeiger zeigt?

Zum Beispiel:

char* ptr;
strcpy(ptr, "hello world"); // crash here!

oder

char* ptr;
scanf("%s", ptr); // crash here!

  • Das Problem ist eher, dass OPs nicht einmal wissen, dass der Zeiger nicht initialisiert ist, sondern dass auf magische Weise ein Objekt erscheint, sobald Sie den Zeiger deklarieren/definieren (sie verwirren dies auch).

    – zu ehrlich für diese Seite

    31. Mai 2016 um 15:21 Uhr

  • Sie sollten wahrscheinlich den Titel ändern, wenn Sie darauf abzielen, dass diese Frage von Personen gelesen wird, bei denen dieses Problem auftritt Vor sie posten es hier.

    – barak manos

    31. Mai 2016 um 15:22 Uhr

  • @Olaf In der Tat, dann stimmen Sie ihre Segmentierungsfragen als Duplikate mit einem Link zu diesem ab. Ich habe eine FAQ-Frage wie diese ewig vermisst; kam endlich dazu, einen aufzuschreiben.

    – Ludin

    31. Mai 2016 um 15:22 Uhr

  • @barakmanos Die Absicht ist, diesen Beitrag als “kanonisches Duplikat” für häufig gestellte Fragen zu verwenden. Ich erwarte nicht wirklich, dass Neulinge es selbst finden.

    – Ludin

    31. Mai 2016 um 15:23 Uhr

  • @Lundin: Ich schätze deine Bemühungen. Wenn das als dup-CV gemeint ist, bin ich dabei. Aber eigentlich wäre es mir lieber, sie würden es selbst finden, bevor sie posten. Aber das ist wahrscheinlich sowieso Wunschdenken, da Anfänger dazu neigen, zu glauben, dass ihr Problem einzigartig ist. Also, habe +1 und ich werde es mir merken – danke!

    – zu ehrlich für diese Seite

    31. Mai 2016 um 15:25 Uhr


Ein Zeiger ist eine spezielle Art von Variable, die nur eine Adresse einer anderen Variablen enthalten kann. Es darf keine Daten enthalten. Sie können keine Daten in einen Zeiger kopieren/speichern – das ergibt keinen Sinn. Sie können einen Zeiger nur so setzen, dass er auf anderweitig zugeordnete Daten zeigt.

Damit ein Zeiger sinnvoll ist, muss er also immer auf eine gültige Speicherstelle zeigen. Zum Beispiel könnte es auf Speicher zeigen, der auf dem Stack zugewiesen ist:

{
  int data = 0;
  int* ptr = &data;
  ...
}

Oder dynamisch auf dem Heap zugewiesener Speicher:

int* ptr = malloc(sizeof(int));

Es ist immer ein Fehler, einen Zeiger zu verwenden, bevor er initialisiert wurde. Es zeigt noch nicht auf eine gültige Erinnerung.

Diese Beispiele können alle zu Programmabstürzen oder anderen unerwarteten Verhaltensweisen wie „Segmentierungsfehlern“ führen:

/*** examples of incorrect use of pointers ***/

// 1.
int* bad;
*bad = 42;

// 2.
char* bad;
strcpy(bad, "hello");

Stattdessen müssen Sie sicherstellen, dass der Zeiger auf (genügend) allozierten Speicher zeigt:

/*** examples of correct use of pointers ***/

// 1.
int var;
int* good = &var;
*good = 42;

// 2.
char* good = malloc(5 + 1); // allocates memory for 5 characters *and*  the null terminator
strcpy(good, "hello");

Beachten Sie, dass Sie einen Zeiger auch so einstellen können, dass er auf ein wohldefiniertes “Nirgendwo” zeigt, indem Sie ihn darauf zeigen lassen NULL. Das macht es zu einem Null Zeiger, was ein Zeiger ist, der garantiert nicht auf einen gültigen Speicher zeigt. Dies unterscheidet sich davon, den Zeiger vollständig uninitialisiert zu lassen.

int* p1 = NULL; // pointer to nowhere
int* p2;        // uninitialized pointer, pointer to "anywhere", cannot be used yet

Sollten Sie jedoch versuchen, auf den Speicher zuzugreifen, auf den ein Nullzeiger zeigt, können Sie ähnliche Probleme bekommen wie bei der Verwendung eines nicht initialisierten Zeigers: Abstürze oder Segmentierungsfehler. Im besten Fall bemerkt Ihr System, dass Sie versuchen, auf die Adresse null zuzugreifen, und wirft dann eine „Nullzeiger-Ausnahme“ aus.

Die Lösung für Nullzeiger-Ausnahmefehler ist dieselbe: Sie müssen den Zeiger so einstellen, dass er auf einen gültigen Speicher zeigt, bevor Sie ihn verwenden.


Weiterlesen:

Zeiger, die auf ungültige Daten zeigen

Wie greife ich mit Zeigern von einer anderen Funktion auf eine lokale Variable zu?
Kann auf den Speicher einer lokalen Variablen außerhalb ihres Geltungsbereichs zugegriffen werden?

Segmentierungsfehler und Ursachen

Was ist ein Segmentierungsfehler?
Warum erhalte ich einen Segmentierungsfehler, wenn ich in eine Zeichenfolge schreibe, die mit „char *s“, aber nicht mit „char s“ initialisiert wurde[]”?
Was ist der Unterschied zwischen char s[] und Char*s?
Endgültige Liste häufiger Gründe für Segmentierungsfehler
Was ist ein Busfehler?

  • Diese Art von Fehlern wird sehr häufig von Anfängern geschrieben, die noch nicht verstanden haben, was Zeiger sind oder wie sie funktionieren. Beachten Sie also bitte, dass die Absicht dieses Community-Wikis daher darin besteht, Erklärungen auf einer grundlegenden Ebene zu halten. Wenn Sie fortgeschrittenere Antworten mit Verweisen auf den C-Standard usw. hinterlassen möchten, posten Sie bitte eine andere Antwort auf die Frage.

    – Ludin

    31. Mai 2016 um 15:18 Uhr

  • “Es darf keine Daten enthalten.” – Hmm, eigentlich die Adresse ist seine Daten.

    – zu ehrlich für diese Seite

    31. Mai 2016 um 15:22 Uhr

  • @Olaf Halten Sie die Dinge hier bitte einfach 🙂 Dies ist für Anfänger gedacht. Obwohl … wenn eine Adresse Daten sind, warum hat eine CPU dann sowohl einen Adressbus als auch einen Datenbus?

    – Ludin

    31. Mai 2016 um 15:25 Uhr

  • Liest es den Inhalt einer Zeigervariablen vom Adressbus? 😉 (Und z. B. PCIe verwendet ein Paket-/Befehlsformat, keinen klassischen Adress-/Datenbusmechanismus). Es gab auch RAM-Designs auf diese Weise (erinnern Sie sich an RAMBUS?) Wie auch immer, ich bin mit der Antwort einverstanden, sollte für Anfänger ausreichen. Hinterlassen Sie einfach die Kommentare, wenn sie daran interessiert sind, sich eingehender damit zu befassen.

    – zu ehrlich für diese Seite

    31. Mai 2016 um 15:26 Uhr


  • Hinweis zum letzten Beispiel: Man muss den Zeiger nicht dereferenzieren, um undefiniertes Verhalten zu zeigen. Wie alles andere auch, unbestimmter Inhalt (der Fall von Wert für p2) ruft naturgemäß undefiniertes Verhalten hervor, wenn es gerade ist ausgewertetgeschweige denn den Wahnsinn fördern Dereferenzierung. Ja, es ist ein anderes Problem, aber eng verwandt. Zusammenfassend ist die Aussage “Es ist immer ein Fehler, einen Zeiger zu verwenden, bevor er initialisiert wurde.” ist wahr, aber “Verwendung” ist nicht nur auf die Dereferenzierung beschränkt.

    – WhozCraig

    31. Mai 2016 um 15:43 Uhr


  1. Pointer zeigen nur auf einen Speicherplatz. Sie haben einen Zeiger erstellt, aber noch nicht an einen Speicherort gebunden. strcpy möchte, dass Sie zwei Zeiger übergeben (Erstens darf man nicht konstant sein), die auf zwei Zeichen-Arrays wie diese Signatur verweisen:

    char * strcpy ( char * destination, const char * source );
    

    Beispielverwendung:

    char* ptr = malloc(32);  
    strcpy(ptr, "hello world");
    
    char str[32];  
    strcpy(str, "hello world");
    
  2. Sie können das folgende Code-Snippet ausprobieren, um die Zeichenfolge zu lesen, bis das Zeilenumbruchzeichen erreicht ist (* Sie können auch andere Leerzeichen hinzufügen, z "%[^\t\n]s"(Tabulator, Zeilenumbruch) oder "%[^ \t\n]s" (Leerzeichen, Tabulator, Zeilenumbruch)).

    char *ptr = malloc(32);
    scanf("%31[^\n]", ptr);
    

    (Vergessen Sie im wirklichen Leben nicht, den Rückgabewert von zu überprüfen scanf()!)

  • Vergessen Sie im wirklichen Leben nicht, den Rückgabewert von zu überprüfen malloc()!

    – SS Anne

    1. März 2020 um 4:02 Uhr

Eine Situation, die beim Erlernen von C häufig auftritt, ist der Versuch, einfache Anführungszeichen zu verwenden, um ein Zeichenfolgenliteral zu bezeichnen:

char ptr[5];
strcpy(ptr, 'hello'); // crash here!
//            ^     ^   because of ' instead of "

In C, 'h' ist ein einzelnes Zeichenliteral, während "h" ist ein Zeichenfolgenliteral, das ein enthält 'h' und ein Nullterminator \0 (d. h. ein 2-Zeichen-Array). Auch in C ist der Typ eines Zeichenliterals intdas ist, sizeof('h') ist äquivalent zu sizeof(int)während sizeof(char) ist 1.

char h="h";
printf("Size: %zu\n", sizeof(h));     // Size: 1
printf("Size: %zu\n", sizeof('h'));   // likely output: Size: 4

Dies geschieht, weil Sie haben nicht zugewiesen Erinnerung für die Zeiger char* ptr . In diesem Fall müssen Sie dynamisch zuordnen Speicher für den Zeiger.

Zwei Funktionen malloc() und calloc() kann verwendet werden für dynamic memory allocation.

Versuchen Sie diesen Code:-

char* ptr;
ptr = malloc(50); // allocate space for 50 characters.
strcpy(ptr, "hello world");

Bei der Verwendung von *ptr vorbei nicht vergessen Speicher freigeben zugeteilt für *ptr .Dies kann mit erfolgen free() Funktion.

free(ptr);  // deallocating memory.

Größe von dynamisch zugewiesener Speicher kann mit geändert werden realloc().

char *tmp = realloc(ptr, 100); // allocate space for 100 characters.
if (! tmp) {
    // reallocation failed, ptr not freed
    perror("Resize failed");
    exit(1);       
}
else {
    // reallocation succeeded, old ptr freed
    ptr = tmp;
}

In den meisten Fällen “Segmentierungsfehler” passiert aufgrund eines Fehlers in Speicherzuweisung oder Array außerhalb der Grenze Fälle.

Um eine änderbare Kopie einer Zeichenfolge zu erstellen, anstatt sie zu verwenden malloc, strlen und strcpyhat die POSIX C-Bibliothek eine praktische Funktion namens strdup in <string.h> die eine Kopie der übergebenen nullterminierten Zeichenfolge mit zugewiesener Speicherdauer zurückgibt. Nach Gebrauch sollte der Zeiger mit losgelassen werden free:

char* ptr;
ptr = strdup("hello world");
ptr[0] = 'H';
puts(ptr);
free(ptr);

  • Bitte beachten Sie das jedoch strdup ist kein Standard-C und nicht unbedingt portabel.

    – Ludin

    8. August 2019 um 9:37 Uhr

  • @Lundin ist konform eine ISO-Normaber nicht das C standardmäßig ja. Für die Portabilität steht das P in POSIX.

    – Antti Haapala – Слава Україні

    8. August 2019 um 9:47 Uhr


  • Das spielt keine Rolle, wenn Sie nicht in einer PC-Umgebung arbeiten.

    – Ludin

    8. August 2019 um 13:06 Uhr

  • @AnttiHaapala Ja, in der Tat. Die Definition des Unix-Programmierers von “portabel” bedeutet im Allgemeinen “portabel zu einem anderen Unix”.

    – Mike Housky

    16. Dezember 2019 um 16:18 Uhr

  • @MikeHousky POSIX ist immer noch ein echter Standard, den sogar Microsoft einmal schlecht implementieren wollte

    – Antti Haapala – Слава Україні

    24. Oktober 2020 um 4:33 Uhr

  • Bitte beachten Sie das jedoch strdup ist kein Standard-C und nicht unbedingt portabel.

    – Ludin

    8. August 2019 um 9:37 Uhr

  • @Lundin ist konform eine ISO-Normaber nicht das C standardmäßig ja. Für die Portabilität steht das P in POSIX.

    – Antti Haapala – Слава Україні

    8. August 2019 um 9:47 Uhr


  • Das spielt keine Rolle, wenn Sie nicht in einer PC-Umgebung arbeiten.

    – Ludin

    8. August 2019 um 13:06 Uhr

  • @AnttiHaapala Ja, in der Tat. Die Definition des Unix-Programmierers von “portabel” bedeutet im Allgemeinen “portabel zu einem anderen Unix”.

    – Mike Housky

    16. Dezember 2019 um 16:18 Uhr

  • @MikeHousky POSIX ist immer noch ein echter Standard, den sogar Microsoft einmal schlecht implementieren wollte

    – Antti Haapala – Слава Україні

    24. Oktober 2020 um 4:33 Uhr

1414410cookie-checkAbsturz oder “Segmentierungsfehler”, wenn Daten in einen nicht initialisierten Zeiger kopiert/gescannt/gelesen werden

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

Privacy policy