C (Programmiersprache)
Die Programmiersprache C wurde von Ken Thompson und Dennis Ritchie in den frühen 70er Jahren für das neu entwickelte Betriebssystem Unix entworfen. Ken Thompson passte zunächst die Programmiersprache BCPL auf seine Bedürfnisse an und nannte die so entstandene Sprache "B" (nach den Bell Laboratories, in denen die Sprache entwickelt wurde). Aus dieser Sprache entstand dann C. Die grundlegenden Programme aller Unix-Systeme und die Kernel vieler Betriebssysteme sind in C programmiert.
Die Sprache C++ ist aus C hervorgegangen und gegenüber C unter anderem um Möglichkeiten zur objektorientierten und generischen Programmierung erweitert.
Überblick
C ist eine Programmiersprache, die auf fast allen Computersystemen zur Verfügung steht. Im Gegensatz zu z.B. BASIC gibt es seit der Definition von ANSI C relativ einheitliche Implementierungen auf den verschiedenen Plattformen. In jedem C-System mit Laufzeitumgebung steht auch die genormte Standard C Library zur Verfügung. Dies und die bei geschickter Programmierung hohe Leistung der resultierenden Programme erklärt die weiterhin relativ hohe Popularität der Sprache, sowohl im kommerziellen als auch im Open-Source-Bereich.
Es gibt Meinungen, C entspreche nicht dem heutigen Stand der Technik; manchmal wird C auch als portierbarer Highlevel-Assembler bezeichnet. Die Kerne fast aller heute verbreiteten Betriebssysteme wurden aber in C implementiert, und dies, obwohl C++ bereits seit Mitte der 80er Jahre zur Verfügung steht, wenn auch in der jetzigen, ISO-genormten Form erst seit 1998.
C eignet sich gut für die Systemprogrammierung. Anders sieht die Sache jedoch bei der Anwendungsentwicklung aus. Dort wird C zunehmend durch die Sprachen C++, Java und C# verdrängt, die gemeinsam haben, dass sie alle über Möglichkeiten zur objektorientierten Programmierung verfügen und C im Bezug auf Wartbarkeit, Entwurfsunterstützung und Abstraktionsniveau erweitern und verbessern.
An anderer Stelle zeigen sich die einfache Struktur und der kleine Umfang der Sprache als großer Vorteil: Die Portierung eines C-Compilers auf eine neue Prozessorplattform ist verglichen mit anderen Sprachen wenig aufwändig, so dass sich dies vom einfachsten Mikrocontroller bis zum Großrechner-Prozessor praktisch immer lohnt. Der GNU C-Compiler gcc ist beispielsweise für eine Vielzahl unterschiedlichster Prozessoren und Betriebssysteme verfügbar. Für den Entwickler bedeutet dies, dass unabhängig von der Zielplattform fast immer auch ein C-Compiler für diese spezifische Plattform existiert. Die prozessorspezifische Programmierung in Assembler bleibt ihm dadurch ganz oder weitgehend erspart, und Quellcode, der für andere Plattformen geschrieben wurde, kann oftmals mit keinen oder nur wenigen Änderungen auf einer neuen Plattform weiterverwendet werden.
Die Sprachbeschreibung wurde 1972 erstmals publiziert. Im Jahre 1989 wurde die Sprache erstmals standardisiert (C89). Dieser Standard wurde überarbeitet und 1999 erschien dann der internationale Standard ISO/IEC 9899:1999, der als C99 bekannt ist. Siehe auch Varianten der Programmiersprache C.
Ein ausführbares C-Programm wird durch den so genannten Linker oder Binder aus Objektcode erzeugt (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 in ein 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 unixartiger Betriebssysteme verbunden, so die Art und Weise der Signalbearbeitung, der Ein- und Ausgabe mit Streams, und das Verfahren des Startens und Beendens eines Programms.
Hallo-Welt-Programm in C
Der folgende Quelltext stellt ein einfaches C-Programm dar, das die Meldung Hallo Welt!
und einen Zeilenumbruch auf der Standardausgabe ausgibt.
#include <stdio.h>
int main (void)
{
printf("Hallo Welt!\n");
return 0;
}
Erläuterung: In der ersten Zeile wird durch #include <stdio.h>
die Verwendung der Ein-/Ausgabe-Bibliothek stdio und damit der Funktion printf
ermöglicht. stdio.h
ist eine so genannte Headerdatei. Sie enthält u.a. den Prototyp von printf
und wird durch den Präprozessor eingefügt. Danach kann der Compiler Aufrufe für printf
generieren, ohne ihren tatsächlichen Code zu kennen, der erst beim Linken geliefert wird. Dies gilt für die überwiegende Mehrheit aller C-Systeme.
In der dritten Zeile (die zweite ist eine Leerzeile, von der beliebig viele existieren können) beginnt der eigentliche Programmablauf mit dem Aufruf der Funktion main
. Sie ist die Hauptfunktion eines C-Programmes und in diesem kleinen Programm sogar die einzige. main
wird immer als erste Funktion aufgerufen und hat wie alle Funktionen einen Anfang (die öffnende geschweifte Klammer) und ein Ende (die schließende geschweifte Klammer).
Die erste Anweisung in der Funktion main
ist eine Ausdrucksanweisung welche die Funktion printf
aufruft. Diese Funktion ist nicht Teil des Sprachkerns, sondern der Standardbibliothek. Daher muss sie (gemäß C99) vor ihrer Verwendung deklariert werden. Dies geschieht durch die Präprozessordirektive #include <stdio.h>
, die in der ersten Zeile angegeben wurde. Die Funktionen der Standardbibliothek werden in aller Regel durch eine Bibliothek mit Hilfe eines Linkers an das Programm gebunden, jedoch kann ein Compiler auch auf einem anderen Weg die Funktion printf
zur Verfügung stellen.
Die zweite Anweisung ist die Sprung-Anweisung return 0;
. Die return
-Anweisung ist Teil des Sprachkerns. Für sie ist ein Header (#include...
), wie dies bei printf
der Fall war, nicht nötig. Der Rückgabewert 0
soll von dem Aufrufer des Programms als fehlerfreie Ausführung interpretiert werden.Alternativ kann man auch return EXIT_SUCCESS;
benutzen. Dazu
wird allerdings der Header stdlib.h
benötig, in welchen das Makro EXIT_SUCCESS definiert ist. Diese Makro wird dann vom Präprozessor in den systemspezifischen Wert für
eine fehlerfreie Ausführung expandiert.
Man kann der Funktion main
beim Aufruf Werte übergeben, die dann zwischen den runden Klammern angegeben werden. Man spricht dabei auch von Parameterliste. Wird void
zwischen den Klammern angegeben, so ist dies gleichbedeutend mit keine Parameter. Fügt man hingegen den Text int argc, char* argv[] in die Parameterliste ein, lässt sich während des Programmablaufs mittels der Ganzzahlvariable argc die Anzahl der beim Programmstart übergebenen Argumente abfragen:
int main (int argc, char* argv[])
{
/* ... */
}
Ein Zugriff auf die Zeichenketten-Reihung argv[] ermöglicht die Abfrage des n-ten Arguments, wobei hierzu eine Ganzzahl zwischen 0 und n-1 in die eckigen Klammern von argv[] einzusetzen ist. Nach der Abarbeitung erhält man von der Funktion main
einen Wert zurück. Dieser Wert ist vom Typ int
. Typ int
bedeutet, dass eine vorzeichenbehaftete Ganzzahl zurückgeliefert wird.
Programmieren in C
Die Programmierung mit der Sprache C kann nach vier Kriterien unterteilt werden:
- Der erste Betrachtungspunkt ist die Sprache selbst. Also der Kern der Sprache, der vom Compiler direkt verstanden wird. Der Sprachkern wird in dem Kapitel zur Beschreibung der Sprache genauer erklärt werden.
- Die Bibliotheksfunktionen, die in jedem ("hosted") C-Compiler zur Verfügung stehen, solange der Compiler den Anspruch hat, ANSI-kompatibel zu sein. Verwendet ein Programm lediglich den Sprachkern gemäß der Sprachdefinition und die Standardbibliothek, so ist es auf jedem System, das einen ANSI-kompatiblen Compiler zur Verfügung stellt, übersetzbar und zeigt auch auf allen Systemen das gleiche Verhalten.
- Die Bibliotheksfunktionen, die von dem (Betriebs-)System zur Verfügung gestellt werden. Diese Bibliotheken stammen also nicht von einem ANSI-kompatiblen Compiler. Daher sind Programme, die diese Bibliotheken verwenden, nur bedingt portabel. Ein Teil dieser Bibliotheken wird in dem Kapitel Weitere C-Bibliotheken beschrieben.
- Die Erweiterungen des Sprachkerns, die von einem Compiler zur Verfügung gestellt werden. Diese Erweiterungen betreffen die Werkzeuge.
Beschreibung der Sprache
Die Beschreibung der Sprache bezieht sich auf das Kapitel 6. Language in C99.
- Konzepte
- Geltungsbereich von Bezeichnern
- Bindung von Bezeichnern
- Speicherdauer von Objekten
- Typen
- lexikalische Elemente
- Schlüsselwörter
- Bezeichner
- Konstanten
- Kommentare
- Deklarationen / Definition
- Ausdrücke
- Anweisungen
- Der Präprozessor
Die Standardbibliothek
Die Standardbibliothek (Standard C Library) beschreibt die Makros und Funktionen, die jedem Compiler zur Verfügung steht.
Weitere C-Bibliotheken
Die nachfolgend beschriebenen Bibliotheken sind nicht Teil der Sprachbeschreibung von C. Daher stehen sie nur zur Verfügung, wenn die API des Systems sie anbietet.
- POSIX - Programmierung einer UNIX-Umgebung
- pcre - Perl Compatible Regular Expressions
- Zugriff auf eine MySQL-Datenbank
Werkzeuge
Bewertung von Sprachmerkmalen
Stärken
- Minimalistischer Sprachumfang: Der kleinste bekannte C-Compiler besteht aus 3742 Bytes C-Code und kann sich selbst kompilieren.
- Keine Felder (ersetzt durch Feldkonstanten und Behandlung von Feldern als Zeigerkonstanten)
- Zeigerarithmetik ermöglicht die effiziente Behandlung von Feldzugriffen, Parametern usw.
- Zeiger auf Unterprogramme (Funktionszeiger) in Datenstrukturen speicherbar
- Einfache Variablen nur als Wertparameter, Felder nur als Referenzparameter
- Referenzparameter für einfache Variablen werden durch Zeiger ersetzt.
- Hardwarenahe Programmierung ist möglich, direkter Umgang mit Bits, Bytes, direkter Speicherzugriff und Zeigerarithmetik
- Präprozessor zur Spracherweiterung und bedingten Übersetzung
- Linker (Binder) (C war eine der ersten Sprachen, die das Einbinden von externen vorübersetzten Routinen in der Sprachdefinition berücksichtigt)
- Viele Optimierungen sind bei Übersetzung möglich
- Ein C-Compiler ist für nahezu jede Prozessorarchitektur vorhanden, seien es "historische" 8-Bit-Prozessoren und einfache Mikrocontroller oder aktuelle 64-Bit-Prozessoren.
Schwächen
- Fehlendes Modulkonzept (wird üblicherweise durch Paare von .c- und .h-Dateien notdürftig nachgebaut)
- Nur eingeschränkt typsicher
- Die Sprachdefinition besitzt Lücken (Verhalten undefiniert)
- Verglichen mit anderen prozeduralen Sprachen wie (das ältere!) Pascal oder Modula-2 zu wenig klare Syntax, was zu sehr komplexen und fehleranfälligen Compilern führt
- "Wilde" Zeiger
- Man kann Zeiger auf beliebige Stellen des Speichers richten. Insbesondere zeigen nicht explizit initialisierte Stack-Variablen oft auf beliebige Stellen des Speichers. Die Folge sind schwer zu diagnostizierende Fehler.
- Felder
- C kennt zwar den Datentyp Feld und erlaubt sogar die Definition von Feldern, die mit Konstanten vorbelegt sind. Intern werden Felder jedoch immer als Zeiger verwaltet. Dies bedeutet, dass eine eventuell nötige dynamische Speicherverwaltung von Feldern vom Programmierer implementiert werden muss. Auch die Feldgröße wird beim Zugriff nicht überprüft. Durch Programmierfehler können Speicherbereiche durch illegale Feldzugriffe während der Laufzeit unabsichtlich oder gezielt (Pufferüberlauf) verändert werden.
- Mehrdimensionale Felder werden in der numerischen Mathematik für Matrizen benötigt. Dafür ist die Struktur der C-Felder jedoch völlig ungeeignet. In Numerical Recipes in C, Kap. 1.2, Seite 20 ff (online lesbar!) wird dieses Problem diskutiert und die in diesem Buch verwendete Lösung erläutert. Natürlich ist es auch eine Stärke von C, dass eine derartige Schwäche überhaupt durch eine Lösung ausgeglichen werden kann, die darüberhinaus auch noch elegant ist.
- Zeichenketten
- C kennt Strings, die nach den Grundsätzen der 60er Jahre durchaus in ein Gesamtkonzept integriert sind. Sie werden als so genannte nullterminierte Strings in Variablen und Konstanten ("Hallo") gespeichert und durch ihre Adressen (Zeiger!) verwendet. Die große Schwäche dieses Konzepts liegt in der großen Programmiererverantwortung, denen reale Programmierer nicht immer gewachsen sind (wilde Zeiger, zu kleine Felder, vergessene Abschlussnull...)
- C hat keine integrierten Zeichenketten ("Strings"). Statt dessen wird ein Zeichenfeld verwendet, das mit einem Nullzeichen abgeschlossen wird. Die Speicherverwaltung von Zeichenketten muss vom Programmierer vorgenommen werden.
- Speicherverwaltung
- Der Programmierer muss den dynamischen Speicher selbst verwalten. Hierzu stehen Bibliotheksfunktionen zur Verfügung.
- niedriger Abstraktionsgrad
- Portabilitätsprobleme
- C schreibt die Speichergröße verschiedener Typen in der Sprachdefinition nicht vor. Dies ermöglicht die Portierung bestehender Programme auf andere, auch neue Prozessoren. Es ist nur zugesichert, dass ein short int nicht länger sein darf als ein long int. In den 1980er und 1990er Jahren wurden vorwiegend 32-Bit Systeme wie VAX, 68000, i386 eingesetzt. Bei diesen waren Zeiger, int und long alle 32 Bits lang, so dass sich dies als Quasistandard etabliert hat. Dies bereitet Probleme bei der Portierung auf modernere 64-Bit-Architekturen, falls der Programmierer von bestimmten Längen ausgegangen ist.
- Einige weitere Eigenschaften der Sprachdefinition (Ergebnistyp bei Zeigersubtraktion, Ausrichtung (Alignment) von Datentypen) bereiten ebenfalls Probleme, wenn statt der empfohlenen abstrakten Typen (wie ptrdiff_t für Zeigersubtraktionen, size_t für Größen von Speicherbereichen) direkt fundamentale Typen wie int verwendet werden.
- In der Sprachversion C99 sind Datentypen mit expliziten Bit-Längen definiert (int8_t, int16_t, etc.).
Zusammenfassung
Man kann sagen, dass die größte Stärke von C - die uneingeschränkte Freiheit des Programmierers im Umgang mit Zeigern und Speicherstrukturen - gleichzeitig ihre größte Schwäche ist.
Da der freizügige Umgang der Programmiersprache mit dem Speicher in kritischen Umgebungen (Kreditinstituten, Börsen, Versicherungen, Raumfahrt usw.) leicht hohe Schäden nach sich ziehen kann, wird hier mittlerweile ernsthaft erwogen, diese Programmiersprache bei neuen Projekten zu verbieten.
Literatur
- Brian W. Kernighan, Dennis Ritchie: Programmieren in C, Hanser Fachbuch, ISBN 3-446-15497-3
IRC
Einen IRC-Channel, wo man Hilfe bekommen kann, findet man zum Beispiel im IRCNet in #c (irc://random.ircd.de/C)