Programmieren
 
 
 
 
 
C-Grundkurs
 
 
 
 
 
.. Zahlen, Konstante, Variable
 
 

 
 
 
 
 
In den Bits von Speicherzellen einer CPU oder ihres Arbeitsspeichers lassen sich als binäre Ziffern interpretierte 0,1-Folgen ablegen. So gesehen bezeichnen die Begriffe Zahl, Zeichen, Konstante und Variableninhalt ziemlich ähnliche Dinge. In allen Fällen handelt es sich um die Abfolge von binären Ziffern, die in einem oder mehreren aufeinander folgenden Speicherbytes abgelegt wurden. Die Anzahl zusammenhängender Speicherbytes sind normaler Weise 1Byte, 2Byte oder vielfache von 2, also 4, 8Byte usw:

Zahlenformat: Fließkommazahl / Ganzzahl
Die Interpretation des Inhalts einer Speicherzelle als Zahl kann auf zwei Wegen erfolgen. Einmal nach einer Theorie von Fließkommazahlen, der hier wegen der notwendigen Mathematik nicht vertieft nachgegangen wird und einmal nach der Theorie der (ganzen) Binärzahlen. In die letzte Kategorie fallen auch die Zeichen wie 'H' oder '#', denn bei ihnen handelt es sich auch nur um Zahlen, die aus der Vereinbarung einer Codetabelle, wie der ASCII-Tabelle hervorgehen.

Veranschaulichung:
Die Zahl 1234, als Binärzahl im Speicher abgelegt, belegt 2 Bytes und lautet codiert 04D2h. Die gleiche Zahl, abgelegt als doppelt genaue Fließkommazahl belegt 8 Bytes und lautet codiert 4093480000000000h. Der Vorteil der letzten Codierung als Fließkommazahl, liegt für diesen Fall nicht im benötigten Speicherplatz, sondern in dem Umstand, dass z.B. bei Divisionen Nachkommastellen erzeugt werden können, was bei Ganzzahlen nicht möglich ist. So ergäbe zum Beispiel die Ganzzahldivision 1234:4 =308 während die Fließkommadivision 308.5 hervorbringen würde.

Zugriff auf den Inhalt einer Speicherzelle
Hat man eine Zahl in einer Speicherzelle abgelegt, macht es Sinn, sich die Nummer (die Adresse) dieser Speicherzelle zu merken, damit man den Inhalt wiederfinden kann Dies geschieht in der Regel durch einen Variablennamen der intern für die Nummer (die Adresse) der Speicherzelle steht. Bei heinz =123; lautet die Anweisung vollständig. Suche die Speicherzelle mit der Adresse heinz im Hauptspeicher und fülle ihren Speicherraum mit der Zahl 123. Der binäre Inhalt der Speicherzelle heinz ist damit 0111 1011.

Variable / Konstante
Bei Variablen kann der Benutzer auf den Inhalt der Speicherzelle beliebig oft lesend und schreibend zugreifen. Es ist ihm also möglich den Inhalt seines Speichers während der Laufzeit des Programms zu ändern. Bei Konstanten, kann er nur einmal in den Speicher schreiben, dann kann er den Bereich nur noch lesen. Eine Konstante (Speicherzelle) kann also zur Laufzeit des Programms nicht mehr verändert werden.

Speicherbedarf einer Variablen => Zahlenmenge
Das Zusammenschalten mehrerer Bytes zu einer Speicherzelle kostet Speicherraum im Arbeitsspeicher. Soll beispielsweise ein Buch mit einigen Mio. Buchstaben im Computer bearbeitet werden, ist es schon wesentlich ob dessen Buchstaben in je einem ober zwei oder noch mehr Bytes abgelegt werden. Zur Ablage eines Zeichens nach der ASCII-Tabelle ist maximal ein Byte pro Zeichen erforderlich. Bei der Verwendung von Speicherzellen mit 4Bytes für je ein Zeichen wären also 75% des benutzten Arbeitsspeichers unnötig verschwendet.
Wie viele Bytes für eine Speicherzelle zusammengeschaltet werden sollen hängt also von der benötigten Zahlenmenge eines Problems ab. Anders herum kann die Zahlenmenge von 1,2,4,8 Bytes berechnet werden indem man die Formel 2 Anzahl der Bits benutzt.

Beispiel:
1 Byte = 8 Bit => 28 = 256 Zahlen. Da 0 auch eine Zahl ist, umfasst die maximale Zahlenmenge in einem Byte die Zahlen von 0 bis 255, wenn alle Zahle positiv gedacht sind. Sollen auch negative Zahlen vorkommen, muss 256 / 2 =128 gerechnet werden, wobei das Byte nun die Zahle von -128 bist +127 enthält. (Bei den positiven Zahlen ist wieder die 0 enthalten.
2 Byte = 16Bit => 216 = 65536 (positiv 0 bis 65535 | negativ+positiv -32768 bis 32767)
 
 
 
 
 
Der VariablenTyp

Wie aus den vorhergehenden Zeilen ersichtlich ist, muss der Programmierer dem Übersetzerprogramm seines Quelltextes, dem Compiler also, Angaben zu dem gewünschten Speicherbedarf und dem Zahlenformat machen. Nur er kennt den späteren Verwendungszweck seiner Zahlen. Beide Angaben zusammen umschreibt man in der Sprache C als Typ der Variablen.

Die Angaben zum Typ werden in der Deklarationszeile einer Variablen vorgenommen.
Es gibt die Binär-Typen char, short, int und long sowie die Fließkomma-Typen float, double, long double.
Zudem gibt es den Modifizierer const (Konstant), der für alle Typenangaben möglich ist und aus einer Variablen eine Konstante macht.
Weitere Modifizierer sind unsigned (ohne Vorzeichen) und signed (mit Vorzeichen) die nur für die Codierung von Ganzzahlen einsetzbar sind und neben den Natürlichen Zahlen (N) eben auch Ganze Zahlen (Z), also solche mit Vorzeichen, ermöglichen.

Normalerweise besteht eine Angabe zum Variablentyp aus dem Modifizierer und der Typenangabe.
Beispiel: unsigned int. Es gibt aber für einige Typen auch Kurzbezeichnungen. Im Beispiel würde unsigned den Ausdruck unsigned int ersetzen können.

Eine spezielle Typangabe ist void (leer). Sie zeigt an, dass kein vorgegebener Typ benutzt wird. Diese Typangabe ist bei Funktionen und Zeigern zu finden und wird dort behandelt.

Die nachfolgende Tabelle führt die Variablen-Grundtypen der Sprache C auf.
 
 

 
 
Binär-Codierung:
 
 
Modifizierer
Typenangabe
Bytes
Zahlenbereich
 
 
 
signed
char
1
-128
127
 
 
unsigned
char
1
0
255
 
 
 
char
1
s.Voreinstellung
s.Voreinstellung
 
 
char von character (Zeichen) .. für ASCII–Codes und kleine Zahlen.

Achtung:
wird nur char angegeben, entscheidet eine Voreinstellung des Compilers, ob signed- oder unsigned char gemeint ist. Das kann zu Verwirrungen bei einer Neucompilationen eines Quelltextes führen, wenn inzwischen die Voreinstellung geändert wurde, oder ein zweiter Compiler mit anderer Voreinstellung verwendet wird.
Sinnvoll ist es, durch die Modifizierer signed und unsigned grundsätzlich anzusagen, welchen char-Typ man benutzen möchte. Diese Angabe überschreibt die Voreinstellung des Compilers.
 
 
 
signed
short
1(2)
-128
127
 
 
unsigned
short
1(2)
0
255
 
 
.. je nach Rechner 1 oder 2 Bytes, für kleine Zahlen. Die Kurzangabe 'short' steht für 'signed short'. Die Angabe 'unsigned short' muss vollständig angegeben werden. Der Typ wird selten benutzt, denn er steht in Konkurrenz zu char und/oder int.
 
 
 
signed
int
2
-32.768
+32.767
 
 
unsigned
int
2
0
+65.535
 
 
Der häufigst benutzte Ganzzahlentyp bei 16Bit CPUs. Die Kurzangabe 'int' steht für 'signed int'. Die Kurzangabe 'unsigned' für 'unsigned int'
 
 
 
signed
long
4
-2.147.483.648
+2.147.483.647
 
 
unsigned
long
4
0
+4.294.967.295
 
 
Für große Ganzzahlen und als Speicher für Adressen(Zeiger). Die Kurzangabe 'long' steht für 'signed long'. Für 'unsigned long' gibt es keine Kurzform. Allerdings kann auch long int (int long) oder long unsigned geschrieben werden.
 
 

 


 

 
Fliesskomma –Codierung:
 
 
Modifizierer
Typenangabe
Bytes
Zahlenbereich
 
 
 
 
float
4
3.4 E -38
3.4 E +38
 
 
 
double
8
1.7 E -308
1.7 E +308
 
 
long
double
10
3.4 E -4932
3.4 E +4932
 
 
Die Fließkommafunktionen der statischen C-Bibliothek arbeiten mit dem Typ double. Für long double müssen eigene Funktionen geschrieben werden.
 
 
 
 
 
Anmerkung
Bei der vorhergehenden Tabelle ist zu beachten, dass die Variablentypen der Sprache C mit einer Anzahl von Bytes im Hauptspeicher des Computers gleichgesetzt werden. Diese Gleichsetzung kann sich ändern wenn die verfügbaren Computer größer und schneller werden. Dann muss man wieder einmal das Handbuch der benutzten IDE lesen.
 
 

 
 
 
 
 
Beispiele für die Deklaration von Variablen
 
 
 
 
int x1;                        // Deklaration
 
 
der Compiler merkt sich, dass im Programm eine Variable mit dem Namen 'x1' auftreten soll, die dann 2 Bytes benötigt. Er merkt sich weiterhin, dass diese Variable binär-codierte Zahlen im Bereich von -32.768 bis +32.767 aufnehmen kann. Die negativen Zahlen werden im Zweierkomplement abgelegt.
 
 
 
 
unsigned x2;                   // Deklaration
 
 
der Compiler merkt sich, dass im Programm eine Variable mit dem Namen 'x2' auftreten soll, die dann 2 Bytes benötigt. Er merkt sich weiterhin, dass diese Variable binär-codierte Zahlen im Bereich von 0 bis +65535 aufnehmen kann.
 
 
 
 
double wert;                   // Deklaration
 
 
der Compiler merkt sich, dass im Programm eine Variable mit dem Namen 'wert' auftreten soll, die dann 8 Bytes benötigt. Er merkt sich weiterhin, dass diese Variable fliesskomma-codierte Zahlen aufnehmen soll und verwendet Fliesskomma-Umrechnungsfilter vor der Ablage der Zahlen in den 8 Bytes.
 
 
 
 
 
 
 
 
Das ewige Dilemma ...

Deklaration, Definition und Initialisation von Variablen
 
 
  • Die Deklaration:
    (Deklarieren - Erklären, zur Kenntnis bringen) Deklarationen sind Mitteilungen an den Compiler, der das Programm übersetzen soll. Sie werden gemacht, damit der Compiler zur Übersetzungszeit eventuelle Programmierfehler entdeckt und Fehlermeldungen herausgeben kann. Sie wirken sich nicht auf das vom Compiler hergestellte Maschinenprogramm aus. Bei der Deklaration von Variablen und Konstanten wird dem Compiler mitgeteilt, dass es eine Variable mit dem angegebenen Namen und dem angegebenen Typ im Verlauf des Programms geben kann. Wenn es die Variable nicht gibt, meldet sich der Compiler mit einer Warnung, denn er geht davon aus, dass der Programmierer einen Sinn darin sah, die Variable einzuführen. Bei Warnungen wird das Programm dennoch vollständig übersetzt.
    Deklarationen führen nicht zur Anforderung von Arbeitsspeicher beim Betriebssystem.
 
 
  • Die Definition:
    (Definieren - Festlegen) Von einer Definition spricht man immer dann, wenn der Compiler zur Ausführung seiner Aufgaben dem Betriebssystem physikalisch vorhandenen Speicherplatz abverlangt. So benötigt sowohl das von ihm erzeugte Programm konkrete, physikalisch vorhandene Speicherbytes in denen es abgelegt werden kann, aber auch dessen Daten, die Variablen und Konstanten.
    Eine Zeile wie x1 = 12345; benötigt Speicherraum, denn es muss die Zahl 12345 in der mit x1 bezeichneten Speicherzelle untergebracht werden und dazu müssen einige Bytes belegt werden. Wurde die Variable x1 zuvor deklariert, dann weiß der Compiler wie viele Bytes er benutzen soll, um die Zahl im Speicher abzulegen ... und er kann mit einer Fehlermeldung und dem Abbruch der Übersetzung reagieren, wenn die Zahl nicht in den Speicher hineinpasst.
 
 
  • Die Initialisation:
    (Initialisieren - erstmaliges Belegen eines Speichers) Im Zusammenhang mit Variablen meint der Begriff 'Initialisieren' die erstmalige Belegung einer Speicherzelle mit einem Wert. Angenommen, in einem Programm tritt diese Abfolge auf ..
 
 
 
 
      double x;   // Deklaration: Fliesskommazahl, 8 Bytes
                  // bei erstem Auftreten von x einrichten

      double y;   // Deklaration: Fliesskommazahl, 8 Bytes
                  // bei erstem Auftreten von y einrichten

      y = sin(x); // Definition von y und x weil erstmaliges
                  // Auftreten der Variablen im Programm. Es muß
                  // Speicherplatz angefordert werden.
                  // y wird auch initialisiert. Es wird also ein
                  // Sinuswert in dem Speicher eingetragen.
                  // Die fehlende Initialisation von x
                  // wird sich in einem Berechnungsfehler zur
                  // Laufzeit auswirken. Der Speicher wird zwar
                  // eingerichtet (definiert), besitzt danach aber
                  // einen unbekannten Wert. 
 
 
 
 
  • .. dann werden zwar in der 3.Zeile für y und x, je 8 Speicherzellen belegt, womit y und x definiert wären, weil aber x keinen Inhalt besitzt (nicht initialisiert wurde) würde die Sinus-Berechnung zur Laufzeit fehlschlagen. Mit Sicherheit enthält die mit x bezeichnete und vom Compiler belegte Speicherzelle irgend einen Wert, ob es sich aber lohnt, mit diesem eine Berechnung vorzunehmen ist fragwürdig. Der Programmierer hätte die Speicherzelle x initialisieren, ihr einen ersten Wert zuweisen sollen, so wie es durch ..
    x = 2.15;
    .. geschehen wäre. Die im Beispiel vorgenommene Programmierung führt entsprechend zu einem ernsten, weil schwer auffindbaren Fehler zur Laufzeit des Programms.
 
 
 
 

 

Weitere Beispiele

 

 

 

 

int x1=5;
// Deklaration, Definition und
// Initialisation in einer Zeile
 
der Compiler merkt sich, dass im Programm eine Variable mit dem Namen 'x1' auftreten soll, die 2 Bytes benötigt. Er merkt sich weiterhin, dass diese Variable binär-codierte Zahlen im Bereich von -32.768 bis +32.767 aufnehmen kann. Die negativen Zahlen werden im Zweierkomplement abgelegt. Dann reserviert er die benötigten 2 Bytes im Speicher und schreibt die binär-codierte Zahl 5 hinein
 
 
 
 

 

unsigned char a;

// Deklaration

 

 

a = 1234;

// Definition mit Initialisation

 

 
Diese Zeilen führen zu einer Fehlermeldung. Der Compiler hat sich gemerkt, dass die Variable 'a' ein Byte benötigt, also Zahlen im Bereich 0 bis 255 aufnehmen kann. Die Definition wird in der nächsten Zeile mit einem Byte durchgeführt, dann soll der Speicher mit der Zahl 1234 initialisiert werden. Da 1234 größer ist als 255, reagiert der Compiler mit einer Fehlermeldung.
 
 
 
 

 

unsigned a,b,c;

// Mehrfachdeklaration in einer Zeile

 
 
Es werden die drei Variablen a, b, c als Ganzzahlen von 4 Byte erklärt, die Werte im Bereich von 0-65535 aufnehmen können. Hierbei werden die einzelnen Variablen durch ein Komma getrennt und der Befehl mit einem Semikolon beendet.
 
 
 
 
const int ok=1;
// Deklaration, Definition und Initi-
// alisation einer Konstanten
int x;
// Deklaration
x = ok + ok;
// Definition und Initialisation von x
 
Hier wird in den 2-Byte-Speicher 'ok' die Zahl 1 eingeschrieben. Der Inhalt des Speichers kann nach dieser Initialisation nicht mehr verändert werden. Aber die Variable 'ok' steht immer für die Zahl 1.
So führt die Zeile x = ok + ok; dazu, dass der Speicher x mit der Zahl 2 gefüllt wird.
Eine andere Konstantendefinition wäre: .. const double pi = 3.1415927;
 
 
 
 
double a,b,c,d; 
// Mehrfachdeklaration von Variablen in
// einer Zeile 
 
Der Compiler merkt sich, dass die Bezeichner a, b, c, d Zahlen aufheben sollen, die in das Fließkommaformat gewandelt werden müssen und jeweils 8 Bytes Speicher benötigen. Später im Programm werden die Bezeichner initialisiert. Die erklärten Speicherbytes werden eingerichtet.
 
 
 
 
a = 12.9;
// der Speicherbereich a erhält die
// Fliesskommazahl 12.9 eingeschrieben
b = 5;
// der Speicherbereich b erhält nach einer
// Wandlung Binärformat-Fließkommaformat 
// die Zahl 5.0
c = a+b;
// c wird das Ergebnis 12.9 + 5.0 = 17.9
// zugeordnet
e = 9 / 4;
// Achtung: Hier wird zunächst der
// Ausdruck 9 : 4 = 2 (Rest 1) im
// Binärformat berechnet.
// Das Ergebnis 2 wird in das
// Fließkommaformat zu 2.0 gewandelt und
// dann im Speicher e abgelegt. e enthält
// also die Zahl 2.0 nicht 2.25
 
Soll bereits die Berechnung im Fließkommaformat durchgeführt werden, so lautet die Anweisung ..
 
e = 9./ 4.;
// Jetzt werden die Fliesskommazahlen 9.0
// und 4.0 geteilt.
 
 
 
 
 
Deklarationen, Definitionen und Initialisationen können in einer Zeile durchgeführt werden. Jede Deklaration, Definition - Initialisation muss dann von der nächsten, durch ein Komma getrennt werden. Es können so recht umfangreiche Ausdrücke entstehen.
 
 
 
 
 
Hinweis
Im obigen Text wird gesagt, dass der Compiler Speicherraum vom Betriebssystem anfordert. Dies ist nicht ganz genau. Speicherraum kann ein Programm erst zu seiner Laufzeit vom Betriebssystem anfordern. Zu dieser Zeit ist der Compiler nicht mehr aktiv, denn es liegt doch schon eine .exe-Datei vor.
Besser wäre es zu sagen, der Compiler erstellt Maschinencode, der zur Laufzeit Speicherraum anfordern kann.
 

 

 

 

www..de