Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen angezeigt.

Link zu dieser Vergleichsansicht

Beide Seiten der vorigen Revision Vorhergehende Überarbeitung
Nächste Überarbeitung
Vorhergehende Überarbeitung
c:tutorial:attribute [2012/01/04 00:30]
xin Quelltexte korrigiert (Danke Aaron :-))
c:tutorial:attribute [2022/09/22 19:58] (aktuell)
Zeile 1: Zeile 1:
 ====== Attribute ====== ====== Attribute ======
  
-  * Was sind Attribute? +===== Was sind Attribute? ​=====
-  * unsigned +
-  * const +
-    * Konstanten +
-    * Read-Only-Speicherbereiche +
-    * Zeigerkonstanten +
-  * Scopes +
-  * static +
-  * volatile+
  
-===== Was sind Attribute ​=====+Von einem Attribut spricht man, wenn man einen Gegenstand genauer beschreibt. Der rote Ball ist ein Ball, der - wenn man ihn genauer beschreibt - als rot beschrieben wird. Rot ist das Attribut. Wir werden uns hier Datentypen-Attribute ​ansehen, die die Sprache C kennt. Das bedeutet, dass man der Beschreibung eines Datentyps noch eine genauere Beschreibung hinzufügen kann.
  
-Von einem Attribut spricht man, wenn man einen Gegenstand genauer beschreibt. Der rote Ball ist ein Ball, der - wenn man ihn genauer beschreibt - als rot beschrieben wird. Rot ist das Attribut. Wir werden uns hier Datentypen-Attribute ansehen, die die Sprache C kennt, das bedeutet, dass man der Beschreibung eines Datentyps noch eine genauerer Beschreibung hinzufügen kann.+===== unsigned =====
  
-===== Das Attribut unsigned ===== +Integer-Datentypen zählen von einem maximalen negativen Wert zu einem maximalen positiven Wert. So zählt ein [[c:​type:​char]] von -128 bis 127. Einen einzelnen ​Farbwert ​(rot, grün oder blau) beschreibt man allerdings in Form seiner ​Intensität:​ Von keine Intensität ​(0) bis maximale Intensität (255). Um die maximale Intensität zu speichern, müssen wir uns die Zahl 255 merken und die passt nicht in ein [[c:​type:​char]]. Integerdatentypen sind per Voreinstellung immer ''​signed''​. ''​signed''​ ist übrigens auch ein Schlüsselwort der Sprache C, aber es benutzt eigentlich keiner, weil es die Voreinstellung ist.
- +
-Integer-Datentypen zählen von einem maximalen negativen Wert zu einem maximalen positiven Wert. So zählt ein [[c:​type:​char]] von -128 bis 127. Einen Farbwert beschreibt man allerdings in Form seiner ​Helligkeit, von aus (0) bis maximale Intensität (255). Um die maximale Intensität zu speichern, müssen wir uns die Zahl 255 merken und die passt nicht in ein [[c:​type:​char]]. Integerdatentypen sind per Voreinstellung immer ''​signed''​. ''​signed''​ ist übrigens auch ein Schlüsselwort der Sprache C, aber es benutzt eigentlich keiner, weil es die Voreinstellung ist.+
 Wenn wir bei einem ''​signed char''​ also die ungebrauchten negativen Zahlen den positiven Zahlen hinzufügen könnten, dann hätten wir 127 (maximaler positiver Wert) + 128 (maximaler negativer Wert) = 255 als größte Zahl - mit der 0 also zusammen 256 unterschiedliche Werte, die wir mit einem ''​unsigned char''​ ausdrücken können. Wenn wir bei einem ''​signed char''​ also die ungebrauchten negativen Zahlen den positiven Zahlen hinzufügen könnten, dann hätten wir 127 (maximaler positiver Wert) + 128 (maximaler negativer Wert) = 255 als größte Zahl - mit der 0 also zusammen 256 unterschiedliche Werte, die wir mit einem ''​unsigned char''​ ausdrücken können.
  
-Uns so sehen Attribute im Quelltext aus - man schreibt einfach ''​unsigned''​ **vor** den Datentyp.+Und so sehen Attribute im Quelltext aus - man schreibt einfach ''​unsigned''​ **vor** den Datentyp.
 <code cpp> <code cpp>
-char           ​c; ​  // Wertebereich -128..126 - entspricht signed char +char           ​c; ​  // Wertebereich -128..127 - entspricht signed char 
-signed ​  char sc;   // Wertebereich -128..126+signed ​  char sc;   // Wertebereich -128..127
 unsigned char uc;   // Wertebereich ​   0..255 unsigned char uc;   // Wertebereich ​   0..255
 </​code>​ </​code>​
Zeile 41: Zeile 31:
 |                                        |                  |                     | unsigned ​  ​| ​                    ​0 ​                        | (18.446.744.073.709.551.615) ​                  | |                                        |                  |                     | unsigned ​  ​| ​                    ​0 ​                        | (18.446.744.073.709.551.615) ​                  |
 \\ \\
-Die angegebenen Größen sind Mindestgrößen! Die wirklichen Größen finden sich in der Headerdatei ''​[[c:​lib:​limits:​|limit.h]]''​. Gerade für den Datentyp ''​[[c:​type:​int]]''​ gilt heute eher, dass er 32 Bit breit ist und damit ''​[[c:​type:​long]]''​ entspricht. Aber C garantiert das für ''​[[c:​type:​int]]''​ nicht, sondern nur 16 Bit, wie bei ''​[[c:​type:​short]]''​.+Die angegebenen Größen sind Mindestgrößen! Die wirklichen Größen finden sich in der Headerdatei ''​[[c:​lib:​limits:​|limits.h]]''​. Gerade für den Datentyp ''​[[c:​type:​int]]''​ gilt heute eher, dass er 32 Bit breit ist und damit ''​[[c:​type:​long]]''​ entspricht. Aber C garantiert das für ''​[[c:​type:​int]]''​ nicht, sondern nur 16 Bit, wie bei ''​[[c:​type:​short]]''​.
  
  
-Auch wenn sich ''​int''​ kürzer schreibt als ''​unsigned int'',​ so steckt für den Compiler, wie auch für den Menschen eine zusätzliche Information in diesem Datentyp. Auf ein Array darf man meistens nur mit positiven Werten zugreifen. Möchte man auf ein Array mit 256 Elementen zugreifen, so darf der Index selbst kein char sein, sondern muss mindestens ein ''​unsigned ​char''​ sein, da man ansonsten nicht hinter den Beginn des Arrays zugreift, sondern ​von 128 Elementen vor dem Arraybeginn ​bis 127 Elemente nach dem ArraybeginnDie hinteren ​128 Elemente erreicht man so nicht, während man gleichzeit ​128 Elemente ​erreicht, auf die man nicht zugreifen darf. Dies zu vergessen ist ein beliebter Anfängerfehler. Es ist auch nicht in der Verantwortung der Programmiersprache, ​den Entwickler zu erklären, was er tun und lassen darf, sondern Du musst dem Computer erklären, was er tun soll. +Auch wenn sich ''​int''​ kürzer schreibt als ''​unsigned int'',​ so steckt für den Compiler, wie auch für den Menscheneine zusätzliche Information in diesem Datentyp. Auf ein Array darf man meistens nur mit positiven Werten zugreifen. Möchte man auf ein Array mit 256 Elementen zugreifen, so darf der Index selbst kein ''​char''​ sein, weil dieser Typ wie bereits erwähnt nur einen Wertebereich ​von -128 bis 127 abdecktObwohl das 256 verschiedene Werte sind, können die höheren ​128 der 256 Elemente ​im Array nicht erreicht werden. Gleichzeitig ​erreicht man über den negativen Wertebereich ​128 Elemente, auf die man eigentlich ​nicht zugreifen darf. Der Index muss daher mindestens ein ''​unsigned char''​ mit einem Wertebereich von 0 bis 255 sein, um auf alle 256 gültigen Elemente des Arrays zuzugreifen. Dies zu vergessen ist ein beliebter Anfängerfehler. Es ist auch nicht in der Verantwortung der Programmiersprache, ​dem Entwickler zu erklären, was er tun und lassen darf, sondern Du musst dem Computer erklären, was er tun soll. 
-Bei einem Array, bei dem Du hinter den Start zugreifen ​möchtest, benutze also möglichst Variablen eines Integertyps,​ der mit dem Attribut ''​unsigned''​ erweitert wurde.+Bei einem Array, bei dem Du nur positive Indizes verwenden ​möchtest, benutze also möglichst Variablen eines Integertyps,​ der mit dem Attribut ''​unsigned''​ erweitert wurde. 
 + 
 +Schauen wir uns das an: 
 +<code cpp> 
 +  char text[] = "​proggen.org";​ 
 +  int index = -5; 
 + 
 +  printf( "​Buchstabe an Position %d: %c\n", index, text[ index ] ); 
 +</​code>​
  
-FIXME: Fallbeispiel Fehlerhafter Arrayzugriff demonstrieren+Verwendest Du für ''​index''​ eine Variable vom Typ ''​unsigned int'',​ kann Dir dies nicht passieren.
  
 Dies solltest Du grundsätzlich tun, wenn Werte nicht negativ werden können. Nehmen wir die Anzahl Daten, zum Beispiel die Größe von Arbeitsspeicher. Man kann keine negative Menge Speicher anfordern: ''​unsigned''​ verwenden! Dies solltest Du grundsätzlich tun, wenn Werte nicht negativ werden können. Nehmen wir die Anzahl Daten, zum Beispiel die Größe von Arbeitsspeicher. Man kann keine negative Menge Speicher anfordern: ''​unsigned''​ verwenden!
 Nehmen wir die Länge eines Strings: Ein String kann nicht kürzer als 0 Zeichen lang sein: ''​unsigned''​ verwenden! Nehmen wir die Länge eines Strings: Ein String kann nicht kürzer als 0 Zeichen lang sein: ''​unsigned''​ verwenden!
  
-Die bessere Version unserer strLength()-Routine sieht also so aus:+Die bessere Version unserer ​''​strLength()''​-Routine sieht also so aus:
 <code cpp> <code cpp>
 unsigned int strLength( char * string ) unsigned int strLength( char * string )
Zeile 70: Zeile 68:
 ===== const ===== ===== const =====
  
-Const und [[cpp:​const:​start|in C++ "​Const-Correctness"​]] sind sehr wichtige Themen, um die Qualität von Software zu erhöhen. Wie unsigned ist const eine zusätzliche Garantie, die man vergibt. Die Garantie bei const lautet, dass sich der Wert nicht verändern wird und das ist eine sehr wichtige Eigenschaft.+''​const'' ​und [[cpp:​const:​start|in C++ "​Const-Correctness"​]] sind sehr wichtige Themen, um die Qualität von Software zu erhöhen. Wie ''​unsigned'' ​ist ''​const'' ​eine zusätzliche Garantie, die man vergibt. Die Garantie bei ''​const'' ​lautet, dass sich der Wert nicht verändern wird und das ist eine sehr wichtige Eigenschaft.
  
 ==== Konstanten ==== ==== Konstanten ====
Zeile 123: Zeile 121:
 const.c:12: error: assignment of read-only variable ‘pi’</​code>​ const.c:12: error: assignment of read-only variable ‘pi’</​code>​
  
-Da der Compiler nun versteht, dass er Pi nicht überschreiben darf, kann er diesen Fehler auch erkennen.+Da der Compiler nun versteht, dass er ''​pi'' ​nicht überschreiben darf, kann er diesen Fehler auch erkennen.
  
 Wir korrigieren die Methode ''​circumference'':​ Wir korrigieren die Methode ''​circumference'':​
Zeile 129: Zeile 127:
 double circumference( double radius ) double circumference( double radius )
 { {
-  return pi*2*radius;​+  return pi * 2 * radius;
 } }
 </​code>​ </​code>​
Zeile 152: Zeile 150:
  
 Wir haben gelernt, dass Zeiger und Arrays sich ähnlich verhalten: Wir haben gelernt, dass Zeiger und Arrays sich ähnlich verhalten:
-<​code>​+<​code ​cpp>
   char arrayVariable[] = "​proggen.org";​   char arrayVariable[] = "​proggen.org";​
   char * pointer = arrayVariable;​   char * pointer = arrayVariable;​
 </​code> ​ </​code> ​
  
-Aber folgendes funktioniert ist gefährlich:​ +Aber folgendes funktioniert ​und ist gefährlich:​ 
-<​code>​+<​code ​cpp>
 #include <​stdio.h>​ #include <​stdio.h>​
  
Zeile 167: Zeile 165:
    ​return 0;    ​return 0;
 }</​code> ​ }</​code> ​
-Der gcc (Version 4.4.5) akzeptiert das, aber das Verhalten wird sich ändern. Früher hat man die Fehlererkennung durch den Compiler nicht so ernst genommen und so entstand viel Quelltext, der solche Text-Zuweisungen enthält. Damit diese weiterhin kompilieren und nicht urplötzlich tausende Zeilen geändert werden müssen, akzeptiert der gcc derartiges. + 
-In C++ ist const mächtiger und damit auch weiter verbreitet: der g++ meckert schonmal: +Der gcc (Version 4.9.1) akzeptiert das, aber das Verhalten wird sich ändern. Früher hat man die Fehlererkennung durch den Compiler nicht so ernst genommen und so entstand viel Quelltext, der solche Text-Zuweisungen enthält. Damit diese weiterhin kompilieren und nicht urplötzlich tausende Zeilen geändert werden müssen, akzeptiert der gcc derartiges. 
-<​code>​constptr.c:​5:​ warning: ​initialization discards qualifiers from pointer target type</​code>​+In C++ ist ''​const'' ​mächtiger und damit auch weiter verbreitet: der g++ meckert schonmal: 
 +<​code>​constptr.c:​5:21: warning: ​ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]</​code>​
  
 Was passiert da eigentlich? Der Text "​proggen.org"​ ist im ausführbaren Programm gespeichert. Die Variable ''​arrayVariable''​ ist eine Variable, deren Größe entsprechend der Länge des Textes "​proggen.org"​ gewählt wird. Und dann wird der Text "​proggen.org"​ in die Variable kopiert. In unserem Array dürfen wir dann den Text nach belieben ändern. Den Text "​proggen.org"​ dürfen wir aber nicht ändern, denn sonst würde bei jedem Aufruf von "​function"​ die Variable ''​arrayVariable''​ verändert sein. Was passiert da eigentlich? Der Text "​proggen.org"​ ist im ausführbaren Programm gespeichert. Die Variable ''​arrayVariable''​ ist eine Variable, deren Größe entsprechend der Länge des Textes "​proggen.org"​ gewählt wird. Und dann wird der Text "​proggen.org"​ in die Variable kopiert. In unserem Array dürfen wir dann den Text nach belieben ändern. Den Text "​proggen.org"​ dürfen wir aber nicht ändern, denn sonst würde bei jedem Aufruf von "​function"​ die Variable ''​arrayVariable''​ verändert sein.
  
 Wenn wir ihn nicht ändern dürfen... dann machen wir das doch einfach mal: Wenn wir ihn nicht ändern dürfen... dann machen wir das doch einfach mal:
-<​code>​+<​code ​cpp>
 #include <​stdio.h>​ #include <​stdio.h>​
  
Zeile 185: Zeile 184:
    ​return 0;    ​return 0;
 }</​code> ​ }</​code> ​
-kompilieren das und führen es aus: + 
-<​code>​xin@trinity:​~/​proggen.org/​tutorial$ ./​a.out ​+Wir kompilieren das und führen es aus: 
 +<​code>​$ ./​a.out ​
 Speicherzugriffsfehler Speicherzugriffsfehler
 </​code>​ </​code>​
  
-Ein String, der in Anführungszeichen steht ist eine Zeiger auf konstante Buchstaben: char const *.+Ein String, der in Anführungszeichen steht ist eine Zeiger auf konstante Buchstaben: ​''​char const *''​ 
 Der korrekte Quelltext sieht also so aus: Der korrekte Quelltext sieht also so aus:
-<​code>​+<​code ​cpp>
 #include <​stdio.h>​ #include <​stdio.h>​
  
Zeile 204: Zeile 205:
 }</​code> ​ }</​code> ​
  
-Hier kann der Computer ​nun auch warnen:+Hier kann der Compiler ​nun auch warnen:
 <​code>​ <​code>​
-xin@trinity:​~/​proggen.org/​tutorial$ gcc const.c ​+$ gcc const.c ​
 constptr.c: In function ‘main’: constptr.c: In function ‘main’:
 constptr.c:​7:​ error: assignment of read-only location ‘*pointer’ constptr.c:​7:​ error: assignment of read-only location ‘*pointer’
 </​code>​ </​code>​
  
-Interessant ist das nun, wenn Zeiger an Funktionen übergeben werden und man sicher sein möchte, dass die Daten von diesen Funktionen verändert werden - zum Beispiel, weil die Daten nicht verändert werden dürfen.+Interessant ist das nun, wenn Zeiger an Funktionen übergeben werden und man sicher sein möchte, dass die Daten von diesen Funktionen ​nicht verändert werden - zum Beispiel, weil die Daten nicht verändert werden dürfen.
  
-Schauen wir uns eine Funktion wie strLength ​nocheinmal ​an, die keine Daten ändert und eine weitere Funktion, die den übergebenen String mit Punkten überschreibt:​+Schauen wir uns eine Funktion wie ''​strLength''​ nochmals ​an, die keine Daten ändert und eine weitere Funktion, die den übergebenen String mit Punkten überschreibt:​
 <code cpp> <code cpp>
 #include <​stdio.h>​ #include <​stdio.h>​
Zeile 252: Zeile 253:
 Nach dem Kompilieren erhält man folgende Ausgabe: Nach dem Kompilieren erhält man folgende Ausgabe:
 <​code>​ <​code>​
-xin@trinity:​~/​proggen.org/​tutorial$ ./​a.out ​+$ ./​a.out ​
 Der Text ist 11 Zeichen lang. Der Text ist 11 Zeichen lang.
 Speicherzugriffsfehler Speicherzugriffsfehler
 </​code>​ </​code>​
  
-Die Funktion strLength() funktioniert,​ da wir glücklicherweise nichts ändern. Anschließend wird ausgegen, wie lang der Text ist und das Programm stürzt in der Funktion dots() ab und stellt klar, dass wir mit konstanten Strings arbeiten. Beide Funktionen bekommen nun statt ''​char *''​ einen ''​char const *''​ übergeben:+Die Funktion ​''​strLength()'' ​funktioniert,​ da wir glücklicherweise nichts ändern. Anschließend wird ausgegeben, wie lang der Text ist und das Programm stürzt in der Funktion ​''​dots()'' ​ab und stellt klar, dass wir mit konstanten Strings arbeiten. Beide Funktionen bekommen nun statt ''​char *''​ einen ''​char const *''​ übergeben:
  
 <code cpp> <code cpp>
Zeile 302: Zeile 303:
 funcconst.c:​9:​ error: assignment of read-only location ‘*(string + (long unsigned int)pos)’</​code>​ funcconst.c:​9:​ error: assignment of read-only location ‘*(string + (long unsigned int)pos)’</​code>​
  
-Die Funktion dots() möchte Änderungen durchführen,​ aber das darf sie bei einem ''​const char *''​ nicht. Also muss hier definitiv ein ''​char *''​ übergeben werden, also ein Pointer auf veränderbare ''​chars'':​+Die Funktion ​''​dots()'' ​möchte Änderungen durchführen,​ aber das darf sie bei einem ''​char ​const *''​ nicht. Also muss hier definitiv ein ''​char *''​ übergeben werden, also ein Pointer auf veränderbare ''​chars'':​
  
 Also zurück zu Also zurück zu
Zeile 309: Zeile 310:
 Auch das lässt sich nicht kompilieren:​ Auch das lässt sich nicht kompilieren:​
 <​code>​ <​code>​
-xin@trinity:​~/​proggen.org/​tutorial$ gcc funcconst.c ​+$ gcc funcconst.c ​
 funcconst.c:​ In function ‘main’: funcconst.c:​ In function ‘main’:
 funcconst.c:​31:​ warning: passing argument 1 of ‘dots’ discards qualifiers from pointer target type funcconst.c:​31:​ warning: passing argument 1 of ‘dots’ discards qualifiers from pointer target type
Zeile 317: Zeile 318:
 Die Funktion ''​dots()''​ eignet sich einfach nicht für konstante Zeiger und der C-Compiler **warnt** uns, dass hier etwas verkehrt läuft. Der C++ Compiler wird deutlicher und quittiert das Programm mit einem **error**: Das Programm ist mit g++ nicht kompilierbar. Die Funktion ''​dots()''​ eignet sich einfach nicht für konstante Zeiger und der C-Compiler **warnt** uns, dass hier etwas verkehrt läuft. Der C++ Compiler wird deutlicher und quittiert das Programm mit einem **error**: Das Programm ist mit g++ nicht kompilierbar.
 <​code>​ <​code>​
-xin@trinity:​~/​proggen.org/​tutorial$ g++ funcconst.c ​+$ g++ funcconst.c ​
 funcconst.c:​ In function ‘int main()’: funcconst.c:​ In function ‘int main()’:
 funcconst.c:​31:​ error: invalid conversion from ‘const char*’ to ‘char*’ funcconst.c:​31:​ error: invalid conversion from ‘const char*’ to ‘char*’
Zeile 327: Zeile 328:
 int main( void ) int main( void )
 { {
-  char const array[] = "​proggen.org"; ​+  char array[] = "​proggen.org"; ​
   unsigned int length = strLength( array );   unsigned int length = strLength( array );
    
Zeile 341: Zeile 342:
  
 Nach dem kompilieren erhalten wir als Ausgabe: Nach dem kompilieren erhalten wir als Ausgabe:
-<​code ​cpp+<​code>​ 
-xin@trinity:​~/​proggen.org/​tutorial$ ./​a.out ​+$ ./​a.out ​
 Der Text ist 11 Zeichen lang. Der Text ist 11 Zeichen lang.
 Der Text ist nach dots() nun: ........... Der Text ist nach dots() nun: ...........
 </​code>​ </​code>​
  
-Wir haben bei ''​strLength()''​ die Garantie, dass der übergebene String nicht verändert wird, wir können also auch Arrays als konstante Werte übergeben. Die Funktion dots() erlaubt keine konstanten Arrays, da sie die Daten ändern möchte. Der Compiler ist in der Lage Fehler zu erkennen und zu melden. Lies Dir auch Warnings durch, Code mit Warnings ist grundsätzlich unzureichend!+Wir haben bei ''​strLength()''​ die Garantie, dass der übergebene String nicht verändert wird, wir können also auch Arrays als konstante Werte übergeben. Die Funktion ​''​dots()'' ​erlaubt keine konstanten Arrays, da sie die Daten ändern möchte. Der Compiler ist in der Lage Fehler zu erkennen und zu melden. Lies Dir auch Warnings durch, Code mit Warnings ist grundsätzlich unzureichend!
  
-**Grundregel**:​ Wenn Du qualitative Software herstellen möchtest, sorge dafür, dass Zeiger grundsätzlich auf konstante Daten zeigen, außer Du musst in dieser Funktion Daten ändern. Das zieht sich durch das ganze Programm durch: ​eine Funktion, die einen konstanten Parameter erhalten hat, kann ihn nicht an eine Funktion übergeben, die eine variablen Speicherbereich benötigt.+**Grundregel**:​ Wenn Du qualitative Software herstellen möchtest, sorge dafür, dass Zeiger grundsätzlich auf konstante Daten zeigen, außer Du musst in dieser Funktion Daten ändern. Das zieht sich durch das ganze Programm durch: ​Eine Funktion, die einen konstanten Parameter erhalten hat, kann ihn nicht an eine Funktion übergeben, die eine variablen Speicherbereich benötigt.
  
 ==== Zeigerkonstanten ==== ==== Zeigerkonstanten ====
Zeile 358: Zeile 359:
 <code cpp>char const * ptr = "​proggen.org";</​code>​ <code cpp>char const * ptr = "​proggen.org";</​code>​
  
-Das ist in seiner Bedeutung gleichwertig,​ es macht also für das Programm ​keinen Unterschied. Ich empfehle trotzdem const nach dem Datentyp zu schreiben, um Verwechslungen auszuschließen. Schauen wir uns folgende Zeigervariablen an:+Das ist in seiner Bedeutung gleichwertig,​ es macht also für den Compiler ​keinen Unterschied. Ich empfehle trotzdem ​''​const'' ​nach dem Datentyp zu schreiben, um Verwechslungen auszuschließen. Schauen wir uns folgende Zeigervariablen an:
 <code cpp> <code cpp>
 char const * ptr; char const * ptr;
Zeile 365: Zeile 366:
 ptr = "Hallo Welt"; ptr = "Hallo Welt";
 </​code>​ </​code>​
-Wir können hier den Wert der Zeigervariablen änderen, mal zeigt sie auf das eine Array mit konstanten chars, mal auf ein anderes ​array mit konstanten chars. Man kann aber auch Zeigerkonstanten definieren, die man - wie alle anderen Konstanten - sofort mit einem Wert initialisieren,​ also beschreiben,​ muss.+ 
 +Wir können hier den Wert der Zeigervariablen änderen, mal zeigt sie auf das eine Array mit konstanten chars, mal auf ein anderes ​Array mit konstanten chars. Man kann aber auch Zeigerkonstanten definieren, die man - wie alle anderen Konstanten - sofort mit einem Wert initialisieren,​ also beschreiben,​ muss.
  
 <code cpp> <code cpp>
Zeile 379: Zeile 381:
 denn hier ist der Datentyp nun doppelt konstant und der Zeiger bleibt variabel. denn hier ist der Datentyp nun doppelt konstant und der Zeiger bleibt variabel.
  
-Eine Zeigerkonstante muss nicht zwangsläufig auf konstante Daten zeigen: ​die Konstante kann auch durchaus auf variable Daten zeigen:+Eine Zeigerkonstante muss nicht zwangsläufig auf konstante Daten zeigen: ​Die Konstante kann auch durchaus auf variable Daten zeigen:
 <code cpp> <code cpp>
 char array[] = "Hallo proggen.org";​ char array[] = "Hallo proggen.org";​
Zeile 389: Zeile 391:
  
 Dies gilt auch in Hinblick zu C++, wo es konstante Methoden gibt. Würde man hier ''​const''​ vor die Signatur schreiben, sähe das so aus: Dies gilt auch in Hinblick zu C++, wo es konstante Methoden gibt. Würde man hier ''​const''​ vor die Signatur schreiben, sähe das so aus:
-<​code>​const char * method()</​code>​ +<​code ​cpp>const char * method()</​code>​ 
-Das geht nicht - das besagt, dass ein Zeiger auf konstante ​Chars zurückgegeben wird. Auch hier wird ''​const''​ hinter ​das betreffende ​Objekt ​geschrieben:​ +Das geht nicht - das besagt, dass ein Zeiger auf konstante ​Zeichen ​zurückgegeben wird. Auch hier wird ''​const''​ hinter ​die betreffende ​Methode ​geschrieben:​ 
-<​code>​char * method() const</​code>​+<​code ​cpp>char * method() const</​code>​
  
 Was konstante Methoden sind, lernt ihr, wenn ihr C++ lernt. Eindeutige Regeln ohne überflüssige Ausnahmen zu verwenden, solltet ihr so früh wie möglich lernen! Was konstante Methoden sind, lernt ihr, wenn ihr C++ lernt. Eindeutige Regeln ohne überflüssige Ausnahmen zu verwenden, solltet ihr so früh wie möglich lernen!
Zeile 397: Zeile 399:
 ===== Scopes ===== ===== Scopes =====
  
-Bevor wir uns dem letzten Attribut widmen, schauen wir uns an, was Scopes sind und was sie für den Programmierer bedeuten.+Bevor wir uns den letzten ​beiden ​Attribut widmen, schauen wir uns an, was Scopes sind und was sie für den Programmierer bedeuten.
  
 Zunächst unterscheiden wir zwischen lokalen und globalen Variablen. Globale Variablen sind für alle Funktionen in diesem Quelltext sichtbar, lokale Variablen sind nur innerhalb der Funktion sichtbar: Zunächst unterscheiden wir zwischen lokalen und globalen Variablen. Globale Variablen sind für alle Funktionen in diesem Quelltext sichtbar, lokale Variablen sind nur innerhalb der Funktion sichtbar:
  
-<​code>​+<​code ​cpp>
 #include <​stdio.h>​ #include <​stdio.h>​
  
Zeile 423: Zeile 425:
 }</​code>​ }</​code>​
  
-Globale Variablen existieren von Programmbeginn bis Programmende. Im Prinzip geht es der Variablen ''​lokal''​ nicht  +Globale Variablen existieren von Programmbeginn bis Programmende. Im Prinzip geht es der Variablen ''​lokal''​ nicht anders, wird sie doch am Anfang der Funktion ''​main()''​ erzeugt und am Ende von main() vernichtet. Dennoch lebt die Variable ''​global''​ ein klein wenig länger als ''​lokal'':​
-anders, wird sie doch am Anfang der Funktion ''​main()''​ erzeugt und am Ende von main() vernichtet. Dennoch lebt die Variable ''​global''​ ein klein wenig länger als ''​lokale'':​+
  
-Wann immer wir eine geschweifte Klammer öffnen um Anweisungen zusammen zu fassen, öffnen ​wir wir einen sogenannten **Scope**. Das ist das Umfeld, in dem die Variablen existieren - sobald die Klammer geschlossen wird, wird auch die Variable zerstört:+Wann immer wir eine geschweifte Klammer öffnen um Anweisungen zusammen zu fassen, öffnen wir einen sogenannten **Scope**. Das ist das Umfeld, in dem die Variablen existieren - sobald die Klammer geschlossen wird, wird auch die Variable zerstört:
  
 <code cpp> <code cpp>
Zeile 458: Zeile 459:
 Man kann immer auf die Variablen im eigenen Scope und den darüber angeordneten Scopes zugreifen. Globale Variablen liegen über allen Scopes. Man kann immer auf die Variablen im eigenen Scope und den darüber angeordneten Scopes zugreifen. Globale Variablen liegen über allen Scopes.
  
-Scopes beschränken also die Lebensdauer von Variablen, globale Variablen liegen gewissermaßen in einem übergeordneten Scope, der erst zerstört wird, nachdem die Funktion main() verlassen wurde. Es gibt ein Leben nach main() - und genauso eins davor, zum Beispiel, um die Variablen anzulegen. Unser Programm arbeitet also bereits, bevor main() gerufen wird. main() wird erst gestartet, nachdem das globale Scope eingerichtet wurde und alle anderen erforderlichen Vorbereitungen durchgeführt sind.+Scopes beschränken also die Lebensdauer von Variablen, globale Variablen liegen gewissermaßen in einem übergeordneten Scope, der erst zerstört wird, nachdem die Funktion ​''​main()'' ​verlassen wurde. Es gibt ein Leben nach ''​main()'' ​- und genauso eins davor, zum Beispiel, um die Variablen anzulegen. Unser Programm arbeitet also bereits, bevor ''​main()'' ​gerufen wird. ''​main()'' ​wird erst gestartet, nachdem das globale Scope eingerichtet wurde und alle anderen erforderlichen Vorbereitungen durchgeführt sind.
  
 ===== static ===== ===== static =====
  
-Das Attribut static bewirkt, dass eine beschriebene Variable eine statische Adresse erhält, die sich nicht ändert. Das bedeutet zum einen, dass es sich um genau eine Variable handelt - klar, sagst Du, wieviele auch sonst?+Das Attribut ​''​static'' ​bewirkt, dass eine beschriebene Variable eine statische Adresse erhält, die sich nicht ändert. Das bedeutet zum einen, dass es sich um genau eine Variable handelt - klar, sagst Du, wieviele auch sonst?
  
-Nun wir haben gerade gelesen, dass es Scopes gibt und wann immer eine Funktion gerufen wird, Variablen erzeugt und am Schluss wieder vernichtet werden. Das ist aber nicht immer gewünscht. Stellen wir uns eine Funktion count() vor, die zählt, wie oft sie aufgerufen wurde:+Nun wir haben gerade gelesen, dass es Scopes gibt und wann immer eine Funktion gerufen wird, Variablen erzeugt und am Schluss wieder vernichtet werden. Das ist aber nicht immer gewünscht. Stellen wir uns eine Funktion ​''​count()'' ​vor, die zählt, wie oft sie aufgerufen wurde:
  
 <code cpp> <code cpp>
Zeile 477: Zeile 478:
 </​code>​ </​code>​
  
-Die Variable counter wird jedesmal neu angelegt, mit 0 initialisiert und anschließend um 1 hochgezählt. Die Funktion gibt also grundsätzlich 1 zurück. So war das nicht gedacht. Wir brauchen also eine Variable, die am Ende des Funktionsaufrufs nicht einfach verschwindet. Wir wissen, dass der globale Scope erst nach dem Verlassen von main() zerstört wird, der Wert der Variablen also erhalten bleibt:+Die Variable ​''​counter'' ​wird jedesmal neu angelegt, mit 0 initialisiert und anschließend um 1 hochgezählt. Die Funktion gibt also grundsätzlich 1 zurück. So war das nicht gedacht. Wir brauchen also eine Variable, die am Ende des Funktionsaufrufs nicht einfach verschwindet. Wir wissen, dass der globale Scope erst nach dem Verlassen von main() zerstört wird, der Wert der Variablen also erhalten bleibt:
  
 <code cpp> <code cpp>
Zeile 491: Zeile 492:
  
 So funktioniert es! So funktioniert es!
-Aber dennoch ist die Lösung eher unschön, denn jeder kann die Variable counter verändern - wir wollen ein Problem lösen, müssen aber zwei Identifier, nämlich count und counter, in den globalen Namensraum setzen. Vermutlich kommt es schnell zu Verwechslungen,​ ob die Funktion nun count() oder counter() heißt. Es wäre also schön, wenn die Variable ''​counter''​ außerhalb der Funktion ''​count()''​ nicht sichtbar wäre. +Aber dennoch ist die Lösung eher unschön, denn jeder kann die Variable ​''​counter'' ​verändern - wir wollen ein Problem lösen, müssen aber zwei Identifier, nämlich ​''​count'' ​und ''​counter''​, in den globalen Namensraum setzen. Vermutlich kommt es schnell zu Verwechslungen,​ ob die Funktion nun ''​count()'' ​oder ''​counter()'' ​heißt. Es wäre also schön, wenn die Variable ''​counter''​ außerhalb der Funktion ''​count()''​ nicht sichtbar wäre. 
-Und hier kommt static das erste mal ins Spiel. Alle globelen ​Variablen sind statisch, also einmalig an einer festen Adresse.+Und hier kommt ''​static'' ​das erste Mal ins Spiel. Alle globalen ​Variablen sind statisch, also einmalig an einer festen Adresse.
  
 Mit dem Schlüsselwort ''​static''​ entkoppelt man eine Variable aus dem Scope, sie wird nach dem Verlassen des Scopes nicht zerstört. Sie landet im Speicherbereich des globalen Scopes, ist allerdings dort nicht sichtbar. Sichtbar bleibt sie nur im Scope, in dem sie angemeldet wurde. Mit dem Schlüsselwort ''​static''​ entkoppelt man eine Variable aus dem Scope, sie wird nach dem Verlassen des Scopes nicht zerstört. Sie landet im Speicherbereich des globalen Scopes, ist allerdings dort nicht sichtbar. Sichtbar bleibt sie nur im Scope, in dem sie angemeldet wurde.
-Die Initialisierung einer statischen ​Variablen ​wird vor dem Ruf von main() ​ausgeführt, ​genau wie bei den globalen Variablen. **Die Initialisierung einer statischen Variable ist einmalig und wird nicht bei jedem Aufruf der Funktion durchgeführt.**+Die Initialisierung einer statischen ​Variable ​wird dann ausgeführt, ​wenn der Programmablauf erstmals an ihrer Definition vorbeikommt. Liegt diese Variable also in einer Funktion, wird sie beim ersten Aufruf der Funktion angelegt und initialisiert. **Die Initialisierung einer statischen Variable ist einmalig und wird nicht bei jedem Aufruf der Funktion durchgeführt**.
  
 <code cpp> <code cpp>
Zeile 509: Zeile 510:
 }                         // counter wird nicht vernichtet! }                         // counter wird nicht vernichtet!
  
-void main()+int main( void )
 {                         // counter ist nicht sichtbar {                         // counter ist nicht sichtbar
   printf( "​Zähle:​ %d\n", count() );   printf( "​Zähle:​ %d\n", count() );
Zeile 525: Zeile 526:
 ===== volatile ===== ===== volatile =====
  
-volatile bedeutet soviel wie '​flüchtig'​ oder '​sprunghaft'​. C wird gerne in der hardwarenahen Programmierung verwendet und wenn auf einen Speicherbereich mehrere Prozessoren schreibend zugreifen können, so darf man nicht davon ausgehen, dass diese Speicherbereiche unverändert bleiben, wenn man selbst keine Änderung daran vornimmt.+''​volatile'' ​bedeutet soviel wie '​flüchtig'​ oder '​sprunghaft'​. C wird gerne in der hardwarenahen Programmierung verwendet und wenn auf einen Speicherbereich mehrere Prozessoren schreibend zugreifen können, so darf man nicht davon ausgehen, dass diese Speicherbereiche unverändert bleiben, ​auch wenn man selbst keine Änderung daran vornimmt.
  
 Prozessoren können viel schneller rechnen, wenn sie Variablen in den Prozessorkern laden und dann damit arbeiten. Das wissen die Compilerhersteller und so wird bei Schleifen wie Prozessoren können viel schneller rechnen, wenn sie Variablen in den Prozessorkern laden und dann damit arbeiten. Das wissen die Compilerhersteller und so wird bei Schleifen wie
Zeile 535: Zeile 536:
   result = result * i;   result = result * i;
 </​code>​ </​code>​
-i nicht in grundsätzlich in jedem Schritt auf den Speicher zurückgeschrieben,​ sondern nur einmal erst nachdem die Schleife durchgelaufen ist. Das spart Zeit, da der Prozessorkern sehr viel schneller ist, als der Arbeitsspeicher. +''​i'' ​nicht grundsätzlich in jedem Schritt auf den Speicher zurückgeschrieben,​ sondern nur einmal erst nachdem die Schleife durchgelaufen ist. Das spart Zeit, da der Prozessorkern sehr viel schneller ist, als der Arbeitsspeicher. 
-Wenn nun ein anderer Prozessor darüber informiert werden muss, wo die Schleife gerade dran ist, dann muss die Variable i aber immer wieder in den Speicher zurückgeschrieben werden, damit der andere Prozessor den Wert aus dem Speicher auslesen kann.+Wenn nun ein anderer Prozessor darüber informiert werden muss, wo die Schleife gerade dran ist, dann muss die Variable ​''​i'' ​aber immer wieder in den Speicher zurückgeschrieben werden, damit der andere Prozessor den Wert aus dem Speicher auslesen kann.
 Um hier eine Optimierung zu verhindern und dem C Compiler zu sagen, dass er den Wert in den Speicher schreiben muss, benutzt man ''​volatile'':​ Um hier eine Optimierung zu verhindern und dem C Compiler zu sagen, dass er den Wert in den Speicher schreiben muss, benutzt man ''​volatile'':​
 <code cpp> <code cpp>
Zeile 546: Zeile 547:
 </​code>​ </​code>​
  
-volatile ist wie gesagt in der Hardwareprogrammierung wichtig und für dieses Tutorial hier nur vollständigkeitshalber aufgeführt.+''​volatile'' ​ist wie gesagt in der Hardwareprogrammierung wichtig und für dieses Tutorial hier nur vollständigkeitshalber aufgeführt. 
 + 
 +====== Ziel dieser Lektion ====== 
 + 
 +Dir sollten nun die Bedeutung von ''​unsigned'',​ ''​const''​ und ''​static''​ bekannt sein. Auch Scopes sollten Dir bewusst sein. ''​volatile''​ darfst Du soweit wieder vergessen und nur im Hinterkopf behalten, dass da was war: Es wird eher selten verwendet. 
 + 
 +Das [[c:​tutorial:​struct|kommende Kapitel '​Strukturen'​]] hingegen ist eher eine Grundlage moderner Software-Architektur und sollte nicht vergessen werden. ;-)