Verursacht ein Ganzzahlüberlauf aufgrund einer Speicherbeschädigung ein undefiniertes Verhalten?

Lesezeit: 8 Minuten

Benutzeravatar von Vinz
Vinz

Ich habe kürzlich gelesen, dass ein Überlauf von vorzeichenbehafteten Ganzzahlen in C und C++ zu undefiniertem Verhalten führt:

Wenn während der Auswertung eines Ausdrucks das Ergebnis nicht mathematisch definiert ist oder nicht im Bereich darstellbarer Werte für seinen Typ liegt, ist das Verhalten undefiniert.

Ich versuche gerade, den Grund für das undefinierte Verhalten hier zu verstehen. Ich dachte, dass hier undefiniertes Verhalten auftritt, weil die Ganzzahl beginnt, den Speicher um sich herum zu manipulieren, wenn sie zu groß wird, um in den zugrunde liegenden Typ zu passen.

Also beschloss ich, ein kleines Testprogramm in Visual Studio 2015 zu schreiben, um diese Theorie mit dem folgenden Code zu testen:

#include <stdio.h>
#include <limits.h>

struct TestStruct
{
    char pad1[50];
    int testVal;
    char pad2[50];
};

int main()
{
    TestStruct test;
    memset(&test, 0, sizeof(test));

    for (test.testVal = 0; ; test.testVal++)
    {
        if (test.testVal == INT_MAX)
            printf("Overflowing\r\n");
    }

    return 0;
}

Ich habe hier eine Struktur verwendet, um Schutzmaßnahmen von Visual Studio im Debugging-Modus wie das temporäre Auffüllen von Stack-Variablen usw. zu verhindern. Die Endlosschleife sollte mehrere Überläufe von verursachen test.testValund das tut es tatsächlich, allerdings ohne andere Folgen als den Überlauf selbst.

Ich habe mir den Speicherauszug angesehen, während ich die Überlauftests mit folgendem Ergebnis ausgeführt habe (test.testVal hatte eine Speicheradresse von 0x001CFAFC):

0x001CFAE5  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x001CFAFC  94 53 ca d8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

Überlaufende Ganzzahl mit Speicherauszug

Wie Sie sehen, blieb der ständig überlaufende Speicher um das int “unbeschädigt”. Ich habe dies mehrmals mit ähnlicher Ausgabe getestet. Nie wurde irgendein Speicher um das überlaufende int herum beschädigt.

was geschieht hier? Warum wird der Speicher um die Variable herum nicht beschädigt? test.testVal? Wie kann dies zu undefiniertem Verhalten führen?

Ich versuche, meinen Fehler zu verstehen und warum während eines Ganzzahlüberlaufs keine Speicherbeschädigung auftritt.

  • Sie erwarten eine Definition des Verhaltens, das “undefiniert” ist?! Ihnen wird ausdrücklich gesagt, dass es keine vernünftigen Erwartungen gibt, die Sie haben können, daher kann sich das Verhalten möglicherweise nicht von dem unterscheiden, was Sie erwarten dürfen.

    – Kerrek SB

    19. Mai 2016 um 14:01 Uhr


  • Der Ganzzahlüberlauf wirkt sich nicht auf den angrenzenden Speicher aus.

    – Saschoalm

    19. Mai 2016 um 14:01 Uhr

  • @NathanOliver, es schadet nicht, über undefiniertes Verhalten nachzudenken. Ich persönlich finde es sehr nützliche Übung.

    – SergejA

    19. Mai 2016 um 14:04 Uhr

  • @Olaf UB hat einen Grund, und ich versuche, das herauszufinden. Das Bild enthält keinen entscheidenden Teil der Frage, sondern dient eher der grafischen Veranschaulichung meiner Testergebnisse. Alles im Bild, auch der verwendete Code, wurde als Klartext gepostet.

    – Winz

    19. Mai 2016 um 14:08 Uhr

  • Diese Frage abzulehnen ist meiner Meinung nach völlig falsch. OP zeigt tatsächlich einen sehr gesunden Wunsch zu verstehen, anstatt blind zu folgen.

    – SergejA

    19. Mai 2016 um 14:13 Uhr

  • @SergeyA Genau! Ich habe versucht, den Grund für das UB zu verstehen, und vermutete, dass es an einer Speicherbeschädigung während des Überlaufs liegen würde. Jetzt weiß ich, dass es arithmetische Hintergründe hat 🙂 Nochmals vielen Dank, und ich denke nicht, dass die Ablehnungen viel schaden … Ich werde diese Frage nicht löschen, da sie für jemanden hilfreich sein könnte, der genauso denkt wie ich 🙂

    – Winz

    19. Mai 2016 um 14:16 Uhr

  • @JonTrauntvein: C++ ist für mehr als nur moderne Architekturen konzipiert.

    – Martin York

    19. Mai 2016 um 15:35 Uhr

  • @JonTrauntvein Einige DSPs unterstützen Latching-Arithmetik. Das Hinzufügen von 1 zum größten Wert bleibt der größte Wert. Auf diese Weise führt ein Überlauffehler nicht dazu, dass Ihre Rakete um 180 Grad in die gewünschte Richtung geht.

    – Brian Beuning

    19. Mai 2016 um 15:41 Uhr

  • @Vinzenz: Beachten Sie, dass eine bestimmte Implementierung von C (wie MSVC) könnte Definieren Sie, was passiert, wenn eine vorzeichenbehaftete Ganzzahl überläuft (dh garantieren Sie korrektes Verhalten mit Zweierkomplement-Ganzzahlen, da dies von der zugrunde liegenden Hardware unterstützt wird.) Das Schreiben von Code, der davon abhängt, wäre nicht einmal für x86 sicher: Einige Compiler (wie gcc und clang) Tag-Vorteil von UB, um mehr zu optimieren. zB in einer Schleife mit an int Schleifenzähler, der ein Array indiziert, kann der Compiler die Vorzeichenerweiterung von 32b auf 64b bei jeder Iteration überspringen.

    – Peter Cordes

    19. Mai 2016 um 15:54 Uhr


  • Ja, das gilt für mehrere Arten von UB. Das Problem ist, dass Ihre Antwort impliziert, dass die Folgen der UB begrenzt sind. Es scheint zu implizieren, dass die Arithmetik für vorzeichenbehaftete C-Ganzzahlen ein 2er-Komplement auf der 2er-Komplement-Hardware ist, was ist nicht wahr für Compiler, die aggressiv optimieren, wie gcc und clang. Ich denke, dies ist ein wirklich wichtiger Punkt, da die Leute sonst versucht sein werden, sich auf den signierten Überlauf zu verlassen, da sie wissen, dass sie auf die 2er-Komplement-Hardware abzielen. Danke für das Update.

    – Peter Cordes

    19. Mai 2016 um 16:13 Uhr


  • Ein Grund für die Unkonventionalität, die Benutzer nicht immer zu schätzen wissen, während sie auf ihren Compiler fluchen, ist, dass der Compiler nicht mit der Annahme geschrieben wurde, dass Sie absichtlich Code schreiben würden, wobei UB den Compiler erwartet etwas Vernünftiges tun. Vielmehr wird davon ausgegangen, dass, wenn der obige Code angezeigt wird, dies wahrscheinlich das Ergebnis einer Art Grenzfall ist, z. B. vielleicht INT_MAX ist das Ergebnis eines Makros, und so ist es sollte als Spezialfall optimieren. Falls du dich jemals änderst INT_MAX in diesem Code zurück zu etwas, das nicht albern ist, wird es aufhören zu optimieren.

    – Steve Jessop

    19. Mai 2016 um 22:12 Uhr


  • @SteveJessop: Viele Programme könnten fast jede Form von Überlaufverhalten tolerieren, sofern zwei Einschränkungen erfüllt sind: (1) Integer-Mathematik hat außer der versuchten Division durch Null keine Nebenwirkungen; (2) Das Umwandeln des N-Bit-Ergebnisses vorzeichenbehafteter additiver, multiplikativer oder bitweiser Operationen in einen vorzeichenlosen Typ mit N-Bit oder kleiner ergibt das gleiche Ergebnis, als ob die Operation unter Verwendung von vorzeichenloser Mathematik durchgeführt worden wäre. Die Autoren des C89 stellten fest, dass die meisten Compiler beide Garantien aufrechterhielten und die Wahl der signierten Förderung für kurze unsignierte Typen teilweise auf diesem Verhalten beruhte.

    – Superkatze

    19. Mai 2016 um 22:38 Uhr

  • @SteveJessop: Wenn es eine Möglichkeit gäbe, diese beiden Anforderungen durchzusetzen, könnte ein Programm, das sie ausnutzt und durch einen Compiler gespeist wird, der sie aufrechterhält, schneller ausgeführt werden als jedes fernlesbare, streng konforme Programm durch den perfektesten Compiler, den man sich vorstellen kann . Standard C fehlt es an Mitteln, Programme am Laufen zu halten und Compilern dennoch einige Freiheiten in Bezug auf das Überlaufverhalten zu gewähren, so dass selbst der beste Compiler an den allzu restriktiven Anforderungen streng konformer Programme festhalten muss.

    – Superkatze

    19. Mai 2016 um 22:42 Uhr


  • @SteveJessop: Ich denke, ein grundlegendes Problem ist, dass einige Leute auf die verrückte Idee gekommen sind, dass der C-Standard alles Wichtige über Qualitätsimplementierungen beschreiben sollte. Wenn man erkennt, dass (1) in einer guten Implementierung die abstrakte Maschine im Allgemeinen Merkmale und Garantien von der realen Ausführungsplattform erbt, auf der sie läuft; (2) unterschiedliche Arten von Programmen können unterschiedliche Grade der Divergenz zwischen der realen und der abstrakten Plattform tolerieren; (3) Es wäre von großem Wert, eine definierte Kategorie von “selektiv konformen” Programmen zu haben, die …

    – Superkatze

    19. Mai 2016 um 22:59 Uhr

  • @SteveJessop: … müsste nicht auf jeder Plattform kompiliert werden, müsste aber auf jeder kompatiblen Plattform, auf der sie kompiliert werden, korrekt ausgeführt werden (umgekehrt müsste eine kompatible Plattform nicht einen erheblichen Teil der selektiv konformen Programme ausführen, müsste aber alle selektiv konformen Programme ablehnen, deren Anforderungen es nicht erfüllen könnte). So wie es jetzt ist, ist “Konformität” so locker definiert, dass es im Wesentlichen bedeutungslos ist, und “strikte Konformität” ist so streng definiert, dass nur wenige reale Aufgaben mit streng konformem Code erfüllt werden können.

    – Superkatze

    19. Mai 2016 um 23:03 Uhr


  • nettes Kopieren und Einfügen … Während ich die Definition von “undefiniert” vollständig verstehe, habe ich versucht, den Grund für die UB zu verstehen, die ziemlich gut definiert ist, wie Sie an der Antwort von @SergeyA sehen können

    – Winz

    19. Mai 2016 um 14:26 Uhr

  • Können Sie Hinweise auf einen Überlauf auf Zweierkomplement-Silent-Wraparound-Hardware finden, die neben der Rückgabe eines bedeutungslosen Ergebnisses vor 2005 oder so Nebenwirkungen hat? Ich verachte die Behauptung, dass es für Programmierer niemals vernünftig war, von Mikrocomputer-Compilern zu erwarten, dass sie Verhaltenskonventionen aufrechterhalten, die auf Mainframes oder Minicomputern nicht konsequent unterstützt wurden, aber soweit ich das beurteilen kann, von Mikrocomputer-Compilern absolut einstimmig unterstützt wurden.

    – Superkatze

    19. Mai 2016 um 17:34 Uhr

  • Es gibt tatsächlich eine ganze Reihe von Anwendungen, bei denen sowohl das Abfangen bei einem Überlauf als auch das stillschweigende Liefern eines willkürlichen Werts ohne Nebeneffekte akzeptabel wären; Leider hat sich das hypermoderne UB weit darüber hinaus entwickelt. Wenn Programmierer sich darauf verlassen könnten, dass ein Überlauf eingeschränkte Konsequenzen hat, könnte Code, der diese Konsequenzen akzeptieren könnte, effizienter sein als Code, der einen Überlauf um jeden Preis verhindern müsste, aber bei modernen Compilern nur der Akt des Testens (a+b > 0) kann beliebig und rückwirkend ändern Sie die Werte von a und b. Das ist es, was beängstigend ist.

    – Superkatze

    21. Mai 2016 um 4:58 Uhr

  • Es gibt tatsächlich eine ganze Reihe von Anwendungen, bei denen sowohl das Abfangen bei einem Überlauf als auch das stillschweigende Liefern eines willkürlichen Werts ohne Nebeneffekte akzeptabel wären; Leider hat sich das hypermoderne UB weit darüber hinaus entwickelt. Wenn Programmierer sich darauf verlassen könnten, dass ein Überlauf eingeschränkte Konsequenzen hat, könnte Code, der diese Konsequenzen akzeptieren könnte, effizienter sein als Code, der einen Überlauf um jeden Preis verhindern müsste, aber bei modernen Compilern nur der Akt des Testens (a+b > 0) kann beliebig und rückwirkend ändern Sie die Werte von a und b. Das ist es, was beängstigend ist.

    – Superkatze

    21. Mai 2016 um 4:58 Uhr

1409120cookie-checkVerursacht ein Ganzzahlüberlauf aufgrund einer Speicherbeschädigung ein undefiniertes Verhalten?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy