2017-01-27 1 views
1

Ich schrieb ein sehr triviales Programm, um das undefinierte Verhalten von Pufferüberläufen zu untersuchen. Insbesondere was passiert, wenn Sie Daten außerhalb des zugewiesenen Speicherplatzes lesen.Undefined Verhaltenskurve: Lesen außerhalb eines Puffers führt dazu, dass eine Schleife niemals beendet wird?

#include <iostream> 
#include<iomanip> 

int main() { 
    int values[10]; 
    for (int i = 0; i < 10; i++) { 
     values[i] = i; 
    } 

    std::cout << values << " "; 
    std::cout << std::endl; 
    for (int i = 0; i < 11; i++) { 
     //UB occurs here when values[i] is executed with i == 10 
     std::cout << std::setw(2) << i << "(" << (values + i) << "): " << values[i] << std::endl; 
    } 
    system("pause"); 
    return 0; 
} 

Wenn ich dieses Programm auf Visual Studio ausgeführt werden, sind die Ergebnisse nicht sonderlich überraschend: Index Lesen 10 produziert Müll:

000000000025FD70 
0(000000000025FD70): 0 
1(000000000025FD74): 1 
2(000000000025FD78): 2 
3(000000000025FD7C): 3 
4(000000000025FD80): 4 
5(000000000025FD84): 5 
6(000000000025FD88): 6 
7(000000000025FD8C): 7 
8(000000000025FD90): 8 
9(000000000025FD94): 9 
10(000000000025FD98): -1966502944 
Press any key to continue . . . 

Aber wenn ich dieses Programm in Ideone.com Online-Compiler zugeführt werden, I got extremely bizarre behavior:

0xff8cac48 
0(0xff8cac48): 0 
1(0xff8cac4c): 1 
2(0xff8cac50): 2 
3(0xff8cac54): 3 
4(0xff8cac58): 4 
5(0xff8cac5c): 5 
6(0xff8cac60): 6 
7(0xff8cac64): 7 
8(0xff8cac68): 8 
9(0xff8cac6c): 9 
10(0xff8cac70): 1 
11(0xff8cac74): -7557836 
12(0xff8cac78): -7557984 
13(0xff8cac7c): 1435443200 
14(0xff8cac80): 0 
15(0xff8cac84): 0 
16(0xff8cac88): 0 
17(0xff8cac8c): 1434052387 
18(0xff8cac90): 134515248 
19(0xff8cac94): 0 
20(0xff8cac98): 0 
21(0xff8cac9c): 1434052387 
22(0xff8caca0): 1 
23(0xff8caca4): -7557836 
24(0xff8caca8): -7557828 
25(0xff8cacac): 1432254426 
26(0xff8cacb0): 1 
27(0xff8cacb4): -7557836 
28(0xff8cacb8): -7557932 
29(0xff8cacbc): 134520132 
30(0xff8cacc0): 134513420 
31(0xff8cacc4): 1435443200 
32(0xff8cacc8): 0 
33(0xff8caccc): 0 
34(0xff8cacd0): 0 
35(0xff8cacd4): 346972086 
36(0xff8cacd8): -29697309 
37(0xff8cacdc): 0 
38(0xff8cace0): 0 
39(0xff8cace4): 0 
40(0xff8cace8): 1 
41(0xff8cacec): 134514984 
42(0xff8cacf0): 0 
43(0xff8cacf4): 1432277024 
44(0xff8cacf8): 1434052153 
45(0xff8cacfc): 1432326144 
46(0xff8cad00): 1 
47(0xff8cad04): 134514984 
... 
//The heck?! This just ends with a Runtime Error after like 200 lines. 

So offenbar mit ihrem Compiler, durch einen einzigen Index der Puffer Überholkupplung bewirkt, dass das Programm in eine Endlosschleife zu betreten!

Nun zu wiederholen: Ich merke, dass ich hier mit undefiniertem Verhalten. Aber trotzdem würde ich gerne wissen, was in der Welt hinter den Kulissen passiert, um das zu verursachen. Der Code, der den Pufferüberlauf physisch durchführt, führt immer noch einen Lesevorgang von 4 Bytes durch und schreibt alles, was er liest, in einen (vermutlich besser geschützten) Puffer. Was macht der Compiler/CPU, der diese Probleme verursacht?

+2

Wenn ein Programm UB anzeigt, können Sie sein Verhalten nicht sinnvoll begründen. –

+0

@NeilButterworth Was ist, wenn ich * lessly * Grund über sein Verhalten verwenden möchte? = D – Xirema

+2

Fragen sollten wirklich für zukünftige Lesevorgänge nützlich sein. Ich bemühe mich, darin einen Nutzen zu sehen. – NathanOliver

Antwort

4

Es gibt zwei Ausführungspfade, die zu der Bedingung i < 11 führen, die ausgewertet wird.

Die erste ist vor der ersten Schleife Iteration. Da i unmittelbar vor der Überprüfung auf 0 initialisiert wurde, ist dies trivialerweise wahr.

Die zweite ist nach einer erfolgreichen Schleife Iteration. Da die Schleifeniteration values[i] verursacht hat und values nur 10 Elemente hat, kann dies nur gültig sein, wenn i < 10. Und wenn i < 10, nach i++, müssen i < 11 auch wahr sein.

Dies ist, was Ideone Compiler (GCC) erkennt. Es gibt keine Möglichkeit, die Bedingung i < 11 kann jemals falsch sein, es sei denn, Sie haben ein ungültiges Programm, daher kann es weg optimiert werden. Zur gleichen Zeit, Ihr Compiler nicht aus dem Weg zu überprüfen, ob Sie ein ungültiges Programm haben, es sei denn, Sie bieten zusätzliche Optionen, um es zu sagen (wie -fsanitize=undefined in GCC/Clang).

Dies ist ein Kompromiss aus Implementierungen müssen. Sie können verständliches Verhalten für ungültige Programme bevorzugen oder sie können rohe Geschwindigkeit für gültige Programme bevorzugen. Oder eine Mischung aus beidem. GCC konzentriert sich definitiv auf letzteres, zumindest standardmäßig.

+1

Ich denke, in diesem speziellen Fall könnte Compiler warnen anstatt aggressiv zu optimieren. Optimierungen, die in diesem Fall durchgeführt werden müssen, sind eindeutig und ergeben ein widersprüchliches Programm (an einem Punkt muss angenommen werden, dass 10 + 1 kleiner als 11 ist). Zugegeben, die Warnung wird nicht vom Standard vorgeschrieben, aber auch die Optimierung, und die Warnung würde dem Interesse der Benutzer besser dienen. – SergeyA

+0

Ich habe ein paar Tests mit Godbolt gemacht und es sieht so aus, als ob deine Erklärung richtig ist: wenn der [Check Wert 11] (https://godbolt.org/g/FrCPVN) ist, entfernt der Compiler die Assembly, die mit dem Sprung aus der Schleife, während [mit einem Wert von 10] (https://godbolt.org/g/oQJR0q) die beiden Anweisungen zurück hinzugefügt werden. – Xirema

+0

Ich stimme dir zu @SergeyA: Der Compiler ist so intelligent zu verstehen, dass die Bedingung, die der Programmierer schrieb, immer wahr ist: vor der Optimierung sollte es den armen Programmierer warnen "Sie wollen wirklich eine Bedingung überprüfen, die immer wahr ist?" –

Verwandte Themen