Zählüberlauf simulieren

Guten Abend,

wir haben in der Schule folgende Aufgabe bekommen:

Entwickeln Sie ein Programm, das den Zahlenwert-Überlauf durch das Inkrementieren von Integer- und Long-Variablen ausgibt. Dabei sollen die zwei unterschiedlichen Fälle „signed“ (vorzeichenbehaftet) und „unsigned“ (vorzeichenlos) für beide Variablentypen untersucht werden. Geben Sie die Werte der Variablen über den seriellen Monitor aus und sorgen sie dafür, dass bei der Ausgabe der Überlauf im Monitor vom Betrachter deutlich zu erkennen ist.

Ich habe jetzt vier for-Schleifen geschrieben, die einmal die Variable int, unsigned int, long und unsigned long zählen.

Was müsste ich denn jetzt genau in der Schleife für eine Anweisung geben, damit man den Überlauf genau erkennt?

Ich dachte da bisher an eine if-Anweisung, wenn die Zahl den Höchstwert erreicht hat, das mein Programm kurz anhält und man die höchste Zahl erkennt.

Wie wäre dann aber die Anweisung, nur die ersten paar negativen Zahlen anzeigen zu lassen?

Unser Lehrer gab uns noch einen Hinweise, was man machen könnte:

Anmerkungen: Für die Darstellung des Überlaufes können entweder nur die Zahlenwerte kurz vor und nach einem Überlauf ausgegeben werden oder man gibt grundsätzlich alle Zahlenwerte (ohne Verzögerungszeit) aus und sorgt dafür, dass kurz vor dem Überlauf eine Ausgabeverzögerung aktiviert wird, um die ZahlenwertAblesung zu erleichtern. Diese wird dann bei einem bestimmten positiven Wert wieder abgeschaltet.

hier mal mein Code, den ich als erstes probiert hatte.

void setup() 
{
  
Serial.begin(9600);

    
}

void loop() 
{
 

  for (int i = -10; i <= 10; i++)
  {
    if (i == j)
    {
    Serial.print("Achtung: ");
    Serial.println(i);
    delay(2000);
    } 
    else
    {
    Serial.print("Zahl: ");
    Serial.println(i);
    delay(100);
    }
  }

}

und der Code mit den vier Schleifen

void setup() 
{
 
Serial.begin(9600);

   
}

void loop() 
{


 for (int i = -32768; i <= 32767; i++)
 {
   Serial.print("Achtung: ");
   Serial.println(i);
   delay(2000);
 }
 for (unsigned int j = 0; j <=65535; j++)
 {
   Serial.print("Zahl: ");
   Serial.println(j);
   delay(2000);
 }
 for (long x = -2147483648; x <= 2147483647; x++)
 {
   Serial.print("Zahl: ");
   Serial.println(x);
   delay(2000);
 }
 for (unsigned long y = 0; y <=  4294967295; y++)
 {
   Serial.print("Zahl: ");
   Serial.println(y);
   delay(2000);
 }
}

Die Aufgabenstellung ist fehlerhaft. Der Überlauf einer vorzeichenbehafteten (signed) Ganzzahl ist in C++ nicht definiert.
Die Aufgabe liefert nur für vorzeichenlose (unsigned) Variablen definierte Ergebnisse.

Gruß Tommy

Was müsste ich denn jetzt genau in der Schleife für eine Anweisung geben, damit man den Überlauf genau erkennt?

Überleg mal:
woran erkennst du einen "Überlauf" ?
wenn du zwei Zahlen hast eine "Alte" und eine "Neue" ... wie kannst du feststellen, dass die Neue einen Überlauf produziert hat?

Ansonsten - Du weist doch, wo die Grenze des Wertebereichs ist - z.B. 6 Werte um die Grenze rum.

Mal für Byte (unsigned) gemacht:

byte b = 255 - 3 ;
for( byte i = 0; i < 7; i++) {
 b++;
 Serial.println(b);
}

Gruß Tommy

wtf,
bei

  int8_t pruefling = -128;
  Serial.print(pruefling); Serial.print(" "); Serial.print(pruefling, BIN); Serial.print(" "); Serial.print(pruefling, HEX);
  Serial.println();

ist das schon fies was der print daraus macht:

-128 11111111111111111111111110000000 FFFFFF80

ja ist klar warum, aber bin auch erst beim Spielen draufgekommen:

125 1111101 7D
126 1111110 7E
127 1111111 7F
-128 11111111111111111111111110000000 FFFFFF80 --> OVR
-127 11111111111111111111111110000001 FFFFFF81
-126 11111111111111111111111110000010 FFFFFF82

Das Problem hatten wir hier schonmal in einer Diskussion. Wenn Du 2 Mal print machst, sieht es besser aus. Aber nochmal: Der Überlauf einer signed Ganzzahl ist undefiniert.

Gruß Tommy

Der Überlauf einer signed Ganzzahl ist undefiniert.

Gerade deshalb muss man ja ein Testprogramm schreiben.

Du wirst (bei mir bekannter Hardware) immer erkennen, dass bei int16_t 32767 + 1 -> -32768 ergibt.
Und dass bei int32_t entsprechend später auch der Überlauf von der größten positiven zur kleinsten negativen erfolgt.
Und dass dabei weiter nichts Schlimmes passiert :slight_smile:

"Undefiniert in C / C++" heißt, dass der Compiler nichts besonderes machen muss um ein bestimmtes Verhalten zu erzwingen, sondern das ganz der Ziel-Hardware überlassen darf.

"Undefiniert in C / C++" heißt, dass der Compiler nichts besonderes machen muss um ein bestimmtes Verhalten zu erzwingen, sondern das ganz der Ziel-Hardware überlassen darf.

Nein.
Wenn du ein UB in deinem Programm hast, heißt das, dass dein ganzes Programm als defekt angesehen werden muss.
Es darf dann alles passieren.
Auch ein Liedchen singen und bei Bill Gates anrufen.

Kurze Problemdarstellung:
Die Addition zweier positiver signed Zahlen, welche zu einem Überlauf führt.
Aus mathematischer Sicht kann eine solche Addition niemals zu einer negativen Zahl führen.
Aus dem Grund wird der Kompiler, bzw. der Optimierer, rigoros alles wegstreichen, was sich mit der Behandlung negativer Zahlen befasst.
Welches dann auch der Grund dafür ist, dass Serial Print damit dann solchen Unsinn anstellt.

Änderst du die Optimerungsstufe, z.B. von -Os zu -O0 dann zeigt dir auch Print das, was der µC Type dann in den Variablen stehen hat.

Was dann in den Variablen steht ist wiederum µC abhängig.
z.B. ob der µC im Einer- oder Zweierkomplement rechnet.

Da ist ein Unterschied zwischen "darf" , "muss" und "muss nicht" (und "darf nicht" natürlich auch).

Und daher ist ein Test, der das aktuelle Verhalten zeigt, sinnvoll.

Allerdings hast du recht, verlässliche sinnvolle Aussagen zum Verhalten erhält man, wenn alle Rahmenbedingungen (Zielhardware, Optimierungs- und sonstige Compiler-Schalter, Versionsnummern, etc.) dokumentiert sind.

Auch darf sich undefiniertes Verhalten in künftigen Versionen ändern. (Das passiert leider im echten Leben gelegentlich auch bei definiertem Verhalten)

...dass dein ganzes Programm als defekt angesehen werden muss

Mathematiker haben es schon lange aufgegeben, formale Beweise für die Fehlerfreiheit von Software finden zu wollen. (Nur Triviales kann formal fehlerfrei sein) "Fehlerfreie Software gibt es nicht" ist der realistischere Ansatz.

z.B. ob der µC im Einer- oder Zweierkomplement rechnet.

Genau. Da kann man die Ergebnisse eines Testprogramms ansehen und folgern, dass wohl uint16_t(32768) und int16_t(-32786) dasselbe Bitmuster sind und beide das Ergebnis von 32767+1 sind.

Ob der Compiler in for (int i=0; i <= 32767; i++) erkennt, dass dies eine Endlosschleife ist, und die Abbruchbedingung einfach ignoriert, oder gar erkennt, dass es nach der Endlosschleife nie weitergehen kann, lassen wir mal offen.

(deleted)

ok danke erstmal. Klingt irgendwie alles ziemlich kompliziert bisher.

Ich denke mal wir sollten das mit einer for-Schleife realisieren. Glaube aber der Lehrer hat festgestellt, das bei einigen teilweise die Grundlagen fehlen.

Am Samstag haben wir wieder Unterricht, da weiß ich dann mehr.

Ich habe jetzt mal verschachtelte for-Schleifen erstellt. Werde ich morgen mal auf den ESP32 laden und ausprobieren.