C Segmentierungsfehler
Dieses Tutorial behandelt den Segmentierungsfehler in C und zeigt einige Codebeispiele, um den Grund für diesen Fehler zu erklären. Zuerst werden wir über Programmsegmente und dynamischen Speicher sprechen.
Später werden wir verschiedene Gründe für den Segmentierungsfehler und mögliche Lösungen untersuchen.
Programmsegmente in C
Der Computerspeicher wird in Primärspeicher und Sekundärspeicher unterteilt. Es muss jedes Programm in den Primärspeicher (RAM) laden, um das Programm auszuführen.
Ein Programm wird weiter in verschiedene Segmente unterteilt.
Es gibt fünf Hauptsegmente eines Programms. Diese Segmente sind:
-
Textsegment – Das Textsegment enthält den Code des Programms.
-
Initialisiertes Datensegment – Das initialisierte Datensegment enthält global initialisierte Variablen des Programms.
-
Nicht initialisiertes Datensegment – Das nicht initialisierte Datensegment enthält globale nicht initialisierte Variablen des Programms. Dieser Abschnitt wird auch
bss
(better save space) genannt.Manchmal deklarieren wir ein großes, nicht initialisiertes Array wie
int x[1000]
, das4000
Bytes erfordert; Dieser Speicherplatz wird jedoch erst benötigt, wenn das Array initialisiert wurde.Daher ist dieser Platz nicht reserviert; nur ein Zeiger wird gespeichert. Der Speicher wird zugewiesen, wenn ein Array initialisiert wird.
-
Heap – Der Heap-Abschnitt enthält Daten, für die zur Laufzeit Speicher angefordert wird, da die genaue Größe dem Programmierer zur Codierzeit unbekannt ist.
-
Stack – Der Stack-Abschnitt enthält alle lokalen Variablen. Immer wenn eine Funktion aufgerufen wird, werden ihre lokalen Variablen auf den Stack geschoben (so dass der Stack wächst), und wenn die Funktion zurückkehrt, werden die lokalen Variablen per Pop (der Stack schrumpft) abgelegt.
Zur Visualisierung und Entwicklung eines besseren Verständnisses von Programmsegmenten siehe den folgenden Code:
int x = 5;
int y[100];
int main() {
int number = 5;
int *x = new int[5];
return 0;
}
In diesem Programm sind x
globale initialisierte Daten und y
globale nicht initialisierte Daten. Als nächstes ist die Nummer
eine lokale Variable; Gehen Sie zu einem Stapelbereich.
x
ist ein Zeiger, wieder eine lokale Variable, gehe zu einem Stack-Bereich. Das new int[5]
weist dem Heap-Bereich Speicherplatz zu.
In der Unix-Familie von Betriebssystemen können Sie die Segmente dieses Programms leicht erkennen.
Dynamischer Speicher in C
Bei vielen Programmen kennen Programmierer den genauen Speicherbedarf nicht. In solchen Fällen nimmt der Programmierer entweder Eingaben vom Benutzer oder der Datei entgegen, um die Datengröße zu erhalten, und deklariert Speicher zur Laufzeit entsprechend der Eingabe.
Siehe ein Beispiel:
int main() {
int size;
cout << "Enter Size: ";
cin >> size
int *x = (int*) malloc(size] * sizeof(int) );
... return 0;
}
In diesem Programm gibt der Benutzer die Größe ein, und das Programm weist zur Laufzeit Speicher gemäß der Größe zu.
In einer Multiprogramming-Umgebung muss ein Betriebssystem Speicherschutz bieten. Das heißt, Programme daran zu hindern, Daten ohne ihre Bereitschaft zu teilen.
Daher verfügt jedes Betriebssystem über einen Mechanismus, um Programme daran zu hindern, auf illegalen Speicher zuzugreifen.
Wir können nur auf den Speicher zugreifen, der für unser Programm reserviert ist. Ein Segmentierungsfehler kann auftreten, wenn wir versuchen, auf Adressen außerhalb des Adressraums des Programms zuzugreifen, oder wenn der zugewiesene Speicher des Programms nicht ausreicht, um die dynamischen Zuordnungsanforderungen zu erfüllen.
Lassen Sie uns dies im Detail besprechen.
Segmentierungsfehler in C
Ein Segmentierungsfehler tritt auf, wenn Sie versuchen, auf den Speicherort außerhalb der Reichweite Ihres Programms zuzugreifen, oder wenn Sie keine Berechtigung zum Zugriff auf den Speicher haben. Lassen Sie uns unten einige Fälle diskutieren.
Versuchen Sie, einen nicht initialisierten Zeiger zurückzuweisen
Dieser Fehler kann verwirrend sein, da einige Compiler in einigen Fällen Warnungen ausgeben und Ihnen helfen, diesen Fehler zu vermeiden, während andere dies nicht tun. Unten ist ein interessantes Beispiel, das verwirrend ist.
int main() {
int* pointer;
printf("%d\n", *pointer);
return 0;
}
Im obigen Code versuchen wir, einen nicht zugeordneten Zeiger zu dereferenzieren, was bedeutet, dass wir versuchen, auf den Speicher zuzugreifen, für den wir keine Zugriffsberechtigung haben.
Das Kompilieren dieses Programms mit (cygwin
) GCC Version 9.3.0 ergibt den Segmentierungsfehler (Core Dump).
Wenn wir es mit g++ 9.3.0 kompilieren, wird Null ausgegeben.
Wenn wir nun dieses Programm ein wenig ändern und eine Funktion hinzufügen:
void access() {
int* pointer;
printf("%d\n", *pointer);
}
int main() {
access();
return 0;
}
Jetzt geben beide Kompilierungen einen Müllwert als Ausgabe aus, was verwirrend ist, da wir immer noch versuchen, auf den nicht zugeordneten Speicher zuzugreifen.
Wenn wir dies auf einem beliebigen Online-Compiler versuchen, zeigt es ein ähnliches Verhalten. Segmentierungsfehler ist anormal; Manchmal kann dieser Fehler durch eine kleine Änderung hinzugefügt oder entfernt werden.
Um diese Art von Fehler zu vermeiden, denken Sie daran, Ihren Zeiger zu initialisieren, und prüfen Sie vor der Dereferenzierung, ob der Zeiger nicht null ist.
Versuchen Sie, großen Speicher zuzuweisen
Dieser Fehler kann auf zwei Arten auftreten. Eine, wenn Sie ein großes Array auf dem Stapel deklarieren, und die zweite, wenn Sie großen Speicher auf dem Heap deklarieren.
Wir werden beide nacheinander sehen.
#include <stdio.h>
int main() {
int largeArray[10000000]; // allocating memory in stack
printf("Ok\n");
return 0;
}
Wenn Sie die Anzahl der Nullen reduzieren, ist die Ausgabe Ok
; Wenn Sie jedoch die Nullen weiter erhöhen, stürzt das Programm irgendwann ab und gibt Folgendes aus:
Segmentation fault
Der Grund dafür ist, dass die Stapelfläche endlich ist. Das bedeutet, dass der von diesem großen Array benötigte Speicher nicht verfügbar ist.
Schließlich versucht Ihr Programm, das Segment zu verlassen.
Wir können einen Heap verwenden, wenn wir mehr Speicher benötigen (größer als auf dem Stack verfügbar). Der Haufen hat jedoch auch Grenzen; Wenn wir also die Speichergröße weiter erhöhen, treten Fehler auf.
Siehe das Beispiel unten.
#include <stdio.h>
#include <stdlib.h>
int main() {
int *veryLargeArr;
long int size = 100000000000;
veryLargeArr = (int *)malloc(sizeof(int) * size);
if (veryLargeArr == NULL)
printf("Space is not enough.\n");
else
printf("memory allocation is successful\n");
return 0;
}
Ausgang:
memory allocation is successful
Wenn wir die Größe jedoch weiter erhöhen, wird das Limit überschritten. Wir haben beispielsweise ein Problem mit der folgenden Größe:
long int size = 1000000000000000; // 100000000000
Sie können in der obigen Aussage weitere Nullen zählen. In einem solchen Fall kann das Programm abstürzen; Um jedoch einen Segmentierungsfehler zu vermeiden, haben wir überprüft, ob der Zeiger NULL
ist oder nicht.
NULL
bedeutet, dass kein Speicher zugewiesen wurde, da der angeforderte Speicherplatz nicht verfügbar ist.
Ausgang:
Space is not enough.
Wenn Sie diesen Code ohne Überprüfung mit dynamisch zugewiesenem Speicher ausprobieren, erhalten Sie einen Segmentierungsfehler.
Endlosschleife oder rekursiver Aufruf
Wenn Sie versehentlich eine Endlosschleife in Ihrem Programm belassen, führt dies zu einem Segmentierungsfehler, insbesondere wenn Sie innerhalb der Schleife dynamischen Speicher zuweisen.
Es wird ein Beispiel für eine Endlosschleife mit dynamischer Speicherzuweisung gegeben.
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p;
while (true) p = (int *)malloc(100000);
return 0;
}
Hier sehen Sie eine Endlosschleife mit while (true)
. Die Speicherzuweisungsanweisung innerhalb der Schleife generiert letztendlich einen Fehler, da die Speicherzuweisung wiederholt wiederholt wird, ohne dass die Methode free
aufgerufen wird, um den Speicher freizugeben.
Ebenso kann das Erstellen einer rekursiven Funktion ohne Hinzufügen eines Basisfalls dazu führen, dass der Stapel überläuft. Siehe das Beispiel unten.
void check() { check(); }
int main() { check(); }
Im obigen Code ruft sich die check
-Funktion immer wieder selbst auf und erstellt ihre Kopien auf dem Stack, was den Segmentierungsfehler verursacht, sobald der verfügbare Speicher für das Programm verbraucht ist.
Abschluss
Ein Segmentierungsfehler tritt auf, wenn ein Programm versucht, auf einen Speicher zuzugreifen, der außerhalb seiner Reichweite oder nicht verfügbar ist. Überprüfen Sie vor der Dereferenzierung, ob der Zeiger auf einen Speicher zeigt.
Verwenden Sie dynamischen Speicher, wenn viel Platz benötigt wird, und prüfen Sie, ob der Zeiger NULL
ist oder nicht. Stellen Sie sicher, dass Sie &
vor einer Variablen in scanf
verwenden und den richtigen Bezeichner nach %
in printf
verwenden.
Versuchen Sie nicht, einem Array außerhalb seiner Größe einen Wert zuzuweisen oder darauf zuzugreifen. Initialisieren Sie die Variablen und die Zeiger immer zum Zeitpunkt der Deklaration.