Konstanter/n Aufenthalt

Bei Protokollen ist man schnell versucht einen konstanten String “mal einfach so” als Argument zu schreiben.
do_something("Hello", "World");

Der Compiler macht das schon! Er reserviert sich im Datenbereich zwei Blöcke und generiert den Funktionsaufruf mit zwei Pointern auf die Blöcke.

Wer Eindeutigkeit und Übersichtlichkeit bevorzugt, investiert ein paar Zeilen mehr:

static char const* TEXT_HELLO = "Hello";
static char const* TEXT_WORLD = "World";
...
do_something(TEXT_HELLO, TEXT_WORLD);

doch dann kam die Portierung auf Mikrocontroller

Der böse Begriff hier lautet “Datenbereich”. Wo genau ist das?

Die Standards von C und C++ überlassen das den Compilerherstellern und das ist gut so. Schließlich will man sich hier keine Ketten anlegen.

Was passiert in der Praxis? Gehen wir zurück zu DOS bzw. dem guten alten 8086 Prozessor. Der kannte 3 Segment-Register

  • CS: Code-Segment, dort wo Programmcode abgelegt ist
  • DS: Daten-Segment, dort wo globale Variablen liegen und der Heap aufgebaut wird.
  • SS: Stack-Segment, dort wo Funktion-lokale Variablen und die Rücksprungadressen der Funktionsaufrufe liegen.
  • … und noch ein bisschen mehr, aber hier uninteressant

Heutzutage nutzen unsere PCs und Server (zum Glück) das Flat-Memory-Model, wo alles in einem virtuellen Adressraum liegt … so wie es Meister Von-Neumann haben wollte. Trotzdem werden die 3 oben genannten Bereiche weiter unterschiedlich verwaltet.

Mikrocontroller sind jedoch oft nach der Harvard Architektur gebaut und legen Code in einem Speicher, Daten und Stack in einem anderen Speicher ab.

Wenn ich von meinen geschätzten Atmel MCUs ausgehe, so liegt der Code immer in einem größeren Flashspeicher, während die Daten und der Stack auf einem recht kleinen SRAM Bröckchen angesiedelt sind.

Beim Arduino UNO ATmega328p können wir 32 KByte Code einbrennen, aber nur 2 KByte Daten während der Ausführung nutzen. Beim ATtiny85 sind es 8 KByte Code und 512 Bytes RAM … nicht gerade viel.

Zurück zum Problem: Wo landen unsere String-Konstanten? Im 2 KByte Datenspeicher und lassen dort weniger Luft für die Anwendung.

Lösung: Zum Glück haben die Erfinder der Platform ein paar Erweiterungen vorgenommen, damit man Konstanten auch in den Codebereich verschieben kann. PROGMEM lautet das Zauberwort und schon wandern Texte dorthin, wo mehr Platz ist.

Fazit: Wer also Bibliotheken schreibt, die Datenaustausch-Protokolle implementieren, ist gut beraten sich für seine Konstanten eine Kapselung zu überlegen. Denn so erreichen wir das alte hoch heilige Ziel der Sprache C: Nämlich Portierbarkeit zwischen allen Plattformen.


Hier noch ein sehr einfaches Arduino Beispiel:

void setup() {
  Serial.begin(9600);
}
void loop() {
  Serial.write("Hello World");
}

Sketch uses 1470 bytes (4%) of program storage space. Maximum is 32256 bytes.
Global variables use 196 bytes (9%) of dynamic memory, leaving 1852 bytes for local variables. Maximum is 2048 bytes.


void setup() {
  Serial.begin(9600);
}
static char const Text[] PROGMEM = { "Hello World" };
void loop() {
  Serial.write(Text);
}

Sketch uses 1470 bytes (4%) of program storage space. Maximum is 32256 bytes. Global variables use 184 bytes (8%) of dynamic memory, leaving 1864 bytes for local variables. Maximum is 2048 bytes.


Yay! 12 Bytes mehr!
Und jetzt stellen wir uns einfach mal vor, dass wir statt “Hello World” über 1 KByte an Texten im Programm haben! Tja, Kleinvieh macht eben auch Mist.


Wenn sich eine triviale Erkenntnis mit Dummheit in der Interpretation paart, dann gibt es in der Regel Kollateralschäden in der Anwendung.
frei zitiert nach A. Van der Bellen
... also dann paaren wir mal eine komplexe Erkenntnis mit Klugheit in der Interpretation!