Ich arbeite mit einem Compiler für einen DSP-Chip, der absichtlich Code generiert, der über das Ende eines Arrays hinaus auf einen C-Code zugreift, der dies nicht tut!
Dies liegt daran, dass die Schleifen so strukturiert sind, dass am Ende einer Iteration einige Daten für die nächste Iteration vorab abgerufen werden. Das am Ende der letzten Iteration vorab abgerufene Datum wird also nie tatsächlich verwendet.
Das Schreiben eines solchen C-Codes ruft undefiniertes Verhalten hervor, aber das ist nur eine Formalität aus einem Standarddokument, das sich mit maximaler Portabilität befasst.
Meistens ist ein Programm, das außerhalb der Grenzen zugreift, nicht geschickt optimiert. Es ist einfach fehlerhaft. Der Code abruft einige Müll - Wert und, im Gegensatz zu den optimierten Schleifen der eingangs genannten Compiler, der Code dann verwendet , um den Wert in nachfolgenden Berechnungen, um dadurch korrumpieren theim.
Es lohnt sich, solche Fehler zu erkennen, und es lohnt sich, das Verhalten auch nur aus diesem Grund undefiniert zu machen: Damit die Laufzeit eine Diagnosemeldung wie "Array-Überlauf in Zeile 42 von main.c" erzeugen kann.
Auf Systemen mit virtuellem Speicher kann ein Array so zugewiesen werden, dass sich die folgende Adresse in einem nicht zugeordneten Bereich des virtuellen Speichers befindet. Der Zugriff bombardiert dann das Programm.
Beachten Sie außerdem, dass wir in C einen Zeiger erstellen dürfen, der hinter dem Ende eines Arrays liegt. Und dieser Zeiger muss mehr als jeder andere Zeiger mit dem Inneren eines Arrays vergleichen. Dies bedeutet, dass eine C-Implementierung ein Array nicht direkt am Ende des Speichers platzieren kann, wo die Plus-Adresse umlaufen und kleiner aussehen würde als andere Adressen im Array.
Trotzdem ist der Zugriff auf nicht initialisierte oder außerhalb der Grenzen liegende Werte manchmal eine gültige Optimierungstechnik, auch wenn sie nicht maximal portabel ist. Dies ist beispielsweise der Grund, warum das Valgrind-Tool keine Zugriffe auf nicht initialisierte Daten meldet, wenn diese Zugriffe stattfinden, sondern nur, wenn der Wert später auf eine Weise verwendet wird, die das Ergebnis des Programms beeinflussen könnte. Sie erhalten eine Diagnose wie "Bedingter Zweig in xxx: nnn hängt vom nicht initialisierten Wert ab" und es kann manchmal schwierig sein, den Ursprung zu ermitteln. Wenn alle derartigen Zugriffe sofort abgefangen würden, würde es viele falsch positive Ergebnisse geben, die sich aus compileroptimiertem Code sowie korrekt handoptimiertem Code ergeben.
Apropos, ich habe mit einem Codec eines Anbieters gearbeitet, der diese Fehler ausgab, wenn er auf Linux portiert und unter Valgrind ausgeführt wurde. Aber der Verkäufer hat mich davon überzeugt, dass nur einige BitsDer verwendete Wert stammte tatsächlich aus dem nicht initialisierten Speicher, und diese Bits wurden von der Logik sorgfältig vermieden. Es wurden nur die guten Bits des Werts verwendet, und Valgrind kann nicht auf das einzelne Bit zurückgreifen. Das nicht initialisierte Material stammt aus dem Lesen eines Wortes nach dem Ende eines Bitstroms codierter Daten, aber der Code weiß, wie viele Bits sich im Stream befinden, und verwendet nicht mehr Bits als tatsächlich vorhanden sind. Da der Zugriff über das Ende des Bitstrom-Arrays hinaus keinen Schaden für die DSP-Architektur verursacht (es gibt keinen virtuellen Speicher nach dem Array, keine speicherabgebildeten Ports und die Adresse wird nicht umbrochen), handelt es sich um eine gültige Optimierungstechnik.
"Undefiniertes Verhalten" bedeutet nicht wirklich viel, denn laut ISO C sind das einfache Einfügen eines Headers, der nicht im C-Standard definiert ist, oder das Aufrufen einer Funktion, die nicht im Programm selbst oder im C-Standard definiert ist, Beispiele für undefiniertes Verhalten Verhalten. Undefiniertes Verhalten bedeutet nicht "von niemandem auf dem Planeten definiert", sondern nur "nicht von der ISO C-Norm definiert". Aber natürlich manchmal wirklich nicht definiertes Verhalten ist absolut nicht von jedermann definiert.