Zum Inhalt springen

C (Programmiersprache)

aus Wikipedia, der freien Enzyklopädie
Dies ist eine alte Version dieser Seite, zuletzt bearbeitet am 22. September 2006 um 06:38 Uhr durch 87.122.149.213 (Diskussion) ([[Hallo-Welt-Programm]] in C). Sie kann sich erheblich von der aktuellen Version unterscheiden.

Die Programmiersprache C wurde von Ken Thompson und Dennis Ritchie in den frühen 70er Jahren für das damals 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, wo die Sprache entwickelt wurde). Aus dieser Sprache entstand dann C. Die grundlegenden Programme aller Unix-Systeme und die Systemkerne vieler Betriebssysteme sind in C programmiert.

Zahlreiche weitere Sprachen, wie Java, C#, C++, Objective-C oder Perl orientieren sich an der Syntax und anderen Eigenschaften von C.

Überblick

C ist eine Programmiersprache, die auf fast allen Computersystemen zur Verfügung steht. Sie zählt zu den so genannten prozeduralen Programmiersprachen. Seit der Definition von ISO-C gibt es relativ einheitliche Implementierungen auf den verschiedenen Computer-Plattformen. In jedem C-System mit Laufzeitumgebung steht auch die genormte Standard C Library zur Verfügung. Konzeptionell ist C auf einfache Kompilierbarkeit ausgelegt. In C geschriebene Programme zeichnen sich im Vergleich zu manchen anderen Hochsprachen durch eine hohe Geschwindigkeit der resultierenden Programme aus.

Obwohl C nicht objektorientiert ist und mit C++, Objective-C, Java oder C# auf C basierende bzw. an C anlehnende, objektorientierte Weiterentwicklungen mit ungleich umfangreicheren Bibliotheken zur Verfügung stehen, ist die Verbreitung von C nach wie vor hoch. Viele ursprünglich in C geschriebene Programme existieren heute noch und müssen gewartet und weiterentwickelt werden. Programmierschnittstellen für Anwendungsprogramme werden häufig in Form von C-Schnittstellen dargeboten.

C eignet sich gut für die Systemprogrammierung. Bei der Anwendungsentwicklung wird C jedoch zunehmend durch andere Sprachen, wie C++, Objective-C, Java oder C# verdrängt, die C im Bezug auf Entwicklungsgeschwindigkeit, Wartbarkeit, Entwurfsunterstützung und Abstraktionsniveau erweitern und verbessern.

C lässt dem Programmierer viele Freiheiten, insbesondere die von freien Speicherzugriffen. Die einfache Struktur und der kleine Umfang der Sprache erweisen sich oft als großer Vorteil. Die Einfachheit der Sprachsyntax steht jedoch in keiner Relation zum C-Code selbst. Dieser kann aufgrund einer exzessiven Nutzung der Zeigerarithmetik in umfangreichen Projekten schnell sehr komplexe Dimensionen annehmen. 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. Tatsächlich wird C manchmal als „High-Level Assembler“ bezeichnet. Die Portierung eines C-Compilers auf eine neue Prozessorplattform ist, verglichen mit anderen Sprachen, aufgrund der genannten Analogie zur Assemblersprache 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 unterschiedlicher 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.

Geschichte

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 Hauptartikel: Varianten der Programmiersprache C

Sprachdesign

Ein C-Programm wird durch den so genannten 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, der Ein- und Ausgabe mit Streams, und das Verfahren des Startens und Beendens eines Programms.

Der folgende Quelltext stellt ein einfaches C-Programm dar, das die Meldung Hallo Welt! gefolgt von einem Zeilenumbruch 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. eine Deklaration von printf und wird durch den Präprozessor eingefügt.

In der zweiten Zeile beginnt das eigentliche Programm mit der Definition der Funktion main. Sie ist die Einstiegsfunktion eines C-Programmes. main wird automatisch als erste Funktion aufgerufen. Anfang und Ende der Funktion main werden durch die beiden geschweiften Klammern markiert.

Die erste Anweisung innerhalb der Funktion main ruft die Funktion printf auf. Die zweite Anweisung ist die Sprunganweisung return 0;; diese legt den Rückgabewert von main fest. Damit wird der „Erfolgsstatus“ des ausgeführten Programms zum Ausdruck gebracht. Der Wert 0 bedeutet fehlerfreie Ausführung. Alle anderen Werte als 0 bedeuten einen Fehlerstatus.

Da es in C noch keine Möglichkeiten der Behandlung von Exceptions wie in moderneren Hochsprachen (wie Java oder C++) gibt, ist es für die sichere Programmausführung wichtig, beim Aufruf von Bibliotheksfunktionen immer den Rückgabewert auf Fehlersituationen zu prüfen und angemessen zu reagieren. Auch selbst geschriebene Funktionen sollten nach diesem Konzept entworfen werden und im Fehlerfall einen entsprechenden Rückgabewert liefern. (In obigem Beispiel hätte es dann return printf("Hallo Welt!\n"); heißen müssen, damit das Programm einen Fehlerwert zurückgibt, falls der Aufruf von printf einen Fehlerwert ergibt.)

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 jedem Programmierer zur Verfügung stehen, sofern das installierte C-Entwicklungssystem den Anspruch hat, ISO-kompatibel zu sein. Verwendet ein Programm lediglich den Sprachkern gemäß der Sprachdefinition und die Standardbibliothek, so ist es auf jedem System, das einen ISO-kompatiblen Compiler zur Verfügung stellt, übersetzbar. Es zeigt aber nur dann auf allen Systemen das gleiche Verhalten, wenn der Programmierer das Programm portabel geschrieben hat, denn nicht jeder Ausdruck der Sprache hat eine wohldefinierte und überall einheitliche Bedeutung.
  • Die Bibliotheksfunktionen, die von dem (Betriebs-)System zur Verfügung gestellt werden. Diese Bibliotheken stammen also nicht von einem ISO-kompatiblen C-Entwicklungssystem. 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/Definitionen
  • 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.


Bewertung von Sprachmerkmalen

Stärken

  • Der minimalistische Sprachumfang enthält keinerlei vordefinierte Funktionen: Der kleinste bekannte C-Compiler („K&R“-Dialekt) besteht aus 3742 Bytes C-Code und kann sich selbst kompilieren.
  • 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 der Ü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.
  • C ist quasi auf jedem Computersystem einschließlich eingebetteten Systemen verfügbar.
  • Fast alle Kernel der bekannten Betriebssysteme sind in C implementiert.
  • Sehr portabel und sowohl assemblernahe als auch abstrakte Programmierung möglich.
  • Wenig Lernaufwand für Programmierer, bei Verwendung unterschiedlicher Systeme.
  • Nach wie vor ist C eine häufig benutzte Programmiersprache nicht nur für Referenzimplementierungen.

Schwächen

  • Eingeschränkte Typsicherheit (durch ISO-Standard etwas abgemildert).
  • Lückenhafte Sprachdefinition (Verhalten z. T. undefiniert).
  • Der Sprachumfang ist sehr minimalistisch, es fehlen z. B. Befehle für Standard-Ein-/Ausgabe komplett. Diese werden erst durch die Standard-Library nachgeliefert.
  • Verglichen mit anderen prozeduralen Sprachen wie dem älteren Pascal oder Modula-2 unübersichtliche Syntax.
  • Kein Bereichsüberprüfung und keine Überprüfung des Überlaufs bei Integer-Datentypen.
  • Niedriger Abstraktionsgrad
    • Fehlendes Modulkonzept. Stattdessen existiert nur ein Sprachmittel (#include) zur textuellen Ersetzung von Teilen von Quelltext durch den Inhalt von anderen Quelltextdateien. (Module werden durch Paare von .c- und .h-Dateien mehr oder weniger nachgebaut.)
    • Nur zweistufiges Namensraumkonzept (Quelltextdatei oder global).
    • Es gibt keine Referenzvariablen als Funktionsargumente. Diese Funktionalität muss durch Zeigeroperationen emuliert werden.
  • „Wilde“ Zeiger
    • Zeiger können auf beliebige Stellen im Speicher gerichtet werden. 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über hinaus auch noch elegant ist.
  • Zeichenketten
    • Zeichenketten (Strings) werden in C als so genannte nullterminierte Strings in Variablen und Konstanten ("Hallo") gespeichert und durch ihre Adressen angesprochen. Die große Schwäche dieses Konzepts liegt in der großen Programmiererverantwortung, denen unerfahrene Programmierer nicht immer gewachsen sind (siehe wilde Zeiger, zu kleine Felder, vergessene Abschlussnull, …). Gewöhnlich werden C Strings entsprechend dem ASCII-Code codiert. Mit dem Unicode Transformation Format UTF-8 existiert aber auch eine Unicode-Implementierung, die in ANSI-C verwendet werden kann. Sinngemäß kann UTF-8 also als eine Art Escape Sequenz betrachtet werden, wie man Sie auch bei Terminalemulatoren oder Druckern antreffen kann. Allerdings existiert keine standardisierte Funktion zur Berechnung der Anzahl der Glyphen einer UTF-8 Bytefolge.
  • Speicherverwaltung
  • Portabilitätsprobleme
    • C schreibt die Speichergröße verschiedener Typen in der Sprachdefinition nicht vor. Dies erschwert 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, Motorola 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.).
    • Nicht garantierte Initialisierung: Variablen werden nicht in jedem Fall automatisch initialisiert. Als Folge können Programme, sofern die Initialisierung vergessen wurde, nicht vorhersagbares Verhalten aufweisen.
    • Compiler darf zwecks Alignment beliebig Padding-Bytes in Datenstrukturen einfügen – Austausch von Datenstrukturen mit anderen Dateiformaten/Plattformen/Entwicklungssystemen unportabel, nur durch Verwendung proprietärer Compilererweiterungen möglich.
  • Mangelnde Eignung für größere Projekte
    • Fehlendes Modulkonzept bewirkt lange Kompilier- und Linkzeiten und zahlreiche Fehlerquellen.
    • Flaches Namensraumkonzept (zweistufig) bewirkt schlechte Wartbarkeit und häufige Kollisionen.
    • Fehlende moderne Sprachkonzepte müssen durch Konventionen und äußerste Disziplin der Entwickler kompensiert werden, was für die Entwickler erhebliche Mehrarbeit und deutlich geringere Produktivität als in moderneren Sprachen bedeutet.

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: Was für die Programmierung von Treibern und Betriebssystemen sinnvoll ist, das ist für die Programmierung gewöhnlicher Anwendungen eher ein lästiges Hindernis, wenn nicht sogar ein Sicherheitsrisiko.

Der freizügige Umgang der Programmiersprache mit dem Speicher sowie zahlreiche fehleranfällige Sprachkonstruktionen können in kritischen Umgebungen (Kreditinstituten, Börsen, Versicherungen, Raumfahrt usw.) leicht hohe Schäden nach sich ziehen.

Literatur

Wikibooks: C Sprachbeschreibung – Lern- und Lehrmaterialien
Wikibooks: C-Programmierung – Lern- und Lehrmaterialien