Ich weise Werte in einem C ++ - Programm außerhalb der folgenden Grenzen zu:
#include <iostream>
using namespace std;
int main()
{
int array[2];
array[0] = 1;
array[1] = 2;
array[3] = 3;
array[4] = 4;
cout << array[3] << endl;
cout << array[4] << endl;
return 0;
}
Das Programm druckt 3
und 4
. Es sollte nicht möglich sein. Ich benutze g ++ 4.3.3
Hier ist der Befehl zum Kompilieren und Ausführen
$ g++ -W -Wall errorRange.cpp -o errorRange
$ ./errorRange
3
4
Nur beim Zuweisen array[3000]=3000
gibt es einen Segmentierungsfehler.
Wenn gcc nicht nach Array-Grenzen sucht, wie kann ich dann sicher sein, dass mein Programm korrekt ist, da dies später zu schwerwiegenden Problemen führen kann?
Ich habe den obigen Code durch ersetzt
vector<int> vint(2);
vint[0] = 0;
vint[1] = 1;
vint[2] = 2;
vint[5] = 5;
cout << vint[2] << endl;
cout << vint[5] << endl;
und dieser erzeugt auch keinen Fehler.
vector
ändert die Größe nicht automatisch, wenn auf Elemente außerhalb der Grenzen zugegriffen wird! Es ist nur UB!Antworten:
Willkommen beim besten Freund eines jeden C / C ++ - Programmierers: Undefiniertes Verhalten .
Es gibt eine Menge, die aus verschiedenen Gründen nicht im Sprachstandard festgelegt ist. Dies ist einer von ihnen.
Im Allgemeinen kann alles passieren , wenn Sie auf undefiniertes Verhalten stoßen . Die Anwendung kann abstürzen, einfrieren, das CD-ROM-Laufwerk auswerfen oder Dämonen aus der Nase ziehen. Es kann Ihre Festplatte formatieren oder alle Ihre Pornos per E-Mail an Ihre Großmutter senden.
Es kann sogar, wenn Sie wirklich Pech haben, scheinen , richtig zu funktionieren.
Die Sprache sagt einfach, was passieren soll, wenn Sie auf die Elemente innerhalb der Grenzen eines Arrays zugreifen . Es bleibt unbestimmt, was passiert, wenn Sie Grenzen überschreiten. Es scheint heute auf Ihrem Compiler zu funktionieren, aber es ist kein legales C oder C ++, und es gibt keine Garantie dafür, dass es beim nächsten Ausführen des Programms weiterhin funktioniert. Oder , dass es nicht überschrieben wesentliche Daten hat auch jetzt, und Sie haben einfach nicht die Probleme, dass es sich auf Grund gehen - noch nicht.
Was die Frage betrifft, warum keine Grenzen überprüft werden, gibt es einige Aspekte bei der Antwort:
std::vector
Klassenvorlage an, die beides erlaubt.operator[]
ist so konzipiert, dass es effizient ist. Der Sprachstandard verlangt nicht, dass er eine Überprüfung der Grenzen durchführt (obwohl er dies auch nicht verbietet). Ein Vektor hat auch dieat()
Elementfunktion, die garantiert eine Grenzüberprüfung durchführt. In C ++ erhalten Sie also das Beste aus beiden Welten, wenn Sie einen Vektor verwenden. Sie erhalten eine Array-ähnliche Leistung ohne Überprüfung der Grenzen, und Sie können den Zugriff mit den Grenzen überprüfen, wenn Sie dies wünschen.quelle
Mit g ++ können Sie die Befehlszeilenoption hinzufügen :
-fstack-protector-all
.In Ihrem Beispiel ergab dies Folgendes:
Es hilft Ihnen nicht wirklich, das Problem zu finden oder zu lösen, aber zumindest der Segfault wird Sie wissen lassen, dass etwas nicht stimmt.
quelle
g ++ sucht nicht nach Array-Grenzen, und Sie überschreiben möglicherweise etwas mit 3,4, aber nichts wirklich Wichtiges. Wenn Sie es mit höheren Zahlen versuchen, kommt es zu einem Absturz.
Sie überschreiben nur Teile des Stapels, die nicht verwendet werden. Sie können fortfahren, bis Sie das Ende des zugewiesenen Speicherplatzes für den Stapel erreicht haben und dieser schließlich abstürzt
BEARBEITEN: Sie haben keine Möglichkeit, damit umzugehen. Vielleicht könnte ein statischer Code-Analysator diese Fehler aufdecken, aber das ist zu einfach. Möglicherweise haben Sie ähnliche (aber komplexere) Fehler, die selbst bei statischen Analysatoren nicht erkannt werden
quelle
Soweit ich weiß, ist es undefiniertes Verhalten. Führen Sie damit ein größeres Programm aus, das irgendwo auf dem Weg abstürzt. Die Überprüfung von Grenzen ist kein Teil von Raw-Arrays (oder sogar von std :: vector).
Verwenden
std::vector::iterator
Sie stattdessen std :: vector mit 's, damit Sie sich darüber keine Sorgen machen müssen.Bearbeiten:
Führen Sie dies nur zum Spaß aus und sehen Sie, wie lange es dauert, bis Sie abstürzen:
Edit2:
Lass das nicht laufen.
Edit3:
OK, hier ist eine kurze Lektion zu Arrays und ihren Beziehungen zu Zeigern:
Wenn Sie die Array-Indizierung verwenden, verwenden Sie tatsächlich einen verkleideten Zeiger (als "Referenz" bezeichnet), der automatisch dereferenziert wird. Aus diesem Grund gibt Array [1] anstelle von * (Array [1]) automatisch den Wert mit diesem Wert zurück.
Wenn Sie einen Zeiger auf ein Array haben, wie folgt:
Dann zerfällt das "Array" in der zweiten Deklaration wirklich in einen Zeiger auf das erste Array. Dies entspricht dem folgenden Verhalten:
Wenn Sie versuchen, über das zugewiesene Maß hinaus zuzugreifen, verwenden Sie wirklich nur einen Zeiger auf einen anderen Speicher (über den sich C ++ nicht beschwert). Wenn ich mein Beispielprogramm oben nehme, ist das gleichbedeutend mit:
Der Compiler wird sich nicht beschweren, da Sie beim Programmieren häufig mit anderen Programmen, insbesondere dem Betriebssystem, kommunizieren müssen. Dies geschieht ziemlich oft mit Zeigern.
quelle
Hinweis
Wenn Sie schnell Constraint Größe Arrays mit Bereich Fehlerprüfung haben wollen, versuchen Sie es mit boost :: array (auch std :: tr1 :: Array aus
<tr1/array>
wird es Standard - Container in der nächsten C ++ Spezifikation). Es ist viel schneller als std :: vector. Es reserviert Speicher auf dem Heap oder innerhalb der Klasseninstanz, genau wie int array [].Dies ist ein einfacher Beispielcode:
Dieses Programm druckt:
quelle
Sie überschreiben sicherlich Ihren Stack, aber das Programm ist so einfach, dass die Auswirkungen davon unbemerkt bleiben.
quelle
C oder C ++ überprüfen nicht die Grenzen eines Array-Zugriffs.
Sie ordnen das Array dem Stapel zu. Das Indizieren des Arrays über
array[3]
entspricht *(array + 3)
, wobei das Array ein Zeiger auf & array [0] ist. Dies führt zu undefiniertem Verhalten.Eine Möglichkeit, dies manchmal in C zu erfassen, besteht darin, einen statischen Prüfer wie eine Schiene zu verwenden . Wenn du läufst:
auf,
dann erhalten Sie die Warnung:
quelle
Führen Sie dies durch Valgrind und Sie sehen möglicherweise einen Fehler.
Wie Falaina betonte, erkennt valgrind nicht viele Fälle von Stapelbeschädigung. Ich habe die Probe gerade unter valgrind ausprobiert und sie meldet tatsächlich keine Fehler. Valgrind kann jedoch hilfreich sein, um viele andere Arten von Speicherproblemen zu finden. In diesem Fall ist es nur dann besonders nützlich, wenn Sie Ihr Bulid so ändern, dass es die Option --stack-check enthält. Wenn Sie das Beispiel als erstellen und ausführen
valgrind wird einen Fehler melden.
quelle
Undefiniertes Verhalten zu Ihren Gunsten. Was auch immer Sie an Erinnerung haben, es ist anscheinend nicht wichtig. Beachten Sie, dass C und C ++ keine Begrenzungsprüfungen für Arrays durchführen, sodass solche Dinge beim Kompilieren oder Ausführen nicht abgefangen werden.
quelle
Wenn Sie das Array mit initialisieren
int array[2]
, wird Platz für 2 Ganzzahlen zugewiesen. aber der Bezeichner zeigtarray
einfach auf den Anfang dieses Raumes. Wenn Sie dann aufarray[3]
und zugreifenarray[4]
, erhöht der Compiler diese Adresse einfach, um zu zeigen, wo sich diese Werte befinden würden, wenn das Array lang genug wäre.array[42]
Wenn Sie versuchen, auf etwas zuzugreifen, ohne es zuerst zu initialisieren, erhalten Sie den Wert, der sich an dieser Stelle bereits im Speicher befindet.Bearbeiten:
Weitere Informationen zu Zeigern / Arrays: http://home.netcom.com/~tjensen/ptr/pointers.htm
quelle
wenn Sie int array [2] deklarieren; Sie reservieren 2 Speicherplätze mit jeweils 4 Bytes (32-Bit-Programm). Wenn Sie Array [4] in Ihren Code eingeben, entspricht dies immer noch einem gültigen Aufruf, aber nur zur Laufzeit wird eine nicht behandelte Ausnahme ausgelöst. C ++ verwendet die manuelle Speicherverwaltung. Dies ist tatsächlich eine Sicherheitslücke, die zum Hacken von Programmen verwendet wurde
Dies kann zum Verständnis beitragen:
int * somepointer;
Somepointer [0] = Somepointer [5];
quelle
Soweit ich weiß, werden lokale Variablen auf dem Stapel zugewiesen. Wenn Sie also die Grenzen Ihres eigenen Stapels überschreiten, können Sie nur eine andere lokale Variable überschreiben, es sei denn, Sie gehen zu weit und überschreiten Ihre Stapelgröße. Da Sie keine anderen Variablen in Ihrer Funktion deklariert haben, verursacht dies keine Nebenwirkungen. Versuchen Sie, eine andere Variable / ein anderes Array direkt nach Ihrer ersten zu deklarieren, und sehen Sie, was damit passieren wird.
quelle
Wenn Sie 'array [index]' in C schreiben, wird es in Maschinenanweisungen übersetzt.
Die Übersetzung geht ungefähr so:
Das Ergebnis adressiert etwas, das Teil des Arrays sein kann oder nicht. Als Gegenleistung für die rasante Geschwindigkeit von Maschinenanweisungen verlieren Sie das Sicherheitsnetz des Computers, der die Dinge für Sie überprüft. Wenn Sie akribisch und vorsichtig sind, ist das kein Problem. Wenn Sie schlampig sind oder einen Fehler machen, werden Sie verbrannt. Manchmal kann eine ungültige Anweisung generiert werden, die eine Ausnahme verursacht, manchmal nicht.
quelle
Ein netter Ansatz, den ich oft gesehen habe und den ich tatsächlich verwendet habe, ist das Einfügen eines Elements vom Typ NULL (oder eines erstellten Elements
uint THIS_IS_INFINITY = 82862863263;
) am Ende des Arrays.Bei der Überprüfung der Schleifenbedingungen
TYPE *pagesWords
befindet sich dann eine Art Zeigerarray:Diese Lösung sagt nichts, wenn das Array mit
struct
Typen gefüllt ist .quelle
Wie jetzt in der Frage erwähnt, löst die Verwendung von std :: vector :: at das Problem und führt vor dem Zugriff eine gebundene Prüfung durch.
Wenn Sie ein Array mit konstanter Größe benötigen, das sich als erster Code auf dem Stapel befindet, verwenden Sie den neuen C ++ 11-Container std :: array. Als Vektor gibt es die Funktion std :: array :: at. Tatsächlich existiert die Funktion in allen Standardcontainern, in denen sie eine Bedeutung hat, dh wo Operator [] definiert ist: (deque, map, unordered_map) mit Ausnahme von std :: bitset, in dem sie std :: bitset heißt: :Prüfung.
quelle
libstdc ++, das Teil von gcc ist, verfügt über einen speziellen Debug-Modus zur Fehlerprüfung. Es wird durch das Compiler-Flag aktiviert
-D_GLIBCXX_DEBUG
. Unter anderem wirdstd::vector
auf Kosten der Leistung geprüft . Hier ist eine Online-Demo mit der neuesten Version von gcc.Sie können also im libstdc ++ - Debug-Modus Grenzen überprüfen, dies sollten Sie jedoch nur beim Testen tun, da dies im Vergleich zum normalen libstdc ++ - Modus eine bemerkenswerte Leistung kostet.
quelle
Wenn Sie Ihr Programm leicht ändern:
(Änderungen in Großbuchstaben - setzen Sie diese in Kleinbuchstaben, wenn Sie dies versuchen möchten.)
Sie werden sehen, dass die Variable foo verworfen wurde. Ihr Code wird Werte speichern in die nicht vorhandene Array [3] und array [4], und in der Lage sein , sie richtig zu holen, aber die tatsächliche Speicherung verwendet wird aus seinen foo .
Sie können also "davonkommen", indem Sie die Grenzen des Arrays in Ihrem ursprünglichen Beispiel überschreiten, jedoch auf Kosten der Verursachung von Schäden an anderer Stelle - Schäden, die sich als sehr schwer zu diagnostizieren erweisen können .
Warum es keine automatische Begrenzungsprüfung gibt - ein korrekt geschriebenes Programm benötigt es nicht. Sobald dies geschehen ist, gibt es keinen Grund, die Laufzeitgrenzen zu überprüfen, und dies würde das Programm nur verlangsamen. Am besten, Sie finden das alles beim Entwerfen und Codieren heraus.
C ++ basiert auf C, das so konzipiert wurde, dass es der Assemblersprache so nahe wie möglich kommt.
quelle