DIPL.-ING. MARTIN WEITZEL, 64380 ROSSDORF, GERMANY

balluff2019-07

Fragen zum "Code-Walk" durch die finale Lösung (Projekt "Solution") bezüglich der Interrupt-Verarbeitung:

  • Welche Klasse implementiert das Callback-Interface, über das der Sekundentakt an die beiden Clocks weitergereicht wird (unter Nutzung von deren Interface `I_Clock`)?
  • Welche Struktur (in welchem Header-File) beschreibt das Register-Layout, das
    • zur Konfiguration des Timers (Faktor für den Frequenzteiler festlegen)
    • sowie den Start und Stopp dieses Timers dient
  • An welcher Hardware-Adresse sind die Register des verwendeten Timers abgebildet?
  • Wie genau (durch Setzen oder Löschen welches Bits an welcher Adresse) wird
    • der Timer gestartet?
    • der Timer gestoppt?
  • Welche Funktion wird direkt vom Interrupt-Controller jede Sekunde aufgerufen?
    • Hinweis: Sie sehen im Projekt nur deren Definition(!) und NICHT deren Aufruf, da letzterer NICHT aus dem Programm heraus erfolgt.
    • Warum muss diese Funktion in C implementiert sein?
    • Könnte man sie alternativ auch als Member-Funktion eine Klasse implementieren?
      • Wenn ja, wie und warum?
      • Wenn nein, warum nicht?
    • Wie geht es nach Aufruf dieser ersten Funktion (direkt durch die Hardware aufgerufen) weiter, bis diese schließlich nach Update aller Zähler in den beiden Clocks inkl. der eventuellen Ausgabe eines Alarms wieder zurückkehrt und die Interrupt-Bearbeitung endgültig abgeschlossen ist?

Fortgeschrittene Frage NUR für diejenigen Teilnehmer, welche die Vorgänge auch HARDWARE-nah nachvollziehen wollen, z.B. um den Overhead gegenüber einer extrem "sparsamen" Lösung abzuschätzen, bei welcher der Interrupt-Handler direkt einen Zähler in einer globalen Variablen inkrementiert:

  • Wie oft muss der Maschinen-Code einen Zeiger im Speicher auslesen um weiter zu kommen bis zu der Stelle, an der schließlich der Sekundenzähler einer Uhr hochgezählt wird?
  • Wer sehr "fleißig" sein will kann dazu ggf. auch eine Handskizze dazu anfertigen. Bedenken Sie dazu,
    • dass ein Zeiger auf ein Interface immer auf ein real vorhandenes Objekt einer Klasse zeigt (das dieses Interface implemeniert).
    • dass eine Klasse, die virtuelle Member-Funktionen hat, einen Zeiger enthält, welcher auf eine Einsprung-Tabelle (VMT) zeigt.
    • dass die VMT wiederum (nur) die Adressen im Maschinen-Code enthält, auf die der Program-Counter zu Beginn dieser Funktion zeigen muss. (Nachdem vitale CPU-Register gesichert wurden - wozu neber dem vorherigen Wert des Program-Counters auch der aktuelle Stack-Pointer gehört, damit bei Rückkehr aus der Funkion hinter deren Aufrufstelle fortgesetzt werden kann.)

Zweite Aufgabe Dienstagmorgen (bitte einfach mit dem letzten - fehlerfrei kompilierbaren - Stand der ersten Aufgabe fortsetzen).

  • Machen Sie ALLE Funktionen inklusive der Konstruktoren der Klasse `Counter` "implicit inline", indem Sie deren Implementierung IN die Klasse selbst verschieben, d.h. den dort bereits vorhandenen Deklaration hinzufügen.
    D.h. es soll dann keine "nicht-inline" Member-Funktion mehr geben und auch alle folgenden Ergänzungen sollen direkt in der Klasse ("implicit inline") implementiert werden.
  • Fügen Sie der Klasse `Counter` Vergleichsoperationen für Gleichheit und Ungleichheit zu (analog zum Beispiel aus der Schulungsunterlage auf Seite OOPCPP-94 bzw. Folie 86), welche NUR den aktuellen Wert (`value`) vergleichen, NICHT den Grenzwert (`limit`).
    • D.h. der Code soll möglich sein:
      Counter c1(20), c2(12);
      // ... hier einige Increment-Aufrufe für jeden der beiden ...
      if (c1 != c2) {
          // c1 und c2 haben einen unterschiedlichen Stand
      } else
          // c1 und c2 haben beide den gleichen Stand
      }
    • bzw.
      if (c1 == c2) {
          // c1 und c2 haben beide den gleichen Stand
      } else
          // c1 und c2 haben einen unterschiedlichen Stand
      }
  • Fügen Sie der Klasse Prä- und Posfix-Versionen von `operator++` hinzu (analog zum Beispiel aus der Schulungsunterlage Seite OOPCPP-94 bzw. Folie 86) vor, verwenden Sie statt `++countValue` dort aber die eigene `Increment`-Member-Funktion der Klasse `Counter`, damit das Rücksprung-Verhalten beim Erreichen des Grenzwerts korrekt ausgeführt wird.
  • Fügen Sie der Klasse eine Member-Funktion `operator+=` hinzu, welche das inkrementieren um mehrere Schritte auf einmal gestattet (analog zum Beispiel aus der Schulungsunterlage Seite OOPCPP-92 bzw. Folie 85). Im Unterschied zum Beispiel dort soll diese Funktion aber eine Referenz auf das gerade veränderte `Counter`-Objekt zurückgeben ... (Sie benötigen also WAS in der return-Anweisung?)
  • Ein fortgeschrittener Aspekt der letzten Aufgabe wäre auch noch, dass Sie überlegen wie sie den Code entweder "einfach" oder "robust" machen, z.B. für den Fall, dass die übergebene Zähldistanz
    • negativ ist
      • dann einfach ignorieren?
      • oder abziehen ...
      • ... aber dabei nicht unter 0 gehen?
    • den Grenzwert "mehrfach" überschreitet,
      • z.B. value=5, limit=7, Zähldistanz = 99
      • soll der neue Stand nun 6 oder 0 sein?

Für einen Zwischenstand (OHNE Test der neuen Funktionalität siehe hier:
http://coliru.stacked-crooked.com/a/187528787fa9fcb0

MIT entsprechenden Tests, wie es TDD-Stil wäre (TDD = "Test-Driven-Development"):
TBD

Erste Aufgabe Dienstagmorgen

den bislang mit

#if 0
...
#endif

ausgeklammerten Teil der `main`-Funktion um auf die Verwendung von `show` und `count`.

Testen Sie anschließend folgende Änderungen (die ALLE zu Fehlern führen werden, die Ihnen der Compiler aufgrund von Widersprüchen zu `const` mitteilen wird).

Machen Sie jede einzelne dieser ÄnderuKngen wieder rückgängig, wenn Sie die Fehlermeldung gesehen und verstanden haben.

  • Ändern Sie den Wert von `limit` (welches in der Klasse `const` ist) innerhalb der Member-Funktion `count` (die selbst nicht `const`).
  • Ändern Sie den Wert von `limit` innerhalb der Member-Funktion `getValue` (die selbst `const`).
  • Erzeugen Sie bezüglich `const` einen Widerspruch, indem Sie
    • die Deklaration einer Member-Funktion in der Klasse `const machen`, aber NICHT in der Implementierung;
    • die Implementierung einer Member-Funktion `const machen`, aber NICHT deren Deklaration in der Klasse.
  • Rufen Sie innerhalb von `show`, welches eine `const` Referenz auf `Counter` erhält, eine Member-Funktion von `Counter`auf, die selbst nicht `const` ist.
  • Als Herausforderung für die etwas fortgeschrittenerenTeilnehmer:
    Umgehen Sie in einer `const`- qualifizierten Member-Funktion (z.B. getLimit()) den zur Compilezeit erzwungenen "Schreibschutz" indem Sie vom `this`-Zeiger das `const` durch eine Cast-Operation entfernen.

Weiter um 10:45


Kurze Zusammenfassung der wichtigsten Punkte des Stoffes von Montag

  • die Programmiersprache C ist eine Teilmenge der Programmiersprache C++
    • es gibt relativ wnig, was in C geht aber in C++ nicht
    • am häufgsten sind es strengere Typprüfungen
    • C++ erfordert dann eine Cast-Operation, die in C nicht notwendig ist
      • Beispiele
        enum -> int : automatisch in C und C++
        int -> enum : automatisch in C nur mit Cast in C++
        T* -> void* : automatisch in C und C++
        void* -> T* : automatisch in C nur mit Cast in C++
    • Die Namen von Strukturen und Aufzählungstypen
      • werden in C++ direkt als Typ-Namen betrachtet
      • erfordern dafür in C ein `typedef`
      • lezteres wird auch in C++ akzptiert
      • bei selbst-referentiellen Strukturen, z.B. Einträgen in verketteten Listen, erfordert auch C++ ein `struct` vor dem Typnamen oder ene Vorausdeklaration
        (hierzu kurzes Beispiel: http://coliru.stacked-crooked.com/a/ef8bf007976455eb)
  • Klassen in C++ sind eine ZUSAMMENFASSUNG von Daten und Operationen
    • Umsetzung in Maschinen-Code
      • Daten wie eine `struct` aus C
      • Operationen wie ein Unterprogramm in C, das als Argument einen Zeiger auf die Stuktur der Daten erhält
    • Terminologie:
      • Daten werden Attribute oder in C++ auch "Member-Daten" genannt
      • Funktionen werden Methoden oder in C++ auch "Member-Funktionen" genannt
      • Der Aufruf einer solchen Funktion wird in (sehr früher Literatur) auch "Senden einer Nachricht" genannt

 

 

 

 

    Ab hier in chronologischer Reihenfolge alle Aufgaben und Loesungen:


    Erste Aufgabe Montagvormittag:

    Zweite Aufgabe Montagvormittag (+ -Nachmittag, wenn nötig) ausgehend von der Loesung zur voherigen Aufgabe:

    • Testen Sie, ob der Code auch mit C kompilierbar wäre (dazu in der Kommandozeile -xc hinzufügen)
      • Wenn es geht, alles OK !
      • Wenn es nicht geht, sorgen Sie durch eine Verwendung `struct Counter` an allen Stellen, wo bisher nur `Counter` stand, dafür dass es geht;
      • Wechseln Sie wieder auf C++ (-xc wieder wegnehmen) und testen Sie, ob es immer noch geht?

    (Als Zwischenziel sollte der Code nun sowohl mit C als auch mit C++ kompilierbar sein.)
    Siehe: 
    http://coliru.stacked-crooked.com/a/fc531ab1ee5a7d55

    • Machen Sie Ihren Code mit C kompilierbar (-xc hinzufügen) indem Sie ein
      • `typedef struct Counter Counter;` hinzufügen (nach Definition der Struktur)
      • UND anschließend NICHT MEHR `struct Counter` sondern nur noch `Counter` bei der Argumentübergabe verwenden.
    • Machen Sie den Grenzwert variabel indem Sie
      • in der `struct Counter` ein weiteres Datenelement `limit` hinzufügen;
      • in `Counter_Init` ein weiteres Argument, das `limit` initialisiert;
      • in `Counter_Increment` statt mit dem festen Wert 100 mit diesem `limit` vergleichen.

    Siehe: http://coliru.stacked-crooked.com/a/429d045b3efd7307

     

    Montagnachmittag zweite Aufgabe:

        • auen Sie die bisher als `struct Counter` in C mit Unterprogrammen `Counter_...` entwickele Lösung um in eine `class Counter`, in der die Daten (`value`und `limit`) privat sind und auf die dann mit Member-Funktionen `GetValue` und `Increment` zugegriffen wird.
        • Für den Zugriff auf `limit` benötigen Sie keine Member-Funktion, da auf den Wert nur in `Increment` zugegriffen wird, aber nicht "von außen".
        • An Stelle der bisherigen Funktion `Counter_Init` benötigen Sie jedoch eine entsprechende Member-Funktion, da eine externe Funktion nicht auf die privaten Datenmember zugreifen kann.
        • Hinweis: erst im nächsten Schritt wird die `Init` Member-Funktion in einen Konstruktor umgewandelt. Wenn Sie dieses C++-Feature aber bereits kennen oder sich daran noch aus dem Einführungskapitel erinnern, können Sie die `Init` Member-Funktion auch gleich als Konstruktor schreiben. (Einen Destruktor benötigt die `Counter` Klasse auf keinen Fall.)

        Siehe: http://coliru.stacked-crooked.com/a/5a72203186790316

        Alternativ ohne Verwendung von `this` (außer in einem Fall als demo zur Reihenfolge des name lookup): http://coliru.stacked-crooked.com/a/4f9fd6bb1ea0cf29

        Montagnachmittag dritte Aufgabe:

        • Wandeln Sie die `Init` Member-Funktion in einen Konstruktor um - einfach indem Sie den Namen ändern (Konstruktor hat denselben Namen wir die Klasse, zu der er gehört) und den Return-Typ ganz entfernen.
        • Sehen Sie zunächst ingesamt zwei Konstruktoren vor, von denen
          • einer aktuellen Wert (Daten-Member `value`)
            und auch den Grenzwert (Daten-Member `limit` initialisiert)
          • der andere nur den Grenzwert und den aktuellen Wert auf 0 setzt.
        • Demonstrieren Sie die folgenden beiden Varianten für den letzten Schritt:
          • Zwei (überladene) Konstruktoren (ein und zwei Argumente)
          • Nur ein Konstruktor mit Defaultwert `0` für dasjenige Argment, das den aktuellen Wert initialisiert.
            Hinweis: hierfür muss das Argument für den aktuellen Wert als zweites stehen, also anders, als es der bisherigen Lösung entspricht.
        • Fügen Sie noch einen dritten Konstruktor hinzu, der keine Argumente hat und
          • den aktuellen Wert mit `0` sowie
          • den Grenzwert auf den größtmöglichen `int`-Wert initialisiert.
            Hinweis: Sie finden diesen Wert, wenn Sie den Header <climits> inkludieren, wo er als Makro `INT_MAX` defniert ist.

        Siehe: http://coliru.stacked-crooked.com/a/bafcbddc9abaafa6 (http://coliru.stacked-crooked.com/a/d50db28fcf7635f6)

        Weitere Lösungsvarianten:

        • Mit direkter Member-Initialisierung (ab C++11):
          http://coliru.stacked-crooked.com/a/50f7cac7a690c9b4
        • Mit Member-Initialisierungsliste (schon immer):
          http://coliru.stacked-crooked.com/a/e74bdf6b362ef287
        • Mit Konstruktor-Delegation (ab C++11):
          TBD

        Kompakt-Beispiele zum Design mit virtuellen Member-Funktionen

        • direkt an der Klienten-Schnittstelle, d.h. mit `public`-Member-Funktionen überschrieben von abgeleiteten Klasse
          • mit Weiterleitung an einen Aufruf der Member-Funktion in der Basisklasse (und zusätzlichem Code, sonst wäre ja kein Überschreiben nötig gewesen)
          • oder (unschöner) Code-Duplizierung, wenn der "Erweiterungspunkt" mitten in der Member-Funktion liegt (erfordert dann häufig, dass zumindest einige der Member-Variablen der Basisklasse nicht `protected` statt `private` sein müssen).
        • als von der Basisklasse vorausgeplante Erweiterungspunkte zur (potenziellen) Nutzung durch abgeleitete Klassen

         

        http://coliru.stacked-crooked.com/a/9ea484493fecc910

        http://coliru.stacked-crooked.com/a/8d5ad423b6075b78

        http://coliru.stacked-crooked.com/a/7da63c70f22effd5

        http://coliru.stacked-crooked.com/a/104ff722c67f985e

        http://coliru.stacked-crooked.com/a/b2ef89d4dd0081ac

        http://coliru.stacked-crooked.com/a/e0b7e0edcb768852

        http://coliru.stacked-crooked.com/a/d20a7de65d71cff5

        Arbeitsbereich:

        http://coliru.stacked-crooked.com/a/854192f717e4ca82

        https://godbolt.org/z/qw8eZh

        http://coliru.stacked-crooked.com/a/187528787fa9fcb0