- C-Code
-
C Paradigmen: imperativ, strukturiert Erscheinungsjahr: 1972 Entwickler: Dennis Ritchie & Bell Labs wichtige Implementierungen: GCC, MSVC, Borland C, Portland Group, Intel Einflüsse: B (BCPL,CPL), ALGOL 68, Assembler Beeinflusste: awk, C++, C#, Objective-C, D, Java, JavaScript, Perl, PHP C ist eine imperative Programmiersprache, die der Informatiker Dennis Ritchie in den frühen 1970er Jahren an den Bell Laboratories für das Betriebssystem Unix entwickelte. Seitdem ist sie auf vielen Computer-Systemen verbreitet.
Die Anwendungsbereiche von C sind sehr verschieden. Es wird zur System- und Anwendungsprogrammierung eingesetzt. Die grundlegenden Programme aller Unix-Systeme und die Systemkerne vieler Betriebssysteme sind in C programmiert. Zahlreiche Sprachen, wie C++, Objective-C, C#, Java, PHP oder Perl orientieren sich an der Syntax und anderen Eigenschaften von C.
Inhaltsverzeichnis
Überblick
C ist eine Programmiersprache, die auf fast allen Computersystemen zur Verfügung steht. Sie zählt zu den sogenannten prozeduralen Programmiersprachen. Um den Wildwuchs zahlreicher Dialekte einzudämmen, wurde C mehrfach standardisiert (C89, C95, C99, ISO-C). Abgesehen vom Mikrocontrollerbereich, wo eigene Dialekte existieren, sind die meisten aktuellen PC-/Server-Implementierungen eng an den Standard angelehnt; eine vollständige Implementierung aktueller Standards ist aber selten. In den meisten C-Systemen mit Laufzeitumgebung steht auch die genormte Standard C Library zur Verfügung. Dadurch können C-Programme, die keine sehr Hardware-nahe Programmierung enthalten, in der Regel gut auf andere Zielsysteme portiert werden. Konzeptionell ist C auf einfache Kompilierbarkeit ausgelegt. Die Compiler erzeugen in der Regel aber auch nur wenig Code zur Gewährleistung der Sicherheit zur Laufzeit der Programme.
Die Verbreitung von C ist hoch, und viele Programmierschnittstellen für Anwendungsprogramme werden in Form von C-Schnittstellen implementiert.
Geschichte
Frühe Entwicklungen
C wurde 1971-1973 von Dennis Ritchie in den Bell Laboratories für die Programmierung des damals neuen UNIX-Betriebssystems entwickelt. Er stützte sich dabei auf die Programmiersprache B, die Ken Thompson in den Jahren 1969/70 geschrieben hatte. B wiederum geht auf die von Martin Richards Mitte der 1960er-Jahre entwickelte Programmiersprache BCPL zurück. Ritchie schrieb auch den ersten Compiler für C. 1973 war die Sprache so weit ausgereift, dass man nun den Unix-Kernel für den PDP-11 neu in C schreiben konnte.
K&R C
1978 veröffentlichten Brian W. Kernighan und Dennis Ritchie die erste Auflage von The C Programming Language (deutsch: Programmieren in C). Die darin beschriebene Fassung von C, die nach den Buchautoren „K&R C“ genannt wird, erweiterte die ursprüngliche Sprache um neue Schlüsselwörter wie
long int
oderunsigned int
und führte erstmals die I/O-Standardbibliothek ein. Bis zur Standardisierung der Sprache diente die von K&R beschriebene Spezifikation als informelle Referenz für das Programmieren in C.Normen und Standards
→ Hauptartikel: Varianten der Programmiersprache C
C verbreitete sich rasch und wurde laufend weiterentwickelt. Das führte dazu, dass das von Kernighan und Ritchie beschriebene C nicht mehr dem C entsprach, das von den Compilern unterstützt wurde. Um eine Normierung der Sprache zu erreichen, setzte das American National Standards Institute (ANSI) 1983 ein Komitee namens X3J11 ein, das 1989 schließlich die Norm ANSI X3.159-1989 Programming Language C verabschiedete. Ein Jahr später übernahm die ISO diesen Standard (mit kleinen Änderungen) als C90. 1995 veröffentlichte die ISO eine Ergänzung zum Standard (C95) und 1999 schließlich ISO/IEC 9899:1999. Mit diesem Standard, der als C99 bekannt ist, flossen auch aus C++ bekannte Erweiterungen zurück in die Sprache C.
Verwendung
Das Haupteinsatzgebiet von C liegt in der Systemprogrammierung, einschließlich der Erstellung von Betriebssystemen und der Programmierung von eingebetteten Systemen. Der Grund liegt in der Kombination von erwünschten Charakteristiken wie Portabilität und Effizienz mit der Möglichkeit, Hardware direkt anzusprechen und dabei niedrige Anforderungen an die Laufzeitumgebung zu haben.
Auch Programme für End-Anwender werden oft in C erstellt.
Wegen der relativ hohen Geschwindigkeit und geringen Codegröße werden Compiler, Programmbibliotheken und Interpreter anderer höherer Programmiersprachen (wie zum Beispiel die JVM) oft in C implementiert.
C wird als Zwischensprache einiger Implementationen höherer Programmiersprachen verwendet. Dabei wird diese zuerst in C-Code übersetzt, der dann kompiliert wird. Dieser Ansatz wird entweder verwendet, um die Portabilität zu erhöhen (C-Compiler existieren für nahezu jede Plattform) oder aus Bequemlichkeit, da kein maschinenspezifischer Codegenerator entwickelt werden muss. Einige Compiler, die C auf diese Art benutzen, sind EiffelStudio, Esterel, Gambit, der Glasgow Haskell Compiler, einige Lisp-Compiler, Lush, PyPy, Sather, Squeak und Vala.
C wurde als Programmiersprache und nicht als Zielsprache für Compiler entworfen. Als Zwischensprache ist es daher eher schlecht geeignet. Das führte zu C-basierten Zwischensprachen wie C--.
Auch wird C oft für die Erstellung von Anbindungen (z. B. JNI) genutzt.
Eigenschaften
- C gehört zu den imperativen Programmiersprachen
- C besitzt eine relativ kleine Menge an Schlüsselwörtern. Die Anzahl der Schlüsselwörter ist so gering, weil viele Aufgaben, welche in anderen Sprachen über eigene Schlüsselwörter realisiert werden, über einzubindende Bibliotheksroutinen (wie z.B. die Ein- und Ausgabe auf der Console oder Dateien, die Verwaltung des dynamischen Speichers usw.) oder über spezielle syntaktische Konstrukte (z.B. Variablendeklarationen) realisiert werden.
- C ermöglicht direkte Speicherzugriffe und sehr hardwarenahe Konstrukte. Es eignet sich daher gut zur Systemprogrammierung. Sollen Programme portierbar sein, sollte von diesen Möglichkeiten aber möglichst wenig Gebrauch gemacht werden.
- C schränkt direkte Speicherzugriffe kaum ein. Dadurch kann der Compiler (anders als z. B. in Pascal) nur sehr eingeschränkt bei der Fehlersuche helfen. Aus diesem Grund ist C für sicherheitskritische Anwendungen (Medizintechnik, Verkehrsleittechnik, Raumfahrt) weniger geeignet.
- C enthält einige sicherheitskritische Funktionen; so überschreibt z. B. gets() fremde Speicherbereiche, wenn es auf eine unpassende (zu lange) Eingabe stößt. Der Fehler ist innerhalb von C weder bemerk- noch abfangbar. Um den großen Vorteil von C – die Existenz zahlreicher älterer Quellcodes – nicht zu verlieren, unterstützen auch aktuelle Implementierungen weiterhin diese und ähnliche Funktionen, warnen jedoch in der Regel, wenn sie beim Übersetzen im Quelltext benutzt werden.
- Historisch bedingt existieren in C keine Funktionen zur positionierten Ausgabe. Es existieren jedoch zahlreiche Bibliotheken, die für das jeweilige Zielsystem eine solche Ausgabe ermöglichen.
- C verwendet im Quellcode einige Sonderzeichen (z. B. {, |, &), die in der Vergangenheit nicht auf allen Zielsystemen zur Verfügung standen. Das hat dazu geführt, dass C sich auf jenen Systemen nicht verbreiten konnte. C bietet zwar eine Alternativschreibweise über Trigraphen, was jedoch die Lesbarkeit des Quelltextes enorm verschlechtert.
- Eine Modularisierung in C erfolgt auf Dateiebene. Eine Datei bildet eine Übersetzungseinheit; intern benötigte Funktionen und Variablen können so vor anderen Dateien verborgen werden. Die Bekanntgabe der öffentlichen Funktionsschnittstellen erfolgt mit sogenannten Headerdateien. Damit verfügt C über ein schwach ausgeprägtes Modulkonzept.[1][2]
Die Programmiersprache C wurde mit dem Ziel entwickelt, eine echte Sprachabstraktion zur Assemblersprache zu implementieren. Es sollte eine direkte Zuordnung zu wenigen Maschineninstruktionen geben, um die Abhängigkeit von einer Laufzeitumgebung zu minimieren. Als Resultat dieses Designs ist es möglich, C-Code auf einer sehr hardwarenahen Ebene zu schreiben, analog zu Assemblerbefehlen. Die Portierung eines C-Compilers auf eine neue Prozessorplattform ist, verglichen mit anderen Sprachen, wenig aufwändig. Beispielsweise ist der freie GNU-C-Compiler (gcc) für eine Vielzahl unterschiedlicher Prozessoren und Betriebssysteme verfügbar. Für den Entwickler bedeutet das, dass unabhängig von der Zielplattform fast immer auch ein C-Compiler existiert. C unterstützt damit wesentlich die Portierbarkeit von Programmen (sofern der Programmierer auf Assemblerteile im Quelltext und/oder hardwarespezifische C-Konstrukte verzichten kann). Bei Microcontrollern ist C die mit Abstand am häufigsten verwendete Hochsprache.
Datentypen
Jeder Ausdruck und jeder Bezeichner in C hat einen bestimmten Datentyp. C unterscheidet zwischen:
- Objekttypen (object types)
- Funktionstypen (function types)
- unvollständige Typen (incomplete types)
Außerdem wird zwischen Basisdatentypen (basic types) und abgeleiteten Typen (derived types) unterschieden.
Basisdatentypen
C verfügt über 19 Basisdatentypen (eng. basic types). Diese werden in Datentypen für Ganzzahl-, und Gleitkommazahlen aufgeteilt. Die verschiedenen Typen ermöglichen bei unterschiedlichem Speicherbedarf das Speichern von Daten aus einem unterschiedlich großen maximalen Wertebereich.
void
ist der leere Datentyp. Er kann keine Werte aufnehmen.void
ist ein unvollständiger Typ, darum können keine Variablen von diesem Typ deklariert werden. Er dient als Platzhalter für den Rückgabetyp von Funktionen, die keinen Wert zurückgeben, sowie zur Bildung von Zeigertypen, die auf Objekte mit unspezifiziertem Typ verweisen (void*
).
_Bool
dient zur Repräsentation von Wahrheitswerten. Er existiert als eigener Datentyp seit C99 und kann nur die Werte 0 und 1 aufnehmen. Die Größe eines_Bool
s ist plattformabhängig und kann größer als 1 char sein!
char
undwchar_t
dienen zur Speicherung alphanumerischer Daten. Sie werden in C als Ganzzahl-Datentypen (mit besonderen Eigenschaften) behandelt. Außerdem repräsentiert einchar
die kleinste adressierbare Einheit in C. Die Größe von Objekten und Typen wird stets als ganzzahliges Vielfache von einemchar
angegeben.
int
ist der Standarddatentyp für ganzzahlige Werte. Für eventuell größere oder kleinere Wertebereiche existieren die Typensigned char
,short int
,long int
und (seit C99)long long int
. Da der Sprachstandard die genaue Größe bzw. den Wertebereich eines Typs nicht festschreibt, gilt nur folgende Relation:
-
signed char
≤short int
≤int
≤long int
≤long long int
.- („≤“ bedeutet dabei, dass der rechts stehende Typ alle Werte des links stehenden Typs aufnehmen kann.)
- Zu all diesen Typen existieren noch vorzeichenlose Typen, die durch ein vorangestelltes
unsigned
notiert werden. Diese benötigen den gleichen Speicherplatz wie ihre entsprechenden vorzeichenbehafteten Typen. - Optional können die vorzeichenbehafteten Typen auch durch ein vorangestelltes
signed
gekennzeichnet werden und das Schlüsselwortint
kann bei den mehrteiligen Typnamen entfallen. - Der Typ
char
ist ein eigener Datentyp, der jedoch – je nach Plattform – entweder zusigned char
oder zuunsigned char
ein identisches Bit-Layout und identische Rechenregeln besitzen muss. - Für jeden Typ schreibt der Standard eine Mindestgröße vor; In einer Implementierung können die Werte auch größer sein. Die tatsächliche Größe eines Typs ist in der Headerdatei
<limits.h>
abgelegt.INT_MAX
ersetzt der Präprozessor beispielsweise durch den Wert, den der Typint
maximal annehmen kann. [3]
float
,double
undlong double
sind die drei Datentypen für Gleitkommazahlen. Auf den meisten Architekturen entsprechenfloat
unddouble
den IEEE-Datentypen. Welchen Wertebereich ein Gleitkommazahltyp auf einer Implementierung einnimmt ist ebenfalls plattformabhängig, der Standard legt nur wieder fest, dass der Wertebereich vonfloat
nachdouble
und vondouble
nachlong double
jeweils entweder gleich bleibt oder zunimmt. Die genauen Eigenschaften und Wertebereiche auf der benutzten Architektur können über die Headerdatei<float.h>
ermittelt werden. [4]
Zusätzlich existieren seit C99 noch drei Gleitkomma-Datentypen für komplexe Zahlen, welche aus den drei Gleitkommatypen abgeleitet sind:
float _Complex
,double _Complex
undlong double _Complex
. Ebenfalls in C99 eingeführt wurden Gleitkomma-Datentypen für rein imaginäre Zahlen:float _Imaginary
,double _Imaginary
undlong double _Imaginary
. Diese 3 Typen sind jedoch auch in einer hosted-Umgebung optional, während die_Complex
-Typen nur in einer freestanding-Umgebung entfallen dürfen[5].Abgeleitete Typen
Aus den Basisdatentypen und bereits deklarierten abgeleiteten Typen lassen sich beliebig viele weitere Typen ableiten. Abgeleitete Typen werden wie folgt aufgeteilt:
- Felder (array types)
- Zu jedem Typ
T
existieren Feldtypen derart "Feld von n Elementen vom Typ T", die dannT[n]
geschrieben werden.
- Zeiger (pointer types)
- Zu jedem Typ
T
existiert ein Typ "Zeiger auf T", derT*
geschrieben wird.
- Funktionen (function types)
- Zu jedem Typ
T
existieren Typen "Funktion, die ein T zurückgibt". Dies wird durch Anhängen von()
gekennzeichnet, wobei in die Klammern ggf. noch die Typen der Funktionsparameter geschrieben werden.
- zusammengesetzte Typen (structure types und union types)
- Hierbei werden mehrere Objekte zu einem neuen Typ zusammengefasst.
Deklarationen
Bevor Variablen, Typen und Funktionen in C verwendet werden können, müssen sie deklariert werden. Eine so deklarierte Variable, Typ oder Funktion erhält einen Namen (Bezeichner). Die Vergabe von Bezeichnern ist an bestimmte Regeln gebunden:
- Es dürfen nur ASCII-Buchstaben, Ziffern, und der Unterstrich verwendet werden.
- Schlüsselworte der Sprache - wie z.B. if, void, auto, und andere - sind nicht erlaubt
- Als Bestandteile anderer Worte - wie z.B. bei automobil - sind sie erlaubt.
- Das erste Zeichen eines Variablennamens muss immer ein Buchstabe oder Unterstrich sein.
Seit C95 sind auch Zeichen aus dem Universal Character Set in Bezeichnern erlaubt, sofern die Implementierung es unterstützt. Die erlaubten Zeichen sind in Anhang D des ISO-C-Standards aufgelistet. Vereinfacht gesagt, sind es all jene Zeichen, die in irgendeiner Sprache als Buchstabe oder buchstabenähnliches Zeichen Verwendung finden. Im Quelltext lassen sich diese Zeichen plattformunabhängig über eine Escape-Sequenz wie folgt ersetzen:
\uXXXX
(wobei X für eine Hexadezimalziffer steht) für Zeichen mit einem Code von 00A0hex bis FFFFhex.\UXXXXXXXX
für alle Zeichen mit einem Code ≥00A0hex.
Das folgende Beispiel in der Programmiersprache C deklariert und definiert die Namen variable_1 und variable_2 als Variablen vom Typ Integer:
void beispiel1() { int variable_1, variable_2; }
Sprachdesign
Ein C-Programm wird durch den sogenannten Linker oder Binder aus Objektcode zum ausführbaren Computerprogramm gebunden. Dabei können mehrere Objektcodedateien zu einem Programm zusammengefasst werden. Die Objektcodedateien ihrerseits werden durch den Compiler aus Textdateien erzeugt (übersetzt), die eine Anzahl Funktions- und Variablendefinitionen enthalten. Neben Programmen kann man aber auch noch Bibliotheken erstellen. Diese werden ähnlich wie Programme gebunden oder zu einem Archiv zusammengefasst. Diese Bibliotheken können dann in einem späteren Bindevorgang wiederum zu einem Programm hinzugebunden werden. Auf diese Weise kann man verhindern, dass für jedes zu erzeugende Programm unzählige (in größeren Systemen durchaus hunderte bis tausende) unveränderliche Objektcodedateien immer wieder erneut gebunden werden müssen.
Das Design der Programmiersprache, die Technik des Linkens und verschiedene zu festen Sprachelementen gewordene Funktionen und Festlegungen sind eng mit dem Design Unix-artiger Betriebssysteme verbunden, so die Art und Weise der Signalbearbeitung, die Ein- und Ausgabe mit Standard-Datenströmen (Streams) und das Verfahren des Startens und Beendens eines Programms.
Beispielprogramm in C
Der folgende Quelltext stellt ein einfaches C-Programm dar, das die Textzeile Hallo Welt!, gefolgt von einem Zeilenumbruch, ausgibt. Dieses Beispiel folgt den Vorgaben des ANSI-C Standards; andere Versionen dieses Programms sind im Artikel Hallo-Welt-Programm beschrieben.
#include <stdio.h> #include <stdlib.h> int main(void) { printf("Hallo Welt!\n"); return EXIT_SUCCESS; } /* end main() */
Erläuterungen
In der ersten Zeile ermöglicht die Präprozessoranweisung
#include <stdio.h>
die spätere Verwendung von Funktionen aus der Ein-/Ausgabe-Bibliothek stdio (auch „standard-input/output“ genannt). Diese include-Anweisung veranlasst den C-Präprozessor, vor der Übersetzung die Headerdateistdio.h
in den Quelltext zu kopieren, die unter anderem eine Deklaration der weiter unten verwendeten Ausgabefunktionprintf
enthält. In der zweiten Zeile wird die Headerdateistdlib.h
eingebunden, die anstatt eines festen numerischen Werts die symbolische KonstanteEXIT_SUCCESS
definiert, damit die erfolgreiche Programmausführung dem Aufrufer plattformunabhängig signalisiert werden kann (siehe Zeile 7 des Programms). Include-Anweisungen können zwar an jeder Stelle im Quelltext eingefügt werden, meist werden sie jedoch an den Anfang eines Programmtextes gestellt, um die Übersichtlichkeit zu erhöhen.In der vierten Zeile beginnt das eigentliche Programm mit der Definition der Funktion
main
. Sie ist die Einstiegsfunktion eines C-Programms.main
wird automatisch als erste Funktion aufgerufen. Anfang und Ende der Funktionmain
werden durch die beiden geschweiften Klammern markiert.Die erste Anweisung innerhalb der Funktion
main
ruft die Funktionprintf
auf. Die zweite Anweisung ist die Sprunganweisungreturn EXIT_SUCCESS;
. Diese legt den Rückgabewert von main fest. Damit wird der „Erfolgsstatus“ des ausgeführten Programms zum Ausdruck gebracht. Der WertEXIT_SUCCESS
bedeutet hier fehlerfreie Ausführung.In der letzten Zeile folgt auf die schließende geschweifte Klammer ein Kommentar, eingeschlossen durch die Zeichenfolgen
/*
und*/
. Kommentare werden bei der Übersetzung ignoriert; sie sind erster Linie für den menschlichen Leser gedacht, können aber auch von automatischen Software-Dokumentationswerkzeugen ausgewertet werden.Die Standardbibliothek
Die C-Standardbibliothek ist integraler Bestandteil einer gehosteten C-Implementation. Sie enthält u. a. Makros und Funktionen, die mittels der Standard-Header-Datei verfügbar gemacht werden. Auf freistehenden Implementationen dagegen kann der Umfang der Standardbibliothek eingeschränkt sein.
Literatur
- Brian W. Kernighan, Dennis Ritchie: Programmieren in C, Hanser Fachbuch, 2. Auflage, 1990, ISBN 3-446-15497-3.
- Samuel P. Harbison III, Guy L. Steele Jr.: C a reference manual, Prentice Hall, 2002, ISBN 0-13-089592-X.
Weblinks
- Ritchie über die Geschichte von C (engl.)
- Abhandlung über die Urzeiten von C und Compilerversionen von 1972/73 (engl.)
- Programming in C – umfangreiche Linksammlung (engl.)
- FAQ der Newsgroup de.comp.lang.c
- C von A bis Z Onlinebuch Einstieg in C von Jürgen Wolf
Einzelnachweise
- ↑ Scheler, Stilkerich, Schröder-Preikschat: Komponenten/Module (PDF)
- ↑ Bertrand Meyer: Objektorientierte Softwareentwicklung. Hanser, Wien, München; Prentice Hall Internat. 1990, S. 406 ISBN 3-446-15773-5.
- ↑ Klaus Schmaranz: Softwareentwicklung in C, Springer, 2001, Seite 29 ff.
- ↑ Helmut Herold und Wolfgang Unger: "C"-Gesamtwerk, te-wi Verlag, 2. Auflage, München, 1992, 2-5 bis 2-7
- ↑ ISO/IEC 9899:1999 Kapitel 7.6.2 Absatz 3
Wikimedia Foundation.