C programmieren: Datentypen, Deklarationen, Operatoren und Ausdrücke

Von Prof. Dr. Christian Siemers *

Die Syntax einer Programmiersprache stellt so etwas wie ihre Grammatik dar: Erst, wenn die lexikalischen Elemente beim Programmieren in C syntaktisch korrekt verwendet werden, können sinnvolle Anweisungen entstehen. Dieser Artikel beschreibt, wie die Syntax von C aufgebaut ist: Von Datentypen, Deklarationen, Operatoren bis hin zu Ausdrücken und Anweisungen.

C-Programmierung: Erst wenn die lexikalischen Elemente aus dem "Wortschatz" von C in einem sinnvollen syntaktischen Zusammenhang stehen entsteht funktionaler Code.
C-Programmierung: Erst wenn die lexikalischen Elemente aus dem "Wortschatz" von C in einem sinnvollen syntaktischen Zusammenhang stehen entsteht funktionaler Code.
(Bild: / CC0)

In einem früheren Beitrag zu den Grundlagen von C haben wir erläutert, welche lexikalischen Elemente die Programmiersprache besitzt: welcher Zeichensatz unterstützt wird, welche essentielle Schlüsselwörter für Programmier-Befehle in C verwendet werden, welche Konstanten und Identifier existieren etc.

Doch nur wenn all diese Elemente in einem sinnvollen Zusammenhang zueinander stehen, entsteht auch ein funktionaler Code. Wenn also die lexikalischen Elemente so etwas wie den "Wortschatz" von C darstellen, sind die syntaktischen Elemente essentiell die Grammatik der Programmiersprache. So wie es in der normalen Sprache Verben, Hauptwörter, Objekte oder Adjektive gibt, gibt es in Programmiersprachen syntaktische Kategorien wie Datentypen, Deklarationen, Operatoren etc.

Im folgenden Beitrag wird näher darauf eingegangen, welche syntaktischen Elemente der Programmiersprache C zu Grunde liegen und wie diese eingesetzt werden. Da die Syntax einer Programmiersprache entsprechend umfangreich ausfallen kann, ist dieser Artikel in zwei Teile gesplittet. Im ersten Teil erläutern wir Elemente wie Datentypen, Deklarationen und Definitionen, Speicherklassen, Operatoren, Ausdrücke sowie Anweisungen in C.

Datentypen in C

Der Begriff des Datentyps beinhaltet folgendes:

  • die Größe und Ausrichtung des belegten Speicherplatzes (size, alignment)
  • die interne Darstellung (Bitbelegung)
  • die Interpretation und damit den Wertebereich
  • die darauf anwendbaren bzw. erlaubten Operationen

Die in der Bildergalerie enthaltene Tabelle 1 zeigt eine Übersicht zu den instrinsischen Datentypen in C.

ISO-C verfügt über einen reichhaltigen Satz von Datentypen, die sich wie in vorangegangener Übersicht gezeigt organisieren lassen. ISO-C verlangt binäre Codierung der integralen Typen. Für die Wertebereiche aller arithmetischen Typen sind Mindestwerte und Größenverhältnisse festgelegt. Die implementierten Größen dieser Datentypen sind in limits.h und float.h definiert.

In obiger Tabelle bezeichnet T* einen Zeiger auf den Typ T, T[...] ein Array vom Typ T, T(...) eine Funktion mit Rückgabetyp T. void ist der leere Typ. Als Rück-gabetyp einer Funktion deklariert zeigt er an, dass die Funktion nichts zurückgibt, in der Parameterliste, dass sie nichts nimmt. Ein Zeiger auf void ist ein Zeiger auf irgendetwas unbestimmtes, ein generischer Zeiger, den man nie dereferenzieren kann. Variablen oder Arrays vom Typ void können daher nicht deklariert werden. Der Array-Typ T[] und der Funktionstyp T() können nicht Typ einer Funktion sein.

Die Gruppen, Klassen und Kategorien dienen zur Kenntlichmachung der auf diesen Typen und in Verbindung mit diesen Typen erlaubten Operationen. Datentypen können durch die sog. type qualifiers const und volatile weiter qualifiziert werden. Dabei bedeutet const, dass ein so bezeichneter Datentyp nur gelesen werden darf (read only), d.h. er könnte z.B. in einem solchen Speicherbereich oder im ROM abgelegt sein. volatile bedeutet, dass die so qualifizierte Größe durch außerhalb des Wirkungsbereichs des Compilers liegende Einflüsse verändert werden könnte, z.B. kann es sich hier um in den Speicherbereich eingeblendete Hardwareregister (sog. Ports) handeln. Dies soll den Compiler davon abhalten, gewisse sonst mögliche Optimierungen des Zugriffs auf die entsprechende Variable vorzunehmen. Beide Qualifizierer können auch zusammen auftreten. Hier einige Beispiele:

int i; /* i ist als Variable vom Typ int definiert */
const int ic = 4711; /* ic ist als Konstante vom Typ int definiert */
const int * pc; /* pc ist Zeiger auf konstanten int */
int * const cpi = &i; /* cpi ist konstanter Pointer auf int */
const int * const cpc = ⁣ /* konstanter Pointer auf konstanten int */
volatile int vi; /* vi kann durch äußeren Einfluss verändert werden */
const volatile int vci; /* vci ist z.B. ein Timerport */

Als const vereinbarte Variablen dürfen vom Programm nicht verändert werden. Falls man es versucht, gibt es Fehlermeldungen vom Compiler. Falls man dies jedoch durch in C legale Mittel wie Typumwandlung zu umgehen versucht, kann es je nach System auch zu Laufzeitfehlern führen.

Deklarationen und Definitionen

C ist eine eingeschränkt blockstrukturierte Sprache, d.h. Blöcke sind das strukturelle Gliederungsmittel. Blöcke werden durch die Blockanweisung { ... } erzeugt.

Die Einschränkung ist, dass Funktionsdefinitionen (siehe dort) nur außerhalb von Blöcken möglich sind. Blöcke können beliebig geschachtelt werden. Alles, was außerhalb von Blöcken deklariert oder definiert wird, ist global. Alles, was in ei-nem Block deklariert oder definiert wird, ist lokal zu diesem Block und gilt bis zum Verlassen dieses Blocks. Ein in einem Block deklarierter Name kann einen in einer höheren Ebene deklarierten Namen maskieren, d.h. der äußere Name wird verdeckt und das damit bezeichnete Objekt ist dort nicht mehr zugreifbar.

Der Compiler bearbeitet (man sagt auch liest) den Quelltext (genauer die vom Präprozessor vorverarbeitete Übersetzungseinheit) Zeile für Zeile, von links nach rechts und von oben nach unten. Bezeichner bzw. Identifier müssen grundsätzlich erst eingeführt sein, d.h. deklariert und/oder definiert sein, bevor sie benutzt werden können.

Typ Name; oder Typ Name1 , Name2, . . . ;

Definitionen weisen den Compiler an, Speicherplatz bereitzustellen und, wenn das angegeben wird, mit einem bestimmten Wert zu initialisieren. Eine Definition ist gleichzeitig auch eine Deklaration. Eine Definition macht den Typ vollständig bekannt und benutzbar, d.h. es wird Speicherplatz dafür reserviert (im Falle von Datentypen) oder auch Code erzeugt (im Falle von Funktionsdefinitionen, siehe dort).

Jetzt Newsletter abonnieren

Täglich die wichtigsten Infos zu Softwareentwicklung und DevOps

Mit Klick auf „Newsletter abonnieren“ erkläre ich mich mit der Verarbeitung und Nutzung meiner Daten gemäß Einwilligungserklärung (bitte aufklappen für Details) einverstanden und akzeptiere die Nutzungsbedingungen. Weitere Informationen finde ich in unserer Datenschutzerklärung.

Aufklappen für Details zu Ihrer Einwilligung

Definitionen von Datenobjekten mit Initialisierung haben die Form:

Typ Name = Wert; oder Typ Name1 = Wert1 , Name2 = Wert2 , . . . ;

Deklarationen machen dem Compiler Bezeichner (Namen) und ihren Typ bekannt. Sie können auch unvollständig sein, d.h. nur den Namen und seine Zugehörigkeit zu einer bestimmten Klasse bekannt machen, ohne wissen zu müssen, wie der Typ nun genau aussieht. Das reicht dann nicht aus, um dafür Speicherplatz zu reser-vieren, aber man kann z.B. einen Zeiger auf diesen jetzt noch unvollständigen Typ erzeugen, um ihn dann später, wenn der Typ vollständig bekannt ist, auch zu benutzen. Deklarationen können, abhängig von ihrer Typklasse, auch Definitionen sein. Wenn sie global, d.h. außerhalb von Blöcken erfolgen, sind sie standardmäßig auf den Wert Null initialisiert. Innerhalb eines Blocks ist ihr Wert bei ausblei-bender Initialisierung undefiniert. Definitionen haben die Form:

Speicherklassen, Sichtbarkeit und Bindung in C

Außerhalb von Blöcken vereinbarte Objekte gehören zur Speicherklasse static. Sie sind vom Start des Programms an vorhanden, und sind global, d.h. im ganzen Programm gültig und sichtbar – sie haben global scope und externe Bindung (external linkage). Wenn sie nicht im Programm auf bestimmte Werte gesetzt sind, werden sie auf den Wert 0 initialisiert (im Gegensatz zur Speicherklasse auto).

Durch Angabe des Schlüsselworts static kann der Sichtbarkeitsbereich (scope) für so vereinbarte Objekte auf die Übersetzungseinheit (Datei) eingeengt werden, das Objekt hat dann interne Bindung (internal linkage) und file scope.

Deklarationen und Definitionen in Blöcken können nur vor allen Anweisungen (siehe dort) stehen, also zu Beginn eines Blocks. Sie sind lokal zu dem Block, in dem sie erscheinen (block scope). Die so vereinbarten Objekte haben die Speicherklasse auto, d.h. sie existieren nur, solange der Block aktiv ist und werden bei Eintritt in den Block jedes Mal wieder neu erzeugt, jedoch ohne definierten Anfangswert. Durch Angabe des Schlüsselworts static kann die Speicherklasse auf static geändert werden, ohne dass Sichtbarkeit und Bindung davon berührt würden. Sie sind von Beginn an vorhanden und behalten ihren Wert auch nach Verlassen des Gültigkeitsbereichs.

Vereinbarungen von Objekten mittels register sind nur in Blöcken oder in Parameterlisten von Funktionen erlaubt und dienen lediglich als Hinweis an den Compiler, er möge sie in (schnellen) Prozessorregistern ablegen. Ob das denn auch geschieht, bleibt dem Compiler überlassen. Auf so vereinbarte Objekte darf der Adressoperator & nicht angewandt werden.

Der Sichtbarkeitsbereich einer Marke (label) ist die Funktion, in der sie deklariert ist (function scope). Innerhalb einer Funktion weist man bei Vereinbarung eines Namens mit extern darauf hin, dass das Objekt anderweitig definiert ist. Außerhalb von Funktionen gelten alle vereinbarten Objekte defaultmäßig als extern.

Operatoren

Tabelle 2: Operatoren in C.
Tabelle 2: Operatoren in C.
(Bild: Christian Siemers)

C verfügt über einen reichhaltigen Satz von Operatoren. Diese lassen sich nach verschiedenen Kategorien gliedern:

  • nach der Art: unäre, binäre und ternäre Operatoren
  • nach Vorrang – Präzedenz (precedence)
  • nach Gruppierung – Assoziativität: links, rechts (associativity)
  • nach Stellung: Präfix, Infix, Postfix nach Darstellung: einfach, zusammengesetzt

Die Vielfalt und oft mehrfache Ausnutzung der Operatorzeichen auch in anderem syntaktischen Zusammenhang bietet anfangs ein verwirrendes Bild. Der Compiler kann aber immer nach dem Kontext entscheiden, welche der Operatorfunktionen gerade gemeint ist. Nachfolgend eine Übersicht der Operatoren und eine ausführliche Erläuterung zu den einzelnen Operatoren, wie sie in Tabelle 2 aufgeführt sind:

Der Operator [ ] deklariert einen sogenannten Vektor bzw. Array. Auf diesen Operator wie auch auf -> werden wir in einem späteren Artikel zu Vektoren (arrays) und Zeigern (Pointer) näher eingehen.

++, -- (z.B. a++, b--) als Postin- bzw. -dekrement liefern sie den ursprünglichen Wert ihres Operanden und erhöhen bzw. erniedrigen den Wert des Operanden danach um 1. Diese Operatoren können nur auf Objekte im Speicher angewandt werden, die vom skalaren Typ sein müssen und auf die schreibend zugegriffen werden kann. Wann die tatsächliche Veränderung des Operandenwertes im Speicher eintritt (der Seiteneffekt dieser Operatoren) ist implementationsabhängig.

Der Operator sizeof arbeitet zur Compilierungszeit (sog. compile time operator) und liefert die Größe seines Operanden in Einheiten des Typs char: sizeof(char) == 1. Der Operand kann ein Typ sein, dann muss er in () stehen, oder ein Objekt im Speicher, dann sind keine Klammern erforderlich. Ist der Operand ein Arrayname, liefert er die Größe des Arrays in char-Einheiten.

Die Tilde ~ (z.B. ~a) liefert den Wert der bitweisen Negation (das Komplement) der Bitbelegung ihres Operanden, der vom integralen Typ sein muss.

! (z.B. !a) liefert die logische Negation des Wertes seines Operanden, der vom skalaren Typ sein muss. War der Wert 0, ist das Ergebnis 1, war der Wert ungleich 0 , ist das Ergebnis 0.

Die unäre Negation -, + (z.B. -a) liefert den negierten Wert ihres Operanden, der vom arithmetischen Typ sein muss. Das unäre Plus wurde nur aus Symmetriegründen eingeführt, und dient lediglich Dokumentationszwecken.

& (z.B. &a) liefert die Adresse eines Objektes im Speicher (und erzeugt somit einen Zeigerausdruck).

* (z.B. *a) erzeugt in einer Deklaration einen Zeiger auf den deklarierten Typ, in der Anwendung auf einen Zeigerwert, liefert er den Wert des so bezeigten Objekts.

(typename) ist ein Typbezeichner. Der sogenannte type cast operator liefert den in diesen Typ konvertierten Wert seines Operanden. Dabei wird versucht, den Wert zu erhalten. Eine (unvermeidbare, beabsichtigte) Wertänderung tritt ein, wenn der Wert des Operanden im Zieltyp nicht darstellbar ist, ähnlich einer Zuweisung an ein Objekt dieses Typs. Im Folgenden einige Hinweise zu erlaubten Konversionen:

  • Jeder arithmetische Typ in jeden arithmetischen Typ.
  • Jeder Zeiger auf void in jeden Objektzeigertyp.
  • Jeder Objektzeigertyp in Zeiger auf void. J
  • eder Zeiger auf ein Objekt oder void in einen Integertyp.
  • Jeder Integertyp in einen Zeiger auf ein Objekt oder void.
  • Jeder Funktionszeiger in einen anderen Funktionszeiger.
  • Jeder Funktionszeiger in einen Integertyp.
  • Jeder Integertyp in einen Funktionszeiger.

Die Zuweisung von void-Zeiger an Objektzeiger und umgekehrt geht übrigens auch ohne den Typkonversionsoperator. In allen anderen Fällen ist seine Anwendung geboten oder erforderlich, und sei es nur, um den Warnungen des Compilers zu entgehen.

% (z.B. a%b): modulo liefert den ganzzahligen Divisionsrest des Wertes seines linken Operanden geteilt durch den Wert seines rechten Operanden und lässt sich nur auf integrale Typen anwenden. Dabei sollte man Überlauf und die Division durch Null vermeiden. Bei positiven Operanden wird der Quotient nach 0 abgeschnitten. Falls negative Operanden beteiligt sind, ist das Ergebnis implementationsabhängig. Es gilt jedoch immer: X = (X/Y) * Y + (X%Y).

Die übrigen arithmetischen Binäroperatoren können nur auf Operandenpaare vom arithmetischen Typ angewandt werden, dabei geht, wie üblich und auch aus der Tabelle zu ersehen, Punktrechnung vor Strichrechnung. Bei der Ganzzahldivision wird ein positiver Quotient nach 0 abgeschnitten. Man vermeide auch hier die Null als Divisor. Wenn unterschiedliche Typen an der Operation beteiligt sind, wird selbstständig in den größeren der beteiligten Typen umgewandelt (balanciert).

<<, >> (z.B. a<<b): die Bitschiebeoperatoren schieben den Wert des linken Operanden um Bitpositionen des Wertes des rechten Operanden jeweils nach links bzw. rechts und können nur auf integrale Operandenpaare angewandt werden. Für eine n-Bit-Darstellung des promovierten linken Operanden muss der Wert des rechten Operanden im Intervall 0..n-1 liegen. Bei positivem linken Operanden werden Nullen in die freigewordenen Positionen nachgeschoben. Ob bei negativem linken Operanden beim Rechtsschieben das Vorzeichen nachgeschoben wird (meist so gehandhabt), oder Nullen, ist implementationsabhängig.

Die Vergleichsoperatoren (z.B. a == b) können nur auf arithmetische und auf Paare von Zeigern gleichen Typs angewandt werden. Sie liefern den Wert 1, wenn der Vergleich erfolgreich war, sonst 0.

Die bitlogischen Operatoren (z.B. a&b) können nur auf integrale Typen angewandt werden und liefern den Wert der bitlogischen Verknüpfung des Wertes des linken mit dem Wert des rechten Operanden (beide als Bitmuster interpretiert).

&& (z.B. a && b): testet, ob beide Operanden ungleich Null (wahr) sind. Ist der linke Operand wahr, wird auch der rechte getestet, andernfalls hört man auf, und der rechte Operand wird nicht mehr bewertet, da das Ergebnis der logischen UND-Verknüpfung ja schon feststeht (sog. Kurzschlussbewertung, short circuit evaluation, mit Sequenzpunkt nach dem linken Operanden). Beide Operanden müssen vom skalaren Typ sein. Im Wahrheitsfall ist der Wert des Ausdrucks 1, sonst 0.

|| (z.B. a || b): testet, ob mindestens einer der beiden Operanden ungleich Null (wahr) ist. Ist der linke Operand gleich Null (falsch), wird auch der rechte getestet, andernfalls hört man auf, und der rechte Operand wird nicht mehr bewertet, da das Ergebnis der logischen ODER-Verknüpfung ja schon feststeht (sog. Kurzschlussbewertung, short circuit evaluation, wie oben) Beide Operanden müssen vom skalaren Typ sein. Im Wahrheitsfall ist der Wert des Ausdrucks 1, sonst 0.

X?Y:Z X muss vom skalaren Typ sein und wird bewertet. Ist X ungleich Null (wahr), wird Y bewertet, andernfalls wird Z bewertet. Y und Z können fast beliebige Ausdrücke sein, auch void ist möglich, sollten aber kompatibel sein. Zwischen der Bewertung von X und der Bewertung von entweder Y oder Z befindet sich ein Sequenzpunkt (sequence point ). Der Wert des Ausdrucks ist dann der Wert des (evtl. im Typ balancierten) Wertes des zuletzt bewerteten Ausdrucks.

= Der Zuweisungsoperator bewertet seine beiden Operanden von rechts nach links, so sind auch Zuweisungsketten in der Art von a = b = c = d = 4711 möglich. Der Wert des Zuweisungsausdrucks ist der Wert des Zugewiesenen, der in den Typ des linken Operanden transformierte Wert des rechten Operanden. Der linke Operand muss ein Objekt im Speicher darstellen, auf das schreibend zugegriffen werden kann. Aufgrund der speziellen Eigenheit von C, dass die Zuweisung ein Ausdruck und keine Anweisung ist, sowie seiner einfachen Wahr-Falsch-Logik, taucht die Zuweisung oft als Testausdruck zur Schleifenkontrolle auf. Ein markantes Beispiel:

while (*s++ = t++); /* C-Idiom für Zeichenketten kopie */

Die Verbund- oder Kombinationszuweiser bestehen aus zwei Zeichen, deren rechtes der Zuweiser ist. Sie führen, kombiniert mit der Zuweisung verschiedene arithmetische, bitschiebende und bitlogische Operationen aus. Dabei bedeutet a op= b soviel wie a = a op b, mit dem Unterschied, dass a, also der linke Operand, nur einmal bewertet wird.

Der Komma- oder Sequenzoperator (z.B. a,b) gruppiert wieder von links nach rechts und bewertet erst seinen linken, dann seinen rechten Operanden. Dazwischen liegt ein Sequenzpunkt, das heißt, alle Seiteneffekte sind garantiert eingetreten. Der Wert des Ausdrucks ist das Resultat der Bewertung des rechten Operanden. Der Nutzen des Operators besteht darin, dass er einen Ausdruck erzeugt und folglich überall stehen kann, wo ein Ausdruck gebraucht wird. Seine Hauptanwendungen sind die Initialisierungs- und Reinitialisierungsausdrücke in der Kontrollstruktur der for-Schleife, wo ja jeweils nur ein Ausdruck erlaubt ist, und manchmal mehrere gebraucht werden.

Einige Operationen erzeugen implementationsabhängige Typen, die in stddef.h definiert sind. size_t ist der vom sizeof-Operator erzeugte vorzeichenlose integrale Typ. ptrdiff_t ist der vorzeichenbehaftete integrale Typ, der vom Subtraktionsoperator erzeugt wird, wenn dieser auf Zeiger (gleichen Typs!) angewandt wird..

Ausdrücke in C

C ist eine Ausdrucks-orientierte Sprache. Der Compiler betrachtet die Ausdrücke und bewertet sie. Ein Ausdruck (expression) in C ist:

  • eine Konstante (constant)
  • eine Variable (variable)
  • ein Funktionsaufruf (function call )
  • eine beliebige Kombination der obigen 3 Elemente mittels Operatoren

Jeder Ausdruck hat einen Typ und einen Wert. Bei der Bewertung von Ausdrücken gelten folgende Regeln: Daten vom Typ char oder short werden sofort in den Typ int umgewandelt (integral promotion). Bei der Kombination von Ausdrücken wird balanciert, d.h. der dem Wertebereich oder Speicherplatz nach kleinere Typ wird in den beteiligten, dem Wertebereich oder Speicherplatz nach größeren Typ umgewandelt. Dabei wird versucht, den Wert zu erhalten (value preservation).

Die Bewertung der einzelnen Elemente von Ausdrücken folgt Vorrang und Assoziativität der Operatoren. Bei Gleichheit in diesen Eigenschaften ist die Reihen-folge der Bewertung (order of evaluation) gleichwohl bis auf wenige Ausnahmen undefiniert, denn der Compiler darf sie auf für ihn günstige Weise ändern, wenn das Ergebnis aufgrund der üblichen mathematischen Regeln gleichwertig wäre. In der Theorie gilt (a * b)/c = (a/c) * b, also darf der Compiler das nach seinem Gusto umordnen, und auch Gruppierungsklammern können ihn nicht daran hindern. Das kann aber bei den Darstellungs-begrenzten Datentypen im Computer schon zu unerwünschtem Überlauf etc. führen.

Soll dies wirklich verhindert werden, d.h., soll der Compiler gezwungen werden, eine bestimmte Reihenfolge einzuhalten, muss die entsprechende Rechnung aufgebrochen und in mehreren Teilen implementiert werden. Die Codesequenzen

x = (a * b) / c;

und

x = a * b;
x = x / c;

bewirken tatsächlich nicht automatisch das gleiche, denn im ersten Fall darf der Compiler umsortieren, im zweiten nicht, da das Semikolon einen so genannten Sequenzpunkt (sequence point) darstellt, den der Compiler nicht entfernen darf.

Manche Operatoren bewirken sog. Seiteneffekte (side effects), d.h. sie können den Zustand des Rechners verändern, z.B. den Wert von Speichervariablen oder Registern oder sonstiger Peripherie. Dazu gehören neben den Zuweisern auch die Post- und Präinkrement und -dekrement-Operatoren und Funktionsaufrufe. Das Eintreten der Wirkung dieser Seiteneffekte sollte niemals abhängig von der Reihenfolge der Bewertung sein! Während durch Komma separierte Deklarations- und Definitionslisten strikt von links nach rechts abgearbeitet und bewertet werden, gilt das z.B. für die Reihenfolge der Bewertung in Parameterlisten beim Funktionsaufruf nicht.

Anweisungen bzw. Statements in C

In C gibt es folgende Anweisungen (statements):

  • Leeranweisung ; (empty statement)
  • Audrucksanweisung expression; (expression statement)
  • Blockanweisung { ... } (block statement)
  • markierte Anweisung label: statement (labeled statement)
  • Auswahlanweisung if else switch ... case (selection statement)
  • Wiederholungsanweisung for while do ... while (iteration statement)
  • Sprunganweisung goto break continue return (jump statement)

Dies sind schon einmal die wesentlichen Grundaspekte der Syntax von C. Allerdings umfasst diese noch weitere Elemente, die es sich genauer zu betrachten lohnt. Im nächsten Beitrag zu den syntaktischen Elementen von C gehen wir näher auf Kontrollstrukturen und Funktionen in der Programmiersprache ein.

Hinweis: Dieser Beitrag ist ein Auszug aus dem Handbuch „Embedded Systems Engineering“ . Dieses ist auch als kostenlose PDF-Version in voller Länge auf ELEKTRONIKPRAXIS.de verfügbar.

Dieser Beitrag, mit Ausnahme der ersten beiden und des letzten Absatzes, ist Copyright © Bernd Rosenlechner 2007-2010. Dieser Text kann frei kopiert und weitergegeben werden unter der Lizenz Creative Commons – Namensnennung – Weitergabe unter gleichen Bedingungen (CC – BY – SA) Deutschland 2.0.

* Prof. Dr. Christian Siemers lehrt an der Technische Universität Clausthal und arbeitet dort am Institut für Elektrische Informationstechnik (IEI).

(ID:45405485)