Im letzten Artikel ging es um eins der primären Konzepte beim Programmieren, nämlich die Funktionen. Im aktuellen Artikel wollen wir uns mit einem zweiten fundamentalen Konzept beschäftigen: den Variablen.

In einem früheren Artikel gab es schon einmal eine abstraktere Einführung zu den Variablen, heute wollen wir uns etwas konkreter damit beschäftigen. Fangen wir zuerst wieder mit etwas technischem Hintergrund an!

Wie bereits im letzten Artikel geschrieben, befindet sich ein Programm während der Ausführung im Programmspeicher des Rechners, das heißt, die einzelnen Anweisungen sind in einzelne Speicherzellen geschrieben. Daneben benötigt ein Programm aber natürlich auch immer noch die Möglichkeit, Daten in irgendeiner Form verwalten zu können. Zu diesem Zweck gibt es noch den Datenspeicher; dieser ist wie der Programmspeicher aus einzelnen Zellen aufgebaut, die beliebige Inhalte (also: Zahlen) aufnehmen können und ebenfalls durch eine Adresse identifiziert werden.1 Diese Speicherzellen werden durch entsprechende Anweisungen modifiziert – sie repräsentieren (zusammen mit einigen anderen Informationen) den Programmzustand. Die Daten im Speicher sind wesentlicher Bestandteil der Programmabarbeitung, da sie einerseits den Programmablauf beeinflussen (wir werden noch sehen, wie) und andererseits natürlich die berechneten Ergebnisse des Programms repräsentieren.

1 Um Verwirrung zu vermeiden: Programmspeicher und Datenspeicher liegen – je nach Architektur des Rechnersystems – zusammen im Arbeitsspeicher (so ist das bei handelsüblichen Rechnern) oder können echt physikalisch getrennte Speicher sein. Wenn sie zusammen im Arbeitsspeicher liegen, sind es oft einfach zwei getrennte, durch das Betriebssystem vergebene Speicherbereiche.

Prinzipiell sind 2 verschiedene Datenspeicher zu unterscheiden: der Stack und der Heap. Mit Stack wird neben dem Datenspeicher auch eine bestimmte Datenstruktur beschrieben, bei der die Daten gestapelt (engl: to stack) werden. Konkret werden beim Stack-Speicher neue Daten “oben aufgelegt”; um ältere, tiefer gelegene Daten zu entfernen, müssen immer zuerst die weiter oben befindlichen entfernt werden (den Vorteil dieses Vorgehens erkläre ich später). Demgegenüber können Daten im Heap an (fast) beliebiger Stelle hinzugefügt und wieder entfernt werden.2 Beide Datenspeicher-Arten kommen in modernen Programmen vor und werden uns auch immer wieder begegnen; im Detail werden wir uns aber erst in den nächsten Artikeln damit beschäftigen.

2 Ebenso wie Programm- und Datenspeicher sind Stack und Heap in der Regel lediglich 2 getrennte Bereiche im gleichen Arbeitsspeicher.

Nun ist natürlich die große Frage: mit was für Anweisungen lassen sich die Speicherzellen der Daten modifizieren, sprich auslesen und beschreiben. Natürlich könnte man ein Programm direkt anweisen, in eine Speicherzelle mit einer bestimmten Adresse einen Wert zu schreiben oder daraus einen Wert zu lesen; das würde aber erstens zu ziemlich unleserlichen Programmen führen und zweitens mit der Arbeitsweise moderner Betriebssysteme fatal kollidieren. Wir benötigen also eine andere Möglichkeit, Daten anzusprechen.

Der Grundmechanismus hierfür sind die titelgebenden Variablen (ein Hinweis vorab, für mitlesende Mathematiker: Variablen sind in der Informatik prinzipiell denen der Mathematik ähnlich, unterscheiden sich aber in einigen fundamentalen Punkten, die im Folgenden klar werden sollten). Im Grunde ist eine Variable erst einmal nichts anderes als der Verweis auf eine Speicherzelle, versteckt hinter einem schönen Namen. Der Vorteil ist, dass erstens das Programm weitaus lesbarer wird, wenn man Namen statt Speicheradresse verwendet, und dass zweitens der Compiler beim Kompilieren eines Programms dafür sorgen kann, dass die Variablen mit vernünftigen Speicheradressen assoziiert werden (wobei hier keine festen, “absoluten”, sondern dynamische, “relative”, Adressen vergeben werden – mehr dazu vielleicht später). Diese Variablen können nun benutzt werden, um passende Werte in den Speicher zu schreiben.

Nehmen wir einmal an, wir hätten zwei Variablen x und y, welche natürliche Zahlen repräsentieren. Um den Wert 42 in die durch x repräsentierte Speicherzelle zu schreiben, würde man in C++ einfach die folgende Anweisung an passender Stelle einfügen – man spricht von einer Zuweisung:

x = 42;

Zum Lesen des Wertes von x reicht die Verwendung des Namens, etwa so (y speichert danach das Quadrat von x):

y = x * x;

Nehmen wir an, dass x und y zwei aufeinanderfolgende Speicherzellen an den Adressen 123456 und 123457 bezeichnen; beim Ausführen der Anweisungen passiert im Speicher das Folgende:

Vorher x = 42; y = x * x;
Adresse Inhalt
123456
123457
123458
Adresse Inhalt
123456 42
123457
123458
Adresse Inhalt
123456 42
123457 1764
123458

Wir sehen – keine große Magie! Variablen sind also lediglich Platzhalter für Adressen im Speicher, die unser Programm besser lesbar machen. Im Gegensatz zur Mathematik ist übrigens auch das folgende in der Programmierung erlaubt:

x = 42; y = x * x; x = 43; y = x + x;

x bekommt hier nacheinander 2 verschiedene Werte zugewiesen (in der Mathematik ist das natürlich unsinnig – hier liegt der große Unterschied im Verhalten von Variablen zur Informatik), die dann entsprechend für die Berechnung von y verwendet werden; dadurch spielt natürlich auch die Reihenfolge der Ausführung der Anweisungen eine Rolle. Im Speicher passiert das Folgende:

1 / 2 / 3 / Auf einer Seite lesen

Kommentare (8)

  1. #1 Andreas
    April 16, 2013

    Sehr schön. Als Info-Dozent freue ich mich immer über die Möglichkeit, die eigene “Vermittlung” der Info-Inhalte vergleichen zu können. Deine Reihe hier ist wunderbar leicht zugänglich. Darf man Sie mit Quellenhinweis verlinken oder eventuell auch im Skript einbauen ?

  2. #2 Marcus Frenkel
    April 16, 2013

    @Andreas

    Klar, kein Problem. So lange die Quellen mit angegeben werden, kann das gern genutzt werden. Ich würde mich freuen, wenn mir noch gesagt wird, wo es genutzt wird, einfach damit ich weiß, wer so alles Interesse an den Inhalten hat und wo sie genutzt werden. :)

  3. #3 Doomtrain
    April 16, 2013

    Hm, ich glaub ich hab nen Fehler gefunden.
    Weiter oben steht, der Datenspeicher wird in Heap uns Stack unterteilt. Ich persöhnlich habe mal gelernt, dass sich die Daten im Buffer befinden und Heap die Bezeichnung für den Programmspeicher ist (daher auch der Bufferoverflow, Buffer läuft über, die Daten landen im Heap -> Supergau). Zudem würde ich den Stack auch eher dem Programmspeicher zuordnen.

    Ich kann mich natürlich auch irren, bin kein “echter” Informatiker.

  4. #4 Marcus Frenkel
    April 16, 2013

    @Doomtrain
    Heap und Stack sind beides Datenspeicher, da in beiden Speicherbereichen neue Speicherzellen reserviert und wieder freigegeben werden können (wobei sich nur der Mechanismus dafür in beiden unterscheidet). Der Programmspeicher ist (in der Regel) fest und ändert sich nicht während der Programmausführung.
    Mit Buffer ist im Allgemeinen nur ein zusammenhängender, eventuell erweiterbarer Speicherbereich gemeint; in der Regel sind Buffer im Heap reserviert (wenn man von der Datenstruktur des Buffers redet), können aber auch Stack-Bereiche bezeichnen.

  5. #5 Regina
    April 17, 2013

    42=Douglas Adams, die schönste aller Zahlen 😉

  6. #6 Dr. Webbaer
    April 20, 2013

    Kleine Anmerkung noch hierzu:

    Der globale Scope sollte nach Möglichkeit so selten wie möglich genutzt werden; für die meisten Variablen benötigt man ihn nicht und jede globale Variable macht ein Programm schwerer wartbar, da man die Übersicht über sie behalten muss. Ein übliches Problem mit globalen Variablen sind die Namenskonflikte.

    Hat schon mal wer mit aus Quellcode-Sicht großen Programmen gearbeitet, die ausschließlich mit globalen Variablen arbeiten? – Das ist gar nicht so-o schlecht.

    MFG
    Dr. W (der allerdings nicht so weit gehen würde per GoTo adressierte Programmblöcke (die vielleicht noch in sogenannten Copy-Dateien liegen) Funktionen vorzuziehen 😉 )

  7. #7 Stefan W.
    http://demystifikation.wordpress.com
    April 24, 2013

    Ein übliches Problem mit globalen Variablen sind die Namenskonflikte.

    Das ist auch ein Problem, aber das kleinere. Das Problem an globalen Variablen ist, dass man nicht verfolgen kann, wer sie wann zuletzt geändert hat. Bei nebenläufigen Programmen kann jederzeit jemand anderes dazwischengefunkt haben, und der Wert hat sich seit dem letzten Zugriff, den man verfolgen kann, geändert.

    Nebenläufige Programme, bei denen zwei Instanzen von etwas die gleiche Variable manipulieren – der Horror.

    Das ist nicht nur so-o schlecht, das ist noch viel schlechter.

  8. #8 Dr. Webbaer
    April 25, 2013
    Ein übliches Problem mit globalen Variablen sind die Namenskonflikte.

    Das ist auch ein Problem, aber das kleinere. Das Problem an globalen Variablen ist, dass man nicht verfolgen kann, wer sie wann zuletzt geändert hat.

    Ischt eine Frage der Organisation, des Divide et Impera, der Verteilung der Logik. Man kann so oder so versuchen glücklich zu werden.
    Probleme der beschriebenen Art kann es immer geben.

    Ischt ein wenig so wie mit dem Automechaniker, der in einem Raum nur unter ganz bestimmten Bedingungen zwei Motoren gleichzeitig zerlegen würde, der zudem mehrere Taschen für seine Werkzeuge hat.

    HTH
    Dr. W