Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

ANSI C - Ein Programmierkurs - Teil IV

Speicherklassen

Weiterhin kann man auch das Verhalten während der Laufzeit des Programms festlegen. Man spricht in diesem Zusammenhang von Speicherklassen. Da es vom Thema hierher paßt, erkläre ich schon hier kurz, was es damit auf sich hat. Wir werden aber erst im Laufe des Kurses, wenn wir das Wissen einsetzen können, dieses Thema mit Leben füllen.

Um die Speicherklasse einer Variablen anzugeben, kann dem Datentyp das Schlüsselwort auto, static, register, const oder volatile vorangestellt werden:

#include <stdio.h>

int main
{
   static int i=10;
   printf("i = %d\n",i);
}

Alle bisherigen Variablen waren vom Typ auto, d. h. alle außerhalb von Funktionen deklarierte Variablen sind in allen Modulen eines Programms bekannt. Variable innerhalb einer Funktion existieren nur dann, wenn gerade diese Funktion ausgeführt wird. Wird dieser Variable in der Funktion ein Wert zugewiesen, so behält die Variable den Wert nicht bis zum nächsten Funktionsaufruf.

Eine static-Variable außerhalb einer Funktion ist nur innerhalb dieses Moduls (Datei) bekannt. Eine static-Variable innerhalb einer Funktion behält ihren Wert bis zum nächsten Funktionsaufruf bei.

Mit register kann dem Compiler mitgeteilt werden, dass diese Variable häufig benötigt wird und der Zugriff darauf optimiert werden sollte. Dies kann z.B. dadurch geschehen, dass die Variable in ein Register des Prozessors gespeichert wird. Es ist aber dem Compiler überlassen, was er daraus erzeugt.

Mit const zeigt man an, dass man den Wert dieser Variablen nicht mehr ändern möchte. Wir werden auch noch bei Funktionen darauf zurückkommen, wenn wir const-Parameter übergeben.

Mit volatile weist man den Compiler darauf hin, dass der Wert dieser Variable von außerhalb verändert werden kann. Dies kann z.B. ein Register der Hardware in einem Computer sein. Der Sinn ist es, Optimierungen des Compiler zu vermeiden. Er könnte erkennen, dass man einen Wert schreibt und anschließend nur liest und dann den Wert direkt ohne Zugriff auf die Variable verwendet. Wenn nun aber beabsichtigt ist, ein Register der Hardware auszulesen, würde man nur fehlerhafte Werte bekommen.

Reservierte Bezeichner

 auto        default     float       long        sizeof      union
 break       do          for         register    static      unsigned
 case        double      goto        return      struct      void
 char        else        if          short       switch      volatile
 const       enum        int         signed      typedef     while
 continue    extern

Ersatzdarstellungen

 \a  Klingelzeichen        \\    Backslash
 \b  Backspace             \?    Fragezeichen
 \f  Seitenvorschub        \'    Anführungszeichen
 \n  Zeilentrenner         \"    Doppelanführungszeichen
 \r  Wagenrücklauf         \000  oktale Zahl
 \t  Tabulatorzeichen      \xhh  hexadezimale Zahl
 \v  Vertikal-Tabulator

Zahlensysteme

Hier wird es etwas mathematischer, aber durch Beispiele sollte es immer noch recht anschaulich sein. Es ist jedem selbst freigestellt, wie weit er dieses Kapitel verfolgen und verstehen möchte. Ein Verständnis von Zahlensystemen und auch Hex-Zahlen ist allerdings für manche Programme hilfreich.

In jedem Zahlensystem wird eine Zahl als eine Summe von Potenzen einer Basis b dargestellt. Für unser Dezimalsystem mit der Basis 10 bedeutet dies:

345 = 3 * 100 + 4 * 10 + 5 = 3 * 102 + 4 * 101 + 5 * 100

Die Einerstelle gibt also an, wie oft die Basis zur Potenz 0 (wir erinnern uns an dem Mathematikunterricht: eine Zahl hoch 0 ist 1, oder etwas mathematischer x0 := 1) in der Zahl enthalten ist. Das ist bei unserem Beispiel fünfmal. Die Zehnerstelle gibt an, wie oft 101 = 10 enthalten ist usw. Der Wertebereich der einzelnen Stellen ist dabei 0 - (b-1), geht also von 0 bis zu einer Zahl, die um 1 kleiner als die Basis ist. Etwas allgemeiner formuliert lässt sich eine Zahl wie folgt als Summe schreiben:

[Bild]

Da man bei einem Computer sehr leicht die zwei Zustände 5 V (positive Versorgungsspannung) und 0 V (Masse, GND) unterscheiden kann, bietet sich die Basis 2 für die interne Darstellung an. Damit kommen wir zum Binär- oder Dualsystem. Da der Wertebereich immer um 1 kleiner als die Basis ist, haben wir für jede Stelle die zwei Ziffern 0 (Null) und 1 (eins). Jede Einzelne Stelle wird auch Bit genannt. Eine Binärzahl sieht damit wie folgt aus:

 1011012 = 1*25 + 0*24 + 1*23 + 1*22 + 0*21 + 1*20
        = 1*32 + 1*8 + 1*4 + 1*1
        = 32 + 8 + 4 + 1
        = 45

Eigentlich doch ganz einfach, oder? Nur werden solche Zahlen leider etwas lang und unübersichtlich. Wenn man aber vier Stellen des Binärsystems zu einer zusammenfassen könnte, würden ein Byte für zwei Stellen reichen. Mit vier Binärstellen kann man die Zahlen 0 bis 15 (11112 = 23+22+21+20 = 8+4+2+1 = 15) darstellen. also nehmen wir die Basis 16 (wir erinnern uns? Die Basis ist um 1 größer als die größte Ziffer). Das ist dann das Hexadezimalsystem oder kurz Hex-System. Jetzt fehlen nur noch Ziffern für 10 - 15, wir müssen ja für jede Stelle eine einzelnes Zeichen schreiben. Dazu nehmen wir die Buchstaben a - f. Damit sieht unsere Zahl 4510 so aus:

2d16 = 2*161 + 13*160 = 2*16 + 13*1 = 32 + 13 = 45

Analog wird das Oktalsystem zur Basis 8 gebildet. Hier können drei Stellen des Binärsystems zu einer zusammengefasst werden.

Um nun zu erkennen, welches Zahlensystem benutzt wird, kann man der Zahl einen Präfix voranstellen. Eine Hexzahl wird mit einem Dollar '$' oder in C mit einem '0x' markiert. Binärzahlen bekommen oft ein angehängtes 'b'.

Wie kann man nun Zahlen zwischen den Zahlensystemen umrechnen? Für die Umrechnung in das Dezimalsystem reicht obiges Verfahren aus. Um nun aus dem Dezimalsystem in ein anderes System umzurechnen, kann das folgende Verfahren benutzt werden:

Betrachte den Teilerrest, wenn die Zahl durch die neue Basis geteilt wird (modulo). Dies ist die erste Stelle rechts. Teile die Zahl durch die Basis. Verfahre mit dem Ergebnis wie oben für die nächste Stelle, bis das Ergebnis des Teilens 0 ist. Etwas anschaulicher an einem Beispiel:
 45 : 2 = 22 Rest 1  -----------+
 22 : 2 = 11 Rest 0 ----------+ |
 11 : 2 =  5 Rest 1 --------+ | |
  5 : 2 =  2 Rest 1 ------+ | | |
  2 : 2 =  1 Rest 0 ----+ | | | |
  1 : 2 =  0 Rest 1 --+ | | | | |
                      | | | | | |
                      v v v v v v
                      1 0 1 1 0 1

Warum funktioniert das Verfahren? Wenn man sich nochmals die allgemeine Darstellung einer Zahl als Summe vor Augen führt, stellt man fest, dass jede Stelle mit Ausnahme der kleinsten die Basis mit einer Potenz größer gleich 1 als Wertigkeit hat. Und damit ist jede Stelle bis auf die erste glatt durch die Basis teilbar. Lediglich die erste Stelle ist 0 mal durch die Basis teilbar, da die Ziffern nur bis zur Basis minus 1 gehen! Der Teilerrest ist also die Stelle mit der Wertigkeit Basis0. Wenn wir die Zahl durch die Basis teilen, wissen wir, wie oft die Basis1 in der Zahl enthalten ist. Da hier auch wieder größere Potenzen der Basis mit enthalten sein könnten, kann wieder das gleiche Verfahren wie für die erste Stelle benutzt werden. Da eine Beschreibung nur wenig anschaulich ist, sollten Sie selbst versuchen, einige Zahlen zu konvertieren.

Da jede Stelle nur mit positiven Ziffern gebildet wird, kann man so nur positive Zahlen darstellen. Die einfachste Lösung wäre, das oberste Bit als Vorzeichen zu betrachten. Es hat sich aber eine andere Darstellung, nämlich das Zweierkomplement, verbreitet, da hierbei eine Subtraktion auf eine Negierung und anschließende Addition des zweiten Operanden zurückgeführt werden kann. Wie funktioniert das nun? Dazu wird einfach die höchste Stelle immer mit einem negativen Vorzeichen versehen. Wenn wir uns ein char (8 Bit) betrachten, sieht das wie folgt aus:

 001011012 = -0*27 + 0*26 + 1*25 + 0*24 + 1*23 + 1*22 + 0*21 + 1*20
          = 1*32 + 1*8 + 1*4 + 1*1
          = 32 + 8 + 4 + 1
          = 45
 101011012 = -1*27 + 0*26 + 1*25 + 0*24 + 1*23 + 1*22 + 0*21 + 1*20
          = -1*128 + 1*32 + 1*8 + 1*4 + 1*1
          = -128 + 32 + 8 + 4 + 1
          = -83

Wenn man eine Zahl negieren möchte, kann man ganz einfach alle Bits drehen, also aus 1 eine 0 und aus 0 eine 1 machen und anschließend 1 addieren.

 001011012 = -0*27 + 0*26 + 1*25 + 0*24 + 1*23 + 1*22 + 0*21 + 1*20
          = 1*32 + 1*8 + 1*4 + 1*1
          = 32 + 8 + 4 + 1
          = 45
 110100102 = -1*27 + 1*26 + 0*25 + 1*24 + 0*23 + 0*22 + 1*21 + 0*20
          = -1*128 + 1*64 + 1*16 + 1*2
          = -128 + 64 + 16 + 2
          = -46
 110100112 = -1*27 + 1*26 + 0*25 + 1*24 + 0*23 + 0*22 + 1*21 + 1*20
          = -1*128 + 1*64 + 1*16 + 1*2 + 1*1
          = -128 + 64 + 16 + 2 + 1
          = -45

Wer möchte, kann sich dies auch noch in der allgemeinen Summendarstellung anschauen und für eine beliebige Zahl überprüfen. Oder, wie es so schön in Lehrbüchern heißt: der einfache Beweis sei dem Leser überlassen. Dies gilt auch für die Subtraktion als Negierung mit Addition.

Der nächste Abschnitt beschäftigt sich mit Anweisungen.

Michael Bernstein


Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster