webentwicklung-frage-antwort-db.com.de

Der Zugriff auf ein Array außerhalb der Grenzen ist fehlerfrei. Warum?

Ich weise Werte in einem C++ - Programm außerhalb der Grenzen wie folgt 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 verwende g ++ 4.3.3

Hier ist der Befehl zum Kompilieren und Ausführen

$ g++ -W -Wall errorRange.cpp -o errorRange
$ ./errorRange
3
4

Nur bei der Zuweisung von array[3000]=3000 wird ein Segmentierungsfehler angezeigt.

Wenn gcc nicht nach Array-Grenzen sucht, wie kann ich dann sicher sein, ob mein Programm korrekt ist, da es später zu ernsthaften Problemen kommen 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 ist auch kein fehler. 

140

Willkommen beim besten Freund eines jeden C/C++ - Programmierers: ndefiniertes Verhalten.

Es gibt aus einer Vielzahl von Gründen eine Menge, die vom Sprachstandard nicht spezifiziert wird. Das ist einer von ihnen.

Im Allgemeinen kann bei undefiniertem Verhalten alles passieren. 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 vorkommen, dass angezeigt wird , wenn Sie wirklich Pech haben.

Die Sprache sagt einfach, was passieren soll, wenn Sie auf die Elemente innerhalb der Grenzen eines Arrays zugreifen. Es bleibt undefiniert, was passiert, wenn Sie die Grenzen überschreiten. Es könnte scheinen auf Ihrem Compiler heute zu funktionieren, aber es ist nicht legal C oder C++, und es gibt keine Garantie, dass es immer noch funktionieren wird Wenn Sie das nächste Mal das Programm ausführen. Oder dass es wesentliche Daten auch jetzt noch nicht überschrieben hat und Sie nur nicht auf die Probleme gestoßen sind, die es verursachen wird - bis jetzt.

Was betrifft, warum keine Grenzen überprüft werden, gibt es ein paar Aspekte bei der Antwort:

  • Ein Array ist ein Überbleibsel von C. C-Arrays sind so primitiv wie möglich. Nur eine Folge von Elementen mit zusammenhängenden Adressen. Es gibt keine Grenzen zu überprüfen, weil es nur rohen Speicher verfügbar macht. Die Implementierung eines robusten Grenzwertprüfungsmechanismus wäre in C fast unmöglich gewesen.
  • In C++ ist die Überprüfung von Grenzen für Klassentypen möglich. Aber ein Array ist immer noch das alte C-kompatible. Es ist keine Klasse. Darüber hinaus basiert C++ auch auf einer anderen Regel, die die Überprüfung von Grenzen nicht ideal macht. Das C++ - Leitprinzip lautet "Sie zahlen nicht für das, was Sie nicht nutzen". Wenn Ihr Code korrekt ist, müssen Sie die Grenzen nicht überprüfen, und Sie sollten nicht gezwungen sein, den Aufwand für die Laufzeitüberprüfung der Grenzen zu bezahlen.
  • Daher bietet C++ die Klassenvorlage std::vector an, die beide zulässt. operator[] ist auf Effizienz ausgelegt. Der Sprachstandard verlangt nicht, dass er eine Begrenzungsprüfung durchführt (obwohl er es auch nicht verbietet). Ein Vektor hat auch die Member-Funktion at(), mit der garantiert wird, dass die Grenzen überprüft werden. In C++ erhalten Sie also das Beste aus beiden Welten, wenn Sie einen Vektor verwenden. Sie erhalten eine Array-ähnliche Leistung, ohne die Grenzen überprüfen zu müssen. und Sie haben die Möglichkeit, den Zugriff auf die Grenzen zu verwenden, wenn Sie dies wünschen.
313
jalf

Mit g ++ können Sie die Befehlszeilenoption hinzufügen: -fstack-protector-all.

An Ihrem Beispiel ergab sich folgendes:

> g++ -o t -fstack-protector-all t.cc
> ./t
3
4
/bin/bash: line 1: 15450 Segmentation fault      ./t

Es hilft nicht wirklich, das Problem zu finden oder zu lösen, aber der Segfault lässt Sie wissen, dass etwas falsch ist.

28
Richard Corden

g ++ prüft nicht auf Arraygrenzen und überschreibt möglicherweise etwas mit 3,4, aber nichts wirklich Wichtiges. Wenn Sie mit höheren Zahlen versuchen, werden Sie abstürzen.

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, der schließlich abstürzt

EDIT: 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 für statische Analysegeräte nicht erkannt wurden

11
Arkaitz Jimenez

Es ist undefiniertes Verhalten, soweit ich weiß. Wenn Sie ein größeres Programm ausführen, wird es irgendwo auf dem Weg abstürzen. Die Überprüfung von Bounds ist nicht Teil von rohen Arrays (oder sogar von std :: vector).

Verwenden Sie stattdessen std :: vector mit std::vector::iterator, damit Sie sich nicht darum kümmern müssen.

Bearbeiten: 

Führe dies aus Spaß aus und schau, wie lange es dauert, bis du abstürzt:

int main()
{
   int array[1];

   for (int i = 0; i != 100000; i++)
   {
       array[i] = i;
   }

   return 0; //will be lucky to ever reach this
}

Edit2: 

Lass das nicht laufen.

Edit3:

OK, hier ist eine kurze Lektion über Arrays und ihre Beziehungen zu Zeigern:

Wenn Sie die Array-Indizierung verwenden, verwenden Sie tatsächlich einen verdeckten Zeiger (als "Referenz" bezeichnet), der automatisch dereferenziert wird. Deshalb 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:

int array[5];
int *ptr = array;

Dann verfällt das "Array" in der zweiten Deklaration wirklich auf einen Zeiger auf das erste Array. Dieses Verhalten entspricht diesem Verhalten:

int *ptr = &array[0];

Wenn Sie versuchen, über das, was Sie zugewiesen haben, zuzugreifen, verwenden Sie eigentlich nur einen Zeiger auf anderen Speicher (über den sich C++ nicht beklagen kann). Wenn Sie mein Beispielprogramm oben verwenden, entspricht dies dem folgenden:

int main()
{
   int array[1];
   int *ptr = array;

   for (int i = 0; i != 100000; i++, ptr++)
   {
       *ptr++ = i;
   }

   return 0; //will be lucky to ever reach this
}

Der Compiler wird sich nicht beschweren, da Sie beim Programmieren häufig mit anderen Programmen kommunizieren müssen, insbesondere mit dem Betriebssystem. Dies geschieht ziemlich viel mit Zeigern. 

7
jkeys

Hinweis

Wenn Sie Arrays mit schneller Einschränkungsgröße mit Bereichsfehlerprüfung wünschen, versuchen Sie es mit boost :: array , (auch std :: tr1 :: array von <tr1/array> wird der Standardcontainer in der nächsten C++ - Spezifikation sein). . Es ist viel schneller als std :: vector. Es reserviert Speicher auf Heap- oder in einer Klasseninstanz, genau wie int array [].
Dies ist ein einfacher Beispielcode:

#include <iostream>
#include <boost/array.hpp>
int main()
{
    boost::array<int,2> array;
    array.at(0) = 1; // checking index is inside range
    array[1] = 2;    // no error check, as fast as int array[2];
    try
    {
       // index is inside range
       std::cout << "array.at(0) = " << array.at(0) << std::endl;

       // index is outside range, throwing exception
       std::cout << "array.at(2) = " << array.at(2) << std::endl; 

       // never comes here
       std::cout << "array.at(1) = " << array.at(1) << std::endl;  
    }
    catch(const std::out_of_range& r)
    {
        std::cout << "Something goes wrong: " << r.what() << std::endl;
    }
    return 0;
}

Dieses Programm wird drucken:

array.at(0) = 1
Something goes wrong: array<>: index out of range
5
Arpegius

Sie überschreiben zwar Ihren Stack, aber das Programm ist so einfach, dass die Auswirkungen davon nicht bemerkt werden.

3
Paul Dixon

C oder C++ prüfen nicht die Grenzen eines Array-Zugriffs.

Sie ordnen das Array auf dem Stapel zu. Die Indizierung des Arrays über array[3] entspricht * (array + 3), wobei array ein Zeiger auf & array [0] ist. Dies führt zu undefiniertem Verhalten.

Eine Möglichkeit, dies manchmal in C zu erfassen, besteht in der Verwendung eines statischen Prüfers wie Splint . Wenn du läufst:

splint +bounds array.c

auf,

int main(void)
{
    int array[1];

    array[1] = 1;

    return 0;
}

dann erhalten Sie die Warnung:

array.c: (in der Funktion main) array.c: 5: 9: Wahrscheinlich außerhalb der Grenzen Geschäft: Array [1] Constraint kann nicht aufgelöst werden: erfordert 0> = 1 Voraussetzung zur Erfüllung der Voraussetzungen: erfordert maxSet (array @ array.c: 5: 9)> = 1 Ein Speicherschreibvorgang kann Schreiben Sie an eine Adresse jenseits von zugewiesener Puffer.

3
Karl Voigtland

Führen Sie dies durch Valgrind aus und möglicherweise wird ein Fehler angezeigt.

Wie Falaina darauf hinweist, erkennt valgrind nicht viele Fälle von Stack-Korruption. Ich habe gerade das Beispiel unter valgrind ausprobiert, und es werden tatsächlich Null Fehler gemeldet. Valgrind kann jedoch dazu beitragen, viele andere Arten von Speicherproblemen zu finden. In diesem Fall ist es nicht besonders nützlich, wenn Sie nicht Ihre Bulid so ändern, dass sie die Option --stack-check enthält. Wenn Sie das Beispiel erstellen und ausführen 

g++ --stack-check -W -Wall errorRange.cpp -o errorRange
valgrind ./errorRange

valgrind will einen Fehler melden.

3
Todd Stout

Undefiniertes Verhalten, das zu Ihren Gunsten funktioniert. Was auch immer die Erinnerung an Sie ist, die anscheinend verrückt ist, enthält nichts Wichtiges. Beachten Sie, dass C und C++ keine Begrenzungen für Arrays überprüfen. Daher werden solche Elemente beim Kompilieren oder während der Laufzeit nicht abgefangen. 

2
John Bode

Wenn Sie das Array mit int array[2] initialisieren, wird Platz für 2 Ganzzahlen zugewiesen. Der Bezeichner array zeigt jedoch einfach auf den Anfang dieses Bereichs. Wenn Sie dann auf array[3] und array[4] zugreifen, inkrementiert der Compiler diese Adresse einfach so, dass sie auf den Wert dieser Werte zeigt, wenn das Array lang genug wäre. Versuchen Sie, auf etwas wie array[42] zuzugreifen, ohne es vorher zu initialisieren. Am Ende erhalten Sie den Wert, der sich an diesem Ort bereits im Speicher befand.

Bearbeiten:

Weitere Informationen zu Zeigern/Arrays: http://home.netcom.com/~tjensen/ptr/pointers.htm

1
Nathan Clark

Wie ich es verstehe, werden lokale Variablen auf Stapel zugewiesen, sodass das Überschreiten der Grenzen Ihres eigenen Stacks nur andere lokale Variablen überschreiben kann, es sei denn, Sie gehen zu viel und überschreiten Ihre Stackgröße in Ihrer Funktion - es verursacht keine Nebenwirkungen. Deklarieren Sie gleich nach der ersten eine andere Variable/ein Array und sehen Sie, was damit passiert.

0
Vorber

wenn Sie ein int-Array [2] deklarieren; Sie reservieren 2 Speicherplätze von jeweils 4 Bytes (32-Bit-Programm) . Wenn Sie in Ihr Code array [4] eingeben, entspricht es immer noch einem gültigen Aufruf, aber nur zur Laufzeit wird eine unbehandelte Ausnahme ausgelöst. C++ verwendet die manuelle Speicherverwaltung. Dies ist tatsächlich ein Sicherheitsfehler, der für das Hacken von Programmen verwendet wurde

dies kann zum Verständnis beitragen:

int * somepointer; 

somepointer [0] = somepointer [5];

0
yan bellavance

Wenn Sie 'array [index]' in C schreiben, wird es in Maschinenanweisungen übersetzt.

Die Übersetzung lautet so etwas wie: 

  1. 'erhalte die Adresse des Arrays'
  2. 'Ermitteln Sie die Größe des Objekttyps, aus dem das Array besteht'
  3. 'Multipliziere die Größe des Typs mit dem Index'
  4. 'addiere das Ergebnis zur Adresse des Arrays'
  5. "Lesen Sie, was sich an der resultierenden Adresse befindet"

Das Ergebnis adressiert etwas, das Teil des Arrays sein kann oder nicht. Als Gegenleistung für die rasante Geschwindigkeit der Maschinenanweisungen verlieren Sie das Sicherheitsnetz des Computers, indem Sie die Dinge für Sie prüfen. Wenn Sie akribisch und vorsichtig sind, ist das kein Problem. Wenn Sie schlampig sind oder einen Fehler machen, werden Sie verbrannt. Manchmal generiert es möglicherweise eine ungültige Anweisung, die eine Ausnahme verursacht, manchmal nicht.

0
Jay

Wenn Sie Ihr Programm leicht ändern:

#include <iostream>
using namespace std;
int main()
{
    int array[2];
    INT NOTHING;
    CHAR FOO[4];
    STRCPY(FOO, "BAR");
    array[0] = 1;
    array[1] = 2;
    array[3] = 3;
    array[4] = 4;
    cout << array[3] << endl;
    cout << array[4] << endl;
    COUT << FOO << ENDL;
    return 0;
}

(Ä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 will speichert Werte im nicht vorhandenen Array [3] und Array [4] und kann sie ordnungsgemäß abrufen. Der tatsächlich verwendete Speicherplatz wird jedoch aus foo stammen.

Sie können also mit dem Überschreiten der Grenzen des Arrays in Ihrem ursprünglichen Beispiel "davonkommen", aber auf Kosten anderer Schäden - Schäden, die sich als sehr schwer diagnostizieren lassen.

Warum gibt es keine automatische Begrenzungsprüfung - ein korrekt geschriebenes Programm benötigt es nicht. Sobald dies geschehen ist, gibt es keinen Grund mehr, Laufzeit-Begrenzungsprüfungen durchzuführen, und dies würde das Programm nur verlangsamen. Am besten, das alles während des Designs und der Codierung herauszufinden.

C++ basiert auf C, das so nah wie möglich an die Assemblersprache gebracht wurde.

0
Jennifer

Ein schöner Ansatz, den ich oft gesehen habe und der eigentlich verwendet wurde, ist, ein NULL-Element (oder ein erstelltes Element wie uint THIS_IS_INFINITY = 82862863263;) am Ende des Arrays einzufügen.

Bei der Überprüfung der Schleifenbedingung ist TYPE *pagesWords eine Art Zeigerarray:

int pagesWordsLength = sizeof(pagesWords) / sizeof(pagesWords[0]);

realloc (pagesWords, sizeof(pagesWords[0]) * (pagesWordsLength + 1);

pagesWords[pagesWordsLength] = MY_NULL;

for (uint i = 0; i < 1000; i++)
{
  if (pagesWords[i] == MY_NULL)
  {
    break;
  }
}

Diese Lösung wird nicht in Word formatiert, wenn das Array mit struct-Typen gefüllt ist.

0
xudre

Wie bereits in der Frage erwähnt, wird std :: vector :: at das Problem lösen und vor dem Zugriff eine gebundene Prüfung durchführen.

Wenn Sie ein Array mit konstanter Größe benötigen, das sich als erster Code auf dem Stapel befindet, verwenden Sie den neuen Container std :: array von C++ 11. Als Vektor gibt es std :: array :: at function. Tatsächlich ist die Funktion in allen Standardcontainern vorhanden, in denen sie eine Bedeutung hat, dh in denen Operator [] definiert ist: (deque, map, unordered_map), mit Ausnahme von std :: bitset, in dem sie std :: bitset heißt: :Prüfung.

0

libstdc ++, ein Teil von gcc, verfügt über einen speziellen debug-Modus zur Fehlerprüfung. Es wird durch das Compiler-Flag -D_GLIBCXX_DEBUG aktiviert. Unter anderem wird std::vector auf Kosten der Performance geprüft. Hier ist Online-Demo mit der neuesten Version von gcc.

Sie können also mit dem libstdc ++ - Debug-Modus eine Begrenzungsprüfung durchführen, sollten Sie dies jedoch nur beim Testen tun, da dies im Vergleich zum normalen libstdc ++ - Modus eine bemerkenswerte Leistung kostet.

0
ks1322