====== Attribute ====== ===== 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. ===== 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. 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. Und so sehen Attribute im Quelltext aus - man schreibt einfach ''unsigned'' **vor** den Datentyp. char c; // Wertebereich -128..127 - entspricht signed char signed char sc; // Wertebereich -128..127 unsigned char uc; // Wertebereich 0..255 ''signed'' und ''unsigned'' lässt sich mit allen Integerdatentypen verwenden: ^ Größe (Bit) ^ Größe (Byte) ^ Name ^ Vorzeichen ^ kleinster Wert ^ größter Wert ^ | [[c:lib:limits:start|CHAR_BIT]] (8) | 1 | [[c:type:char]] | signed | [[c:lib:limits:start|SCHAR_MIN]] (-128) | [[c:lib:limits:start|SCHAR_MAX]] (127) | | | | | unsigned | 0 | [[c:lib:limits:start|UCHAR_MAX]] (255) | | 2*[[c:lib:limits:start|CHAR_BIT]] (16) | 2 | [[c:type:short]] | signed | [[c:lib:limits:start|SHRT_MIN]] (-32768) | [[c:lib:limits:start|SHRT_MAX]] (32767) | | | | | unsigned | 0 | [[c:lib:limits:start|USHRT_MAX]] (65535) | | 2*[[c:lib:limits:start|CHAR_BIT]] (16) | 2 (s.u.) | [[c:type:int]] | signed | [[c:lib:limits:start|INT_MIN]] (-32768) | [[c:lib:limits:start|INT_MAX]] (32767) | | | | | unsigned | 0 | [[c:lib:limits:start|UINT_MAX]] (65535) | | 4*[[c:lib:limits:start|CHAR_BIT]] (32) | 4 | [[c:type:long]] | signed | [[c:lib:limits:start|LONG_MIN]] (-2147483648) | [[c:lib:limits:start|LONG_MAX]] (2147483647) | | | | | unsigned | 0 | [[c:lib:limits:start|ULONG_MAX]] (4294967295) | | 8*[[c:lib:limits:start|CHAR_BIT]] (64) | 8 | [[c:type:longlong|long long]] | signed | (-9.223.372.036.854.775.808) | (9.223.372.036.854.775.807) | | | | | 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:|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, weil dieser Typ wie bereits erwähnt nur einen Wertebereich von -128 bis 127 abdeckt. Obwohl 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 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: char text[] = "proggen.org"; int index = -5; printf( "Buchstabe an Position %d: %c\n", index, text[ index ] ); 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! 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: unsigned int strLength( char * string ) { unsigned int length = 0; while( string[ length ] ) length = length + 1; return length; } Ohne zu wissen, wie die Funktion arbeitet, können wir an der Signatur unsigned int strLength( char * string ) schon sehen, dass wir einen positive Ganzzahl zurückbekommen werden - vorher konnten wir das nur vermuten. ===== 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. ==== Konstanten ==== Bisher kennen wir vorrangig Datentypen mit Zahlen. Mit ''const'' wird aus einer Variablen eine Konstante. Wozu ist das gut? #include double pi = 3.141592654; double circle_area( double r ) { return pi * r * r; } double circumference( double r ) { return pi = 2 * r; } int main( void ) { do_something_stupid(); printf( "Kreisumfang für Radius 1: %d\n", circumference( 1.0 )); printf( "Kreisfläche für Radius 1: %d\n", circle_area( 1.0 )); return 0; } Und damit bekommen wir folgendes Ergebnis: xin@trinity:~/proggen.org/tutorial$ ./a.out Kreisumfang für Radius 1: 2.000000 Kreisfläche für Radius 1: 2.000000 Das belegt mal wieder, dass man Ergebnisse eines Computer niemals unkritisch für wahr nehmen darf. Der Radius eines Kreises mit dem Radius 1 ist 6,28... aber hier hat sich ein Tippfehler in der Funktion ''circumference'' eingeschlichen, statt ''pi * 2 * r'' steht dort ''pi = 2 * r''. Leider ist dieser Tippfehler kein syntaktischer Fehler, also läuft das Programm - es macht halt nur nicht, was man sich wünscht. Pi ist aber keine Variable, sondern eine Konstante - mit ''const'' lässt sich das auch dem Compiler erklären: double const pi = 3.141592654; Einer Konstante kann man aber keinen neuen Wert zuweisen: xin@trinity:~/proggen.org/tutorial$ gcc const.c const.c: In function ‘circumference’: const.c:12: error: assignment of read-only variable ‘pi’ Da der Compiler nun versteht, dass er ''pi'' nicht überschreiben darf, kann er diesen Fehler auch erkennen. Wir korrigieren die Methode ''circumference'': double circumference( double radius ) { return pi * 2 * radius; } Damit lässt sich das Programm kompilieren und ausführen: Kreisumfang für Radius 1: 6.283185 Kreisfläche für Radius 1: 3.141593 ==== Read-Only-Speicherbereiche ==== Wem Konstanten nicht wichtig genug sind, der wird hier eine besonders wichtige Eigenschaft für ''const'' kennen lernen: Konstante Speicherbereiche. Wir haben bisher Arrays mit Strings initialisiert: void function( void ) { char arrayVariable[] = "proggen.org"; ... } Wir haben gelernt, dass Zeiger und Arrays sich ähnlich verhalten: char arrayVariable[] = "proggen.org"; char * pointer = arrayVariable; Aber folgendes funktioniert und ist gefährlich: #include int main( void ) { char * pointer = "proggen.org"; return 0; } 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. In C++ ist ''const'' mächtiger und damit auch weiter verbreitet: der g++ meckert schonmal: constptr.c:5:21: warning: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings] 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: #include int main( void ) { char * pointer = "proggen.org"; pointer[0] = 'a'; return 0; } Wir kompilieren das und führen es aus: $ ./a.out Speicherzugriffsfehler Ein String, der in Anführungszeichen steht ist eine Zeiger auf konstante Buchstaben: ''char const *'' Der korrekte Quelltext sieht also so aus: #include int main( void ) { char const * pointer = "proggen.org"; pointer[0] = 'a'; return 0; } Hier kann der Compiler nun auch warnen: $ gcc const.c constptr.c: In function ‘main’: constptr.c:7: error: assignment of read-only location ‘*pointer’ 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'' nochmals an, die keine Daten ändert und eine weitere Funktion, die den übergebenen String mit Punkten überschreibt: #include void dots( char * string ) { unsigned int pos = 0; while( string[ pos ] ) { string[ pos ] = '.'; pos = pos + 1; } } unsigned int strLength( char * string ) { unsigned int length = 0; while( string[ length ] ) length = length + 1; return length; } int main( void ) { unsigned int length = strLength( "proggen.org" ); printf( "Der Text ist %d Zeichen lang.\n", length ); dots( "proggen.org" ); return 0; } Nach dem Kompilieren erhält man folgende Ausgabe: $ ./a.out Der Text ist 11 Zeichen lang. Speicherzugriffsfehler 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: #include void dots( char const * string ) { unsigned int pos = 0; while( string[ pos ] ) { string[ pos ] = '.'; pos = pos + 1; } } unsigned int strLength( char const * string ) { unsigned int length = 0; while( string[ length ] ) length = length + 1; return length; } int main( void ) { char const * ptr = "proggen.org"; unsigned int length = strLength( ptr ); printf( "Der Text ist %d Zeichen lang.\n", length ); dots( ptr ); printf( "Der Text ist nach dots() nun: %s\n", ptr ); return 0; } Das lässt sich aber nicht kompilieren: funcconst.c: In function ‘dots’: funcconst.c:9: error: assignment of read-only location ‘*(string + (long unsigned int)pos)’ 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 void dots( char * string ) Auch das lässt sich nicht kompilieren: $ gcc funcconst.c funcconst.c: In function ‘main’: funcconst.c:31: warning: passing argument 1 of ‘dots’ discards qualifiers from pointer target type funcconst.c:3: note: expected ‘char *’ but argument is of type ‘const char *’ 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. $ g++ funcconst.c funcconst.c: In function ‘int main()’: funcconst.c:31: error: invalid conversion from ‘const char*’ to ‘char*’ funcconst.c:31: error: initializing argument 1 of ‘void dots(char*)’ Hier müssen wir einen Zeiger auf Speicher übergeben, den wir verändern dürfen. int main( void ) { char array[] = "proggen.org"; unsigned int length = strLength( array ); printf( "Der Text ist %d Zeichen lang.\n", length ); dots( array ); printf( "Der Text ist nach dots() nun: %s\n", array ); return 0; } Nach dem kompilieren erhalten wir als Ausgabe: $ ./a.out Der Text ist 11 Zeichen lang. Der Text ist nach dots() nun: ........... 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. ==== Zeigerkonstanten ==== Dieses Tutorial ist nur eins von vielen, die sich im Internet finden. In vermutlich allen anderen Tutorials wird das Attribut ''const'' vor dem Datentyp geschrieben, also const char * ptr = "proggen.org"; statt char const * ptr = "proggen.org"; 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: char const * ptr; ptr = "Hallo proggen.org"; ptr = "Hallo Welt"; 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. char const * const ptr = "Hallo proggen.org"; ptr = "Hallo Welt"; // das geht nicht: ptr ist read-only Bei Zeigerkonstanten steht ''const'' hinter dem Zeiger ('*'). Gleichwertig ist auch: const char * const ptr = "Hallo proggen.org"; Hier steht const vor dem Datentyp und nach dem Zeiger. Und folgendes geht überhaupt nicht: const char const * ptr = "Hallo proggen.org"; 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: char array[] = "Hallo proggen.org"; char * const ptr = array; Und hier besteht nun eine gewisse Verwechlungsgefahr, wenn man ''const'' mal vor oder nach dem Element schreibt, dass man konstant halten möchte: char const * ptr; Schreibt man ''const'' grundsätzlich hinter das konstant zu haltene Element ist sofort klar, was gemeint ist. 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: const char * method() Das geht nicht - das besagt, dass ein Zeiger auf konstante Zeichen zurückgegeben wird. Auch hier wird ''const'' hinter die betreffende Methode geschrieben: char * method() const 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! ===== Scopes ===== 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: #include int global; void func() { global = 1; } int main( void ) { int lokal; func(); lokal = 2; printf( "global: %d; lokal: %d\n", global, lokal ); return 0; } 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'': 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: int global; int main( void ) { // Scope 1 int a = 4; if( a == 4 || a == 5 ) { // Scope 2 int b = a / 5; if( b == 1 ) { // Scope 3 int c = 7; printf( "%d\n", c ); } // Variable c stirbt /* c existiert nicht mehr */ } // Variable b stirbt /* Hier ist b und c nicht mehr existent */ return 0; } // Variable a stirbt 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. ===== 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? 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: int count() { int counter = 0; counter = counter + 1; return counter; } 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: int counter = 0; int count() { counter = counter + 1; return counter; } 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. 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. 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**. #include int count() { // Scope in dem counter bekannt ist static int counter = 0; counter = counter + 1; return counter; } // counter wird nicht vernichtet! int main( void ) { // counter ist nicht sichtbar printf( "Zähle: %d\n", count() ); printf( "Zähle: %d\n", count() ); printf( "Zähle: %d\n", count() ); // printf( "Aktueller Zählstand: %d\n", counter ); // <- das geht nicht! return 0; } ''counter'' verhält sich wie eine globale Variable, aber man kann sie nur in dem Scope nutzen, in dem sie deklariert wurde, bzw. in Scopes, die unterhalb der Deklaration liegen. ===== 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, 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 int result = 0; int i = 0; for( ; i < 10; i = i + 1 ) result = result * i; ''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. Um hier eine Optimierung zu verhindern und dem C Compiler zu sagen, dass er den Wert in den Speicher schreiben muss, benutzt man ''volatile'': int result = 0; volatile int i = 0; for( ; i < 10; i = i + 1 ) result = result * i; ''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. ;-)