C (Programmiersprache) - C (programming language)

C
Text in hellblauen Großbuchstaben mit Serifen auf weißem Hintergrund und sehr großen hellblauen Sans-Serif-Buchstaben C.
Die Programmiersprache C (oft als K&R bezeichnet ), das bahnbrechende Buch über C
Paradigma Multiparadigma : Imperativ ( prozedural ), strukturiert
Entworfen von Dennis Ritchie
Entwickler Dennis Ritchie & Bell Labs (Schöpfer); ANSI X3J11 ( ANSI C ); ISO/IEC JTC1/SC22/WG14 (ISO C)
Erstmals erschienen 1972 ; Vor 49 Jahren ( 1972 )
Stabile Version
C17 / Juni 2018 ; Vor 3 Jahren ( 2018-06 )
Vorschauversion
C2x ( N2596 ) / 11. Dezember 2020 ; vor 10 Monaten ( 2020-12-11 )
Schreibdisziplin Statisch , schwach , manifest , nominell
Betriebssystem Plattformübergreifend
Dateinamenerweiterungen .CH
Webseite www .iso .org /standard /74528 .html
www .open-std .org /jtc1 /sc22 /wg14 /
Wichtige Implementierungen
K&R C , GCC , Clang , Intel C , C++Builder , Microsoft Visual C++ , Watcom C
Dialekte
Cyclone , Unified Parallel C , Split-C , Cilk , C*
Beeinflusst von
B ( BCPL , CPL ), ALGOL 68 , Montage , PL/I , FORTRAN
Beeinflusst
Zahlreiche : AMPL , AWK , csh , C++ , C-- , C# , Objective-C , D , Go , Java , JavaScript , Julia , Limbo , LPC , Perl , PHP , Pike , Processing , Python , Rust , Seed7 , Vala , Verilog (HDL), Nim , Zig

C ( / s Ï / , wie sie in dem Buchstaben c ) sind ein für allgemeine Zwecke , prozeduralen Computerprogrammiersprache stütz strukturierte Programmierung , lexikalischen variable Umfang und Rekursion , mit einem statischen Typ System . C bietet von Natur aus Konstrukte, die sich effizient auf typische Maschinenanweisungen abbilden lassen . Es hat dauerhafte Verwendung in Anwendungen gefunden, die zuvor in Assembler codiert wurden . Zu diesen Anwendungen gehören Betriebssysteme und verschiedene Anwendungssoftware für Computerarchitekturen, die von Supercomputern bis hin zu SPS und eingebetteten Systemen reichen .

Als Nachfolger der Programmiersprache B wurde C ursprünglich zwischen 1972 und 1973 in den Bell Labs von Dennis Ritchie entwickelt , um Dienstprogramme zu entwickeln, die unter Unix laufen . Es wurde auf die Neuimplementierung des Kernels des Unix-Betriebssystems angewendet. In den 1980er Jahren gewann C allmählich an Popularität. Es hat sich zu einer der am weitesten verbreiteten Programmiersprachen entwickelt , wobei C- Compiler von verschiedenen Anbietern für die meisten existierenden Computerarchitekturen und Betriebssysteme verfügbar sind. C ist seit 1989 von ANSI ( ANSI C ) und von der International Organization for Standardization (ISO) standardisiert .

C ist eine zwingende Verfahrenssprache . Es wurde entwickelt, um kompiliert zu werden, um Low-Level- Zugriff auf Speicher und Sprachkonstrukte zu ermöglichen, die effizient auf Maschinenanweisungen abbilden , und das alles mit minimaler Laufzeitunterstützung . Trotz ihrer geringen Fähigkeiten wurde die Sprache entwickelt, um die plattformübergreifende Programmierung zu fördern. Ein standardkonformes C-Programm, das im Hinblick auf Portabilität geschrieben wurde, kann mit wenigen Änderungen am Quellcode für eine Vielzahl von Computerplattformen und Betriebssystemen kompiliert werden.

Seit dem Jahr 2000 gehört C im TIOBE-Index , einem Maß für die Popularität von Programmiersprachen , durchweg zu den zwei besten Sprachen.

Überblick

Dennis Ritchie (rechts), der Erfinder der Programmiersprache C, mit Ken Thompson

Wie die meisten prozeduralen Sprachen in der ALGOL- Tradition verfügt C über Möglichkeiten zur strukturierten Programmierung und ermöglicht lexikalischen Variablenbereich und Rekursion. Sein statisches Typensystem verhindert unbeabsichtigte Operationen. In C ist der gesamte ausführbare Code in Unterprogrammen enthalten (auch "Funktionen" genannt, wenn auch nicht streng im Sinne der funktionalen Programmierung ). Funktionsparameter werden immer als Wert übergeben (außer Arrays ). Die Referenzübergabe wird in C simuliert, indem explizit Zeigerwerte übergeben werden. Der Quelltext von C-Programmen ist frei formatiert und verwendet das Semikolon als Anweisungsabschlusszeichen und geschweifte Klammern zum Gruppieren von Anweisungsblöcken .

Die Sprache C weist außerdem die folgenden Eigenschaften auf:

  • Die Sprache hat eine kleine, feste Anzahl von Schlüsselwörtern, einschließlich eines vollständigen Satzes von Kontrollfluss- Primitiven: if/else, for, do/while, while, und switch. Benutzerdefinierte Namen werden durch kein Siegel von Schlüsselwörtern unterschieden .
  • Es hat eine große Anzahl von arithmetischen, bitweisen und logischen Operatoren: +, +=, ++, &, ||, usw.
  • In einer einzigen Anweisung können mehrere Zuweisungen ausgeführt werden.
  • Funktionen:
    • Funktionsrückgabewerte können ignoriert werden, wenn sie nicht benötigt werden.
    • Funktions- und Datenzeiger ermöglichen einen Ad-hoc -Laufzeitpolymorphismus .
    • Funktionen dürfen nicht innerhalb des lexikalischen Geltungsbereichs anderer Funktionen definiert werden.
  • Die Datentypisierung ist statisch , aber nur schwach erzwungen ; alle Daten haben einen Typ, aber implizite Konvertierungen sind möglich.
  • Erklärung Syntax ahmt Nutzungskontext. C hat kein "define"-Schlüsselwort; stattdessen wird eine Anweisung, die mit dem Namen eines Typs beginnt, als Deklaration verwendet. Es gibt kein Schlüsselwort "Funktion". stattdessen wird eine Funktion durch das Vorhandensein einer Argumentliste in Klammern angezeigt.
  • Benutzerdefinierte ( typedef ) und zusammengesetzte Typen sind möglich.
    • Heterogene aggregierte Datentypen ( struct) ermöglichen den Zugriff auf zusammengehörige Datenelemente und deren Zuordnung als Einheit.
    • Union ist eine Struktur mit überlappenden Mitgliedern; nur das zuletzt gespeicherte Mitglied ist gültig.
    • Die Array- Indizierung ist eine sekundäre Notation, die durch Zeigerarithmetik definiert wird. Im Gegensatz zu Strukturen sind Arrays keine erstklassigen Objekte: Sie können nicht mit einzelnen integrierten Operatoren zugewiesen oder verglichen werden. Es wird kein "array"-Schlüsselwort verwendet oder definiert; stattdessen zeigen eckige Klammern syntaktisch Arrays an, zum Beispiel month[11].
    • Aufzählungstypen sind mit dem enumSchlüsselwort möglich. Sie sind mit ganzen Zahlen frei ineinander konvertierbar.
    • Strings sind kein eigenständiger Datentyp, sondern werden konventionell als nullterminierte Zeichenarrays implementiert .
  • Ein Low-Level-Zugriff auf den Computerspeicher ist möglich, indem Maschinenadressen in typisierte Zeiger umgewandelt werden .
  • Prozeduren (Unterroutinen, die keine Werte zurückgeben) sind ein Sonderfall von Funktionen mit einem nicht typisierten Rückgabetyp void.
  • Ein Präprozessor führt die Makrodefinition , das Einschließen von Quellcodedateien und die bedingte Kompilierung durch .
  • Es gibt eine grundlegende Form der Modularität : Dateien können separat kompiliert und miteinander verknüpft werden, wobei gesteuert wird, welche Funktionen und Datenobjekte über staticund externAttribute für andere Dateien sichtbar sind .
  • Komplexe Funktionen wie I/O , String- Manipulation und mathematische Funktionen werden konsequent an Bibliotheksroutinen delegiert .

Während C bestimmte Funktionen anderer Sprachen (wie Objektorientierung und Garbage Collection ) nicht enthält, können diese implementiert oder emuliert werden, oft durch die Verwendung externer Bibliotheken (zB GLib Object System oder Boehm Garbage Collector ).

Beziehungen zu anderen Sprachen

Viele spätere Sprachen haben sich direkt oder indirekt von C entlehnt, darunter C++ , C# , die C-Shell von Unix , D , Go , Java , JavaScript (einschließlich Transpiler ), Julia , Limbo , LPC , Objective-C , Perl , PHP , Python , Ruby , Rust , Swift , Verilog und SystemVerilog (Hardwarebeschreibungssprachen). Diese Sprachen haben viele ihrer Kontrollstrukturen und andere grundlegende Funktionen von C übernommen. Die meisten von ihnen (Python ist eine dramatische Ausnahme) drücken auch eine sehr ähnliche Syntax wie C aus, und sie neigen dazu, die erkennbare Ausdrucks- und Anweisungssyntax von C mit dem zugrunde liegenden Typ zu kombinieren Systeme, Datenmodelle und Semantik, die radikal unterschiedlich sein können.

Geschichte

Frühe Entwicklungen

Zeitleiste der Sprachentwicklung
Jahr C-Standard
1972 Geburt
1978 K&R C
1989/1990 ANSI C und ISO C
1999 C99
2011 C11
2017 C17
noch offen C2x

Der Ursprung von C ist eng mit der Entwicklung des Unix- Betriebssystems verbunden, das ursprünglich von Dennis Ritchie und Ken Thompson in Assemblersprache auf einer PDP-7 implementiert wurde und mehrere Ideen von Kollegen beinhaltete. Schließlich beschlossen sie, das Betriebssystem auf eine PDP-11 zu portieren . Die ursprüngliche PDP-11-Version von Unix wurde ebenfalls in Assembler entwickelt.

Thompson wünschte sich eine Programmiersprache, um Dienstprogramme für die neue Plattform zu erstellen. Zunächst versuchte er, einen Fortran- Compiler zu erstellen , gab die Idee jedoch bald wieder auf. Stattdessen erstellte er eine abgespeckte Version der kürzlich entwickelten BCPL- Systemprogrammiersprache . Die offizielle Beschreibung von BCPL war zu diesem Zeitpunkt noch nicht verfügbar, und Thompson änderte die Syntax, um weniger wortreich zu sein, wodurch ein ähnliches, aber etwas einfacheres B entstand . Letztendlich wurden jedoch nur wenige Dienstprogramme in B geschrieben, weil es zu langsam war und B die PDP-11-Funktionen wie die Byte- Adressierbarkeit nicht nutzen konnte.

1972 begann Ritchie, B zu verbessern, insbesondere durch Hinzufügen von Datentypisierungen für Variablen, was zur Entwicklung einer neuen Sprache C führte. Der C-Compiler und einige damit erstellte Dienstprogramme waren in Version 2 Unix enthalten .

In Version 4 Unix , die im November 1973 veröffentlicht wurde, wurde der Unix- Kernel umfassend in C neu implementiert. Zu diesem Zeitpunkt hatte die C-Sprache einige mächtige Funktionen wie structTypen erworben.

Der Präprozessor wurde um 1973 auf Drängen von Alan Snyder und auch in Anerkennung der Nützlichkeit der in BCPL und PL/I verfügbaren Mechanismen zum Einschließen von Dateien eingeführt. Seine ursprüngliche Version bot nur enthaltene Dateien und einfache String-Ersetzungen: #includeund #definevon parameterlosen Makros. Bald darauf wurde es erweitert, hauptsächlich von Mike Lesk und dann von John Reiser, um Makros mit Argumenten und bedingter Kompilierung zu integrieren.

Unix war einer der ersten Betriebssystem-Kernel, der in einer anderen Sprache als Assembler implementiert wurde . Frühere Beispiele sind das Multics- System (das in PL/I geschrieben wurde ) und das Master Control Program (MCP) für den Burroughs B5000 (das in ALGOL geschrieben wurde ) im Jahr 1961. Um 1977 nahmen Ritchie und Stephen C. Johnson weitere Änderungen an die Sprache, um die Portabilität des Unix-Betriebssystems zu erleichtern. Johnsons Portable C Compiler diente als Basis für mehrere Implementierungen von C auf neuen Plattformen.

K&R C

Das Cover des Buches The C Programming Language , Erstausgabe, von Brian Kernighan und Dennis Ritchie

1978 veröffentlichten Brian Kernighan und Dennis Ritchie die erste Ausgabe von The C Programming Language . Dieses Buch, den C-Programmierern als K&R bekannt , diente viele Jahre lang als informelle Spezifikation der Sprache. Die darin beschriebene Version von C wird allgemein als „ K&R C “ bezeichnet. Da diese 1978 veröffentlicht wurde, wird sie auch als C78 bezeichnet . Die zweite Auflage des Buches behandelt den späteren ANSI C- Standard, der unten beschrieben wird.

K&R hat mehrere Sprachfeatures eingeführt:

  • Standard-I/O-Bibliothek
  • long int Datentyp
  • unsigned int Datentyp
  • Zusammengesetzte Zuweisungsoperatoren der Form (wie ) wurden in die Form ( dh ) geändert, um die semantische Mehrdeutigkeit zu beseitigen, die durch Konstrukte wie erzeugt wurde , die als (dekrementieren um 10) anstelle des möglicherweise beabsichtigten (seiens − 10).=op=-op=-=i=-10i =- 10ii = -10i

Auch nach der Veröffentlichung des ANSI-Standards von 1989 galt K&R C noch viele Jahre als der " kleinste gemeinsame Nenner ", auf den sich C-Programmierer beschränkten, wenn maximale Portabilität gewünscht war, da noch viele ältere Compiler im Einsatz waren und weil sorgfältig geschriebene K&R C-Code kann auch legaler Standard C sein.

In frühen Versionen von C müssen nur Funktionen, die andere Typen als intzurückgeben, deklariert werden, wenn sie vor der Funktionsdefinition verwendet werden; Bei Funktionen, die ohne vorherige Deklaration verwendet wurden, wurde angenommen, dass sie den Typ zurückgeben int.

Zum Beispiel:

long some_function();
/* int */ other_function();

/* int */ calling_function()
{
    long test1;
    register /* int */ test2;

    test1 = some_function();
    if (test1 > 0)
          test2 = 0;
    else
          test2 = other_function();
    return test2;
}

Die intauskommentierten Typenbezeichnungen könnten in K&R C entfallen, werden aber in späteren Standards benötigt.

Da K&R-Funktionsdeklarationen keine Informationen über Funktionsargumente enthielten , wurden keine Überprüfungen des Funktionsparametertyps durchgeführt, obwohl einige Compiler eine Warnmeldung ausgeben würden, wenn eine lokale Funktion mit der falschen Anzahl von Argumenten aufgerufen wurde oder wenn eine externe Funktion mehrfach aufgerufen wurde verwendet unterschiedliche Zahlen oder Arten von Argumenten. Separate Tools wie das Dienstprogramm lint von Unix wurden entwickelt, das (unter anderem) die Konsistenz der Funktionsverwendung über mehrere Quelldateien hinweg überprüfen kann.

In den Jahren nach der Veröffentlichung von K&R C wurden der Sprache mehrere Funktionen hinzugefügt, die von Compilern von AT&T (insbesondere PCC ) und einigen anderen Anbietern unterstützt werden. Diese enthielten:

Die große Anzahl von Erweiterungen und die fehlende Übereinstimmung über eine Standardbibliothek , zusammen mit der Sprachpopularität und der Tatsache, dass nicht einmal die Unix-Compiler die K&R-Spezifikation präzise umgesetzt haben, führten zur Notwendigkeit einer Standardisierung.

ANSI C und ISO C

In den späten 1970er und 1980er Jahren wurden Versionen von C für eine Vielzahl von Großrechnern , Minicomputern und Mikrocomputern , einschließlich des IBM PC , implementiert , da seine Popularität erheblich zunahm.

1983 bildete das American National Standards Institute (ANSI) ein Komitee, X3J11, um eine Standardspezifikation von C zu erstellen. X3J11 basierte den C-Standard auf der Unix-Implementierung; der nicht tragbare Teil der Unix-C-Bibliothek wurde jedoch an die IEEE- Arbeitsgruppe 1003 übergeben, um die Grundlage für den POSIX- Standard von 1988 zu werden. 1989 wurde der C-Standard als ANSI X3.159-1989 "Programming Language C" ratifiziert. Diese Version der Sprache wird oft als ANSI C , Standard C oder manchmal C89 bezeichnet.

1990 wurde der ANSI-C-Standard (mit Formatierungsänderungen) von der International Organization for Standardization (ISO) als ISO/IEC 9899:1990 übernommen, der manchmal als C90 bezeichnet wird. Daher beziehen sich die Begriffe "C89" und "C90" auf dieselbe Programmiersprache.

ANSI entwickelt, wie andere nationale Normungsgremien auch, den C-Standard nicht mehr eigenständig, sondern orientiert sich an dem internationalen C-Standard, der von der Arbeitsgruppe ISO/IEC JTC1/SC22 /WG14 gepflegt wird. Die nationale Übernahme einer Aktualisierung des internationalen Standards erfolgt in der Regel innerhalb eines Jahres nach der ISO-Veröffentlichung.

Eines der Ziele des C-Standardisierungsprozesses war es, eine Obermenge von K&R C zu erstellen , die viele der später eingeführten inoffiziellen Funktionen enthält. Der Normenausschuss hat auch mehrere zusätzliche Features wie Funktionsprototypen (von C++ entlehnt), voidZeiger, Unterstützung für internationale Zeichensätze und Gebietsschemata sowie Präprozessor-Erweiterungen aufgenommen. Obwohl die Syntax für Parameterdeklarationen um den in C++ verwendeten Stil erweitert wurde, wurde die K&R-Schnittstelle aus Kompatibilitätsgründen mit bestehendem Quellcode weiterhin zugelassen.

C89 wird von aktuellen C-Compilern unterstützt und der meiste moderne C-Code basiert darauf. Jedes Programm, das nur in Standard C und ohne hardwareabhängige Annahmen geschrieben wurde, wird auf jeder Plattform mit einer konformen C-Implementierung innerhalb ihrer Ressourcengrenzen korrekt ausgeführt . Ohne eine solche Vorsichtsmaßnahmen kann Programme kompilieren nur auf einer bestimmten Plattform oder mit einem bestimmten Compiler, durch zum Beispiel die Verwendung von Nicht-Standard - Bibliotheken, wie GUI - Bibliotheken, oder zu einer Abhängigkeit von Compiler- oder plattformspezifische Attribute wie beispiels wie die genaue Größe der Datentypen und Byte- Endianness .

In Fällen, in denen Code entweder von standardkonformen oder K&R C-basierten Compilern kompilierbar sein muss, kann das __STDC__Makro verwendet werden, um den Code in Standard- und K&R-Abschnitte aufzuteilen, um die Verwendung von Funktionen, die nur in Standard verfügbar sind, auf einem K&R C-basierten Compiler zu verhindern C.

Nach dem ANSI/ISO-Standardisierungsprozess blieb die C-Sprachspezifikation für mehrere Jahre relativ statisch. 1995 wurde die Normative Änderung 1 des C-Standards von 1990 (ISO/IEC 9899/AMD1:1995, informell als C95 bekannt) veröffentlicht, um einige Details zu korrigieren und eine umfassendere Unterstützung für internationale Zeichensätze hinzuzufügen.

C99

1999 ISO C.pdf

Der C-Standard wurde Ende der 1990er Jahre weiter überarbeitet, was zur Veröffentlichung von ISO/IEC 9899:1999 im Jahr 1999 führte, die allgemein als „ C99 “ bezeichnet wird. Es wurde seitdem dreimal durch Technische Berichtigungen geändert.

C99 führte mehrere neue Funktionen ein, darunter Inline-Funktionen , mehrere neue Datentypen (einschließlich long long intund ein complexTyp zur Darstellung komplexer Zahlen ), Arrays mit variabler Länge und flexible Array-Mitglieder , verbesserte Unterstützung für IEEE 754- Gleitkomma, Unterstützung für variadische Makros (Makros von Variablen). arity ) und Unterstützung für einzeilige Kommentare, die mit beginnen //, wie in BCPL oder C++. Viele davon waren bereits als Erweiterungen in mehreren C-Compilern implementiert.

C99 ist größtenteils abwärtskompatibel mit C90, ist aber in gewisser Weise strenger; insbesondere wird eine Deklaration ohne Typbezeichner nicht mehr intimplizit angenommen. Ein Standardmakro __STDC_VERSION__ist mit Wert definiert, 199901Lum anzuzeigen, dass C99-Unterstützung verfügbar ist. GCC , Solaris Studio und andere C-Compiler unterstützen jetzt viele oder alle der neuen Funktionen von C99. Der C-Compiler in Microsoft Visual C++ implementiert jedoch den C89-Standard und die Teile von C99, die für die Kompatibilität mit C++11 erforderlich sind .

Außerdem wird nun die Unterstützung von Unicode- Bezeichnern (Variablen-/Funktionsnamen) in Form von Escape-Zeichen (zB \U0001f431) benötigt. Die Unterstützung für unformatierte Unicode-Namen ist optional.

C11

Im Jahr 2007 begann die Arbeit an einer weiteren Überarbeitung des C-Standards, informell "C1X" genannt, bis zu seiner offiziellen Veröffentlichung am 08.12.2011. Der C-Normenausschuss hat Richtlinien verabschiedet, um die Einführung neuer Funktionen zu begrenzen, die nicht von bestehenden Implementierungen getestet wurden.

Der C11-Standard fügt C und der Bibliothek zahlreiche neue Funktionen hinzu, einschließlich typgenerischer Makros, anonymer Strukturen, verbesserter Unicode-Unterstützung, atomarer Operationen, Multithreading und Funktionen mit Grenzüberprüfung. Es macht auch einige Teile der bestehenden C99-Bibliothek optional und verbessert die Kompatibilität mit C++. Das Standardmakro __STDC_VERSION__ist so definiert 201112L, dass es anzeigt, dass C11-Unterstützung verfügbar ist.

C17

C17 wurde im Juni 2018 veröffentlicht und ist der aktuelle Standard für die Programmiersprache C. Es führt keine neuen Sprachfeatures, nur technische Korrekturen und Klarstellungen zu Fehlern in C11 ein. Das Standardmakro __STDC_VERSION__ist definiert als 201710L.

C2x

C2x ist ein informeller Name für die nächste (nach C17) Hauptrevision der C-Sprachnorm. Es wird voraussichtlich im Jahr 2023 abgestimmt und würde daher C23 heißen.

Eingebettetes C

Historisch gesehen erfordert die eingebettete C-Programmierung nicht standardmäßige Erweiterungen der Sprache C, um exotische Funktionen wie Festkomma-Arithmetik, mehrere unterschiedliche Speicherbänke und grundlegende E/A-Operationen zu unterstützen.

Im Jahr 2008 veröffentlichte das C Standards Committee einen technischen Bericht , der die Sprache C erweitert, um diese Probleme zu lösen, indem ein gemeinsamer Standard für alle Implementierungen bereitgestellt wird. Es enthält eine Reihe von Funktionen, die in normalem C nicht verfügbar sind, wie Festkommaarithmetik , benannte Adressräume und grundlegende E/A-Hardwareadressierung.

Syntax

C hat eine formale Grammatik, die durch den C-Standard spezifiziert ist. Zeilenenden sind in C im Allgemeinen nicht signifikant; Liniengrenzen haben jedoch während der Vorverarbeitungsphase Bedeutung. Kommentare können entweder zwischen den Trennzeichen /*und stehen */, oder (seit C99) //bis zum Zeilenende folgen . Kommentare, die durch /*und */nicht verschachtelt werden, und diese Zeichenfolgen werden nicht als Kommentartrennzeichen interpretiert, wenn sie in String- oder Zeichenliteralen vorkommen.

C-Quelldateien enthalten Deklarationen und Funktionsdefinitionen. Funktionsdefinitionen wiederum enthalten Deklarationen und Anweisungen . Deklarationen definieren entweder neue Typen mit Schlüsselwörtern wie struct, union, und enum, oder weisen neuen Variablen Typen zu und reservieren möglicherweise Speicherplatz für neue Variablen, normalerweise durch Schreiben des Typs gefolgt vom Variablennamen. Schlüsselwörter wie charund intgeben integrierte Typen an. Codeabschnitte werden in geschweifte Klammern ( {und }, manchmal auch als "geschwungene Klammern" bezeichnet) eingeschlossen, um den Geltungsbereich von Deklarationen einzuschränken und als einzelne Anweisung für Kontrollstrukturen zu fungieren.

Als zwingende Sprache verwendet C Anweisungen , um Aktionen zu spezifizieren. Die gebräuchlichste Anweisung ist eine expression-Anweisung , bestehend aus einem auszuwertenden Ausdruck, gefolgt von einem Semikolon; als Nebeneffekt der Auswertung können Funktionen aufgerufen und Variablen neue Werte zugewiesen werden. Um die normale sequentielle Ausführung von Anweisungen zu ändern, stellt C mehrere Kontrollflussanweisungen bereit, die durch reservierte Schlüsselwörter identifiziert werden. Strukturierte Programmierung wird durch if(- else) bedingte Ausführung und durch do- while, while, und foriterative Ausführung (Schleifen) unterstützt. Die forAnweisung hat separate Initialisierungs-, Test- und Reinitialisierungsausdrücke, von denen einige oder alle weggelassen werden können. breakund continuekann verwendet werden, um die innerste einschließende Schleifenanweisung zu verlassen oder zu ihrer Neuinitialisierung zu springen. Es gibt auch eine nicht strukturierte gotoAnweisung, die direkt zum angegebenen Label innerhalb der Funktion verzweigt . switchwählt a aus case, das basierend auf dem Wert eines ganzzahligen Ausdrucks ausgeführt werden soll.

Ausdrücke können eine Vielzahl von integrierten Operatoren verwenden und können Funktionsaufrufe enthalten. Die Reihenfolge, in der Argumente für Funktionen und Operanden für die meisten Operatoren ausgewertet werden, ist nicht festgelegt. Die Auswertungen können sogar verschachtelt sein. Alle Nebenwirkungen (einschließlich der Speicherung in Variablen) treten jedoch vor dem nächsten " Sequenzpunkt " auf; Sequenzpunkte umfassen das Ende jeder Ausdrucksanweisung sowie den Einstieg und die Rückkehr von jedem Funktionsaufruf. Sequenzpunkte treten auch bei der Auswertung von Ausdrücken auf, die bestimmte Operatoren ( &&, ||, ?:und den Kommaoperator ) enthalten. Dies ermöglicht ein hohes Maß an Objektcode-Optimierung durch den Compiler, erfordert jedoch, dass C-Programmierer mehr Sorgfalt aufwenden, um zuverlässige Ergebnisse zu erzielen, als dies für andere Programmiersprachen erforderlich ist.

Kernighan und Ritchie sagen in der Einführung in die Programmiersprache C : "C hat, wie jede andere Sprache auch, seine Fehler. Einige der Operatoren haben die falsche Priorität; einige Teile der Syntax könnten besser sein." Der C-Standard hat nicht versucht, viele dieser Fehler zu korrigieren, da sich solche Änderungen auf bereits vorhandene Software auswirken.

Zeichensatz

Der grundlegende C-Quellzeichensatz enthält die folgenden Zeichen:

Newline bezeichnet das Ende einer Textzeile; es muss keinem tatsächlichen einzelnen Zeichen entsprechen, obwohl C es der Einfachheit halber als eines behandelt.

In Zeichenfolgenliteralen können zusätzliche Multibyte-codierte Zeichen verwendet werden, die jedoch nicht vollständig portierbar sind . Der neueste C-Standard ( C11 ) ermöglicht die tragbare Einbettung multinationaler Unicode- Zeichen in C-Quelltext durch Verwendung \uXXXXoder \UXXXXXXXXKodierung (wobei das XZeichen ein hexadezimales Zeichen bezeichnet), obwohl diese Funktion noch nicht weit verbreitet ist.

Der grundlegende C-Ausführungszeichensatz enthält dieselben Zeichen, zusammen mit Darstellungen für alert , backspace und carriage return . Die Laufzeitunterstützung für erweiterte Zeichensätze wurde mit jeder Überarbeitung des C-Standards erhöht.

Reservierte Wörter

C89 hat 32 reservierte Wörter, die auch als Schlüsselwörter bezeichnet werden. Das sind Wörter, die nicht für andere Zwecke als die, für die sie vordefiniert sind, verwendet werden können:

C99 reservierte fünf weitere Wörter:

C11 reservierte sieben weitere Wörter:

  • _Alignas
  • _Alignof
  • _Atomic
  • _Generic
  • _Noreturn
  • _Static_assert
  • _Thread_local

Die meisten der kürzlich reservierten Wörter beginnen mit einem Unterstrich gefolgt von einem Großbuchstaben, da Bezeichner dieser Form zuvor vom C-Standard nur für die Verwendung durch Implementierungen reserviert waren. Da vorhandener Programmquellcode diese Bezeichner nicht hätte verwenden dürfen, wäre er nicht betroffen, wenn C-Implementierungen beginnen, diese Erweiterungen der Programmiersprache zu unterstützen. Einige Standardheader definieren bequemere Synonyme für unterstrichene Bezeichner. Die Sprache enthielt zuvor ein reserviertes Wort namens entry, das jedoch selten implementiert wurde und jetzt als reserviertes Wort entfernt wurde.

Betreiber

C unterstützt eine Vielzahl von Operatoren , bei denen es sich um Symbole handelt, die innerhalb eines Ausdrucks verwendet werden , um die bei der Auswertung dieses Ausdrucks durchzuführenden Manipulationen anzugeben. C hat Operatoren für:

C verwendet den Operator =(der in der Mathematik verwendet wird, um Gleichheit auszudrücken), um eine Zuweisung anzuzeigen, die dem Präzedenzfall von Fortran und PL/I folgt , jedoch im Gegensatz zu ALGOL und seinen Derivaten. C verwendet den Operator ==, um auf Gleichheit zu testen. Die Ähnlichkeit zwischen diesen beiden Operatoren (Zuweisung und Gleichheit) kann dazu führen, dass versehentlich einer anstelle des anderen verwendet wird, und in vielen Fällen führt der Fehler nicht zu einer Fehlermeldung (obwohl einige Compiler Warnungen ausgeben). Beispielsweise if (a == b + 1)könnte der bedingte Ausdruck fälschlicherweise als geschrieben werden if (a = b + 1), was als wahr ausgewertet wird, wenn anach der Zuweisung nicht Null ist.

Die Rangfolge des C- Operators ist nicht immer intuitiv. Der Operator ==bindet beispielsweise enger als die Operatoren &(bitweise UND) und |(bitweise ODER) in Ausdrücken wie (wird vor ausgeführt) x & 1 == 0, die so geschrieben werden müssen, als (x & 1) == 0ob dies die Absicht des Programmierers wäre.

Beispiel "Hallo Welt"

"Hallo Welt!" Programm von Brian Kernighan (1978)

Das in der Erstausgabe von K&R erschienene Beispiel „ hallo, world “ ist in den meisten Programmierlehrbüchern zum Vorbild für ein Einführungsprogramm geworden. Das Programm gibt "hallo, world" auf der Standardausgabe aus , die normalerweise eine Terminal- oder Bildschirmanzeige ist.

Die Originalversion war:

main()
{
    printf("hello, world\n");
}

Ein standardkonformes „Hallo Welt“-Programm ist:

# include <stdio.h>

int main(void)
{
    printf("hello, world\n");
}

Die erste Zeile des Programms enthält eine Vorverarbeitungsanweisung , die durch gekennzeichnet ist #include. Dadurch ersetzt der Compiler diese Zeile durch den gesamten Text des stdio.hStandardheaders, der Deklarationen für Standardeingabe- und -ausgabefunktionen wie printfund enthält scanf. Die umgebenden spitzen Klammern stdio.hgeben an, dass stdio.hmit einer Suchstrategie gefunden wird, die Header, die mit dem Compiler bereitgestellt werden, anderen Headern mit demselben Namen vorzieht, im Gegensatz zu doppelten Anführungszeichen, die typischerweise lokale oder projektspezifische Header-Dateien enthalten.

Die nächste Zeile zeigt an, dass eine benannte Funktion maindefiniert wird. Die mainFunktion dient einem speziellen Zweck in C-Programmen; die Laufzeitumgebung ruft die mainFunktion auf, um die Programmausführung zu starten. Der Typbezeichner gibt intan, dass der Wert, der als Ergebnis der Auswertung der mainFunktion an den Aufrufer (in diesem Fall die Laufzeitumgebung) zurückgegeben wird, eine ganze Zahl ist. Das Schlüsselwort voidals Parameterliste zeigt an, dass diese Funktion keine Argumente akzeptiert.

Die öffnende geschweifte Klammer zeigt den Beginn der Definition der mainFunktion an.

Die nächste Zeile ruft (lenkt die Ausführung zu) eine Funktion namens printf, die in diesem Fall von einer Systembibliothek bereitgestellt wird . In diesem Aufruf wird der printfFunktion ein einzelnes Argument übergeben (mitgeliefert), die Adresse des ersten Zeichens im String literal "hello, world\n" . Das String-Literal ist ein unbenanntes Array mit Elementen vom Typ char, das vom Compiler automatisch mit einem abschließenden 0-wertigen Zeichen eingerichtet wird, um das Ende des Arrays zu markieren ( printfmuss dies wissen). Das \nist eine Escape-Sequenz , die C in ein Newline- Zeichen übersetzt, das bei der Ausgabe das Ende der aktuellen Zeile anzeigt . Der Rückgabewert der printfFunktion ist vom Typ int, wird aber stillschweigend verworfen, da er nicht verwendet wird. (Ein sorgfältigeres Programm könnte den Rückgabewert testen, um festzustellen, ob die printfFunktion erfolgreich war oder nicht .) Das Semikolon ;beendet die Anweisung.

Die schließende geschweifte Klammer gibt das Ende des Codes für die mainFunktion an. Gemäß der Spezifikation C99 und neuer gibt die mainFunktion im Gegensatz zu jeder anderen Funktion 0beim Erreichen von implizit den Wert zurück, der }die Funktion beendet. (Früher war eine explizite return 0;Anweisung erforderlich.) Dies wird vom Laufzeitsystem als Exit-Code interpretiert, der eine erfolgreiche Ausführung anzeigt.

Datentypen

Das Typsystem in C ist statisch und schwach typisiert , wodurch es dem Typsystem von ALGOL- Nachkommen wie Pascal ähnelt . Es gibt integrierte Typen für Ganzzahlen unterschiedlicher Größe, sowohl mit als auch ohne Vorzeichen, Gleitkommazahlen und Aufzählungstypen ( enum). Der Integer-Typ charwird häufig für Einzelbyte-Zeichen verwendet. C99 hat einen booleschen Datentyp hinzugefügt . Es gibt auch abgeleitete Typen wie Arrays , Zeiger , Records ( struct) und Unions ( union).

C wird häufig in der Low-Level-Systemprogrammierung verwendet, bei der Escapes aus dem Typsystem erforderlich sein können. Der Compiler versucht, die Typkorrektheit der meisten Ausdrücke sicherzustellen, aber der Programmierer kann die Prüfungen auf verschiedene Weise außer Kraft setzen, entweder indem er eine Typumwandlung verwendet , um einen Wert explizit von einem Typ in einen anderen umzuwandeln, oder indem er Zeiger oder Unions verwendet, um die zugrunde liegenden Bits neu zu interpretieren eines Datenobjekts auf andere Weise.

Einige finden die Deklarationssyntax von C nicht intuitiv, insbesondere für Funktionszeiger . (Ritchies Idee war, Bezeichner in Kontexten zu deklarieren, die ihrer Verwendung ähneln: „ Deklaration spiegelt Verwendung wider “.)

Die üblichen arithmetischen Konvertierungen von C ermöglichen die Generierung von effizientem Code, können jedoch manchmal zu unerwarteten Ergebnissen führen. Zum Beispiel erfordert ein Vergleich von vorzeichenbehafteten und vorzeichenlosen ganzen Zahlen gleicher Breite eine Umwandlung des vorzeichenbehafteten Werts in vorzeichenlose. Dies kann zu unerwarteten Ergebnissen führen, wenn der Wert mit Vorzeichen negativ ist.

Zeiger

C unterstützt die Verwendung von Zeigern , einer Art von Referenz , die die Adresse oder Position eines Objekts oder einer Funktion im Speicher aufzeichnet. Zeiger können dereferenziert werden, um auf Daten zuzugreifen, die an der Adresse gespeichert sind, auf die gezeigt wird, oder um eine Funktion aufzurufen, auf die gezeigt wird. Zeiger können durch Zuweisung oder Zeigerarithmetik manipuliert werden . Die Laufzeitdarstellung eines Zeigerwerts ist normalerweise eine Rohspeicheradresse (vielleicht ergänzt durch ein Offset-innerhalb-Wort-Feld), aber da der Typ eines Zeigers den Typ des Objekts enthält, auf das gezeigt wird, können Ausdrücke, die Zeiger enthalten, typgeprüft werden zur Kompilierzeit. Die Zeigerarithmetik wird automatisch um die Größe des Datentyps skaliert, auf den verwiesen wird. Zeiger werden in C für viele Zwecke verwendet. Textzeichenfolgen werden häufig mit Zeigern in Zeichenarrays manipuliert. Die dynamische Speicherzuweisung erfolgt unter Verwendung von Zeigern. Viele Datentypen, wie Bäume , werden üblicherweise als dynamisch zugewiesene structObjekte implementiert, die unter Verwendung von Zeigern miteinander verbunden sind. Zeiger auf Funktionen sind nützlich, um Funktionen als Argumente an Funktionen höherer Ordnung (wie qsort oder bsearch ) oder als Callbacks zu übergeben, die von Ereignishandlern aufgerufen werden.

Ein Null - Zeiger - Wert weist ausdrücklich keine gültige Position. Das Dereferenzieren eines Nullzeigerwerts ist nicht definiert, was häufig zu einem Segmentierungsfehler führt . Null-Zeigerwerte sind nützlich, um Sonderfälle anzuzeigen, wie z. B. keinen "nächsten" Zeiger im letzten Knoten einer verketteten Liste oder als Fehleranzeige von Funktionen, die Zeiger zurückgeben. In geeigneten Kontexten im Quellcode, z. B. für die Zuweisung zu einer Zeigervariablen, kann eine Nullzeigerkonstante als 0, mit oder ohne explizite Umwandlung in einen Zeigertyp, oder als das NULLdurch mehrere Standardheader definierte Makro geschrieben werden. In bedingten Kontexten werden Nullzeigerwerte als falsch ausgewertet, während alle anderen Zeigerwerte als wahr ausgewertet werden.

Leerzeiger ( void *) zeigen auf Objekte unspezifizierten Typs und können daher als "generische" Datenzeiger verwendet werden. Da Größe und Typ des Objekts, auf das gezeigt wird, nicht bekannt sind, können void-Zeiger weder dereferenziert werden, noch ist Zeigerarithmetik auf sie erlaubt, obwohl sie leicht (und in vielen Kontexten implizit) in und von jedem anderen Objektzeiger konvertiert werden können Typ.

Der unachtsame Umgang mit Zeigern ist potenziell gefährlich. Da sie normalerweise ungeprüft sind, kann eine Zeigervariable dazu gebracht werden, auf eine beliebige Stelle zu zeigen, was unerwünschte Auswirkungen haben kann. Obwohl richtig verwendete Zeiger auf sichere Stellen zeigen, können sie durch die Verwendung einer ungültigen Zeigerarithmetik dazu gebracht werden, auf unsichere Stellen zu zeigen ; die Objekte, auf die sie zeigen, können nach der Freigabe weiter verwendet werden ( dangling pointer ); sie können ohne Initialisierung verwendet werden ( Wildzeiger ); oder ihnen kann direkt ein unsicherer Wert unter Verwendung einer Umwandlung, Vereinigung oder durch einen anderen beschädigten Zeiger zugewiesen werden. Im Allgemeinen erlaubt C die Manipulation von und die Konvertierung zwischen Zeigertypen, obwohl Compiler normalerweise Optionen für verschiedene Überprüfungsebenen bereitstellen. Einige andere Programmiersprachen lösen diese Probleme, indem sie restriktivere Referenztypen verwenden.

Arrays

Array- Typen in C haben traditionell eine feste, statische Größe, die zur Kompilierzeit angegeben wird. (Der neuere C99-Standard erlaubt auch eine Form von Arrays mit variabler Länge.) Es ist jedoch auch möglich, einen Speicherblock (beliebiger Größe) zur Laufzeit mit der mallocFunktion der Standardbibliothek zuzuweisen und ihn als Array. Die Vereinheitlichung von Arrays und Zeigern in C bedeutet, dass deklarierte Arrays und diese dynamisch zugewiesenen simulierten Arrays praktisch austauschbar sind.

Da auf Arrays (tatsächlich) immer über Zeiger zugegriffen wird, werden Array-Zugriffe normalerweise nicht anhand der zugrunde liegenden Array-Größe überprüft, obwohl einige Compiler möglicherweise eine Begrenzungsprüfung als Option bereitstellen . Verletzungen von Array-Grenzen sind daher in nachlässig geschriebenem Code möglich und ziemlich häufig und können zu verschiedenen Auswirkungen führen, einschließlich illegaler Speicherzugriffe, Beschädigung von Daten, Pufferüberläufen und Laufzeitausnahmen. Wenn eine Grenzüberprüfung gewünscht wird, muss sie manuell durchgeführt werden.

C hat keine spezielle Vorkehrung für die Deklaration mehrdimensionaler Arrays , sondern verlässt sich auf die Rekursion innerhalb des Typsystems, um Arrays von Arrays zu deklarieren, was effektiv dasselbe erreicht. Die Indexwerte des resultierenden "mehrdimensionalen Arrays" kann man sich als in Zeilen-Major-Reihenfolge ansteigend vorstellen .

Mehrdimensionale Arrays werden häufig in numerischen Algorithmen (hauptsächlich aus der angewandten linearen Algebra ) verwendet, um Matrizen zu speichern. Die Struktur des C-Arrays ist für diese spezielle Aufgabe gut geeignet. Da Arrays jedoch lediglich als Zeiger übergeben werden, müssen die Grenzen des Arrays bekannte feste Werte sein oder explizit an jede Subroutine übergeben werden, die sie benötigt, und auf Arrays von Arrays mit dynamischer Größe kann nicht mit doppelter Indizierung zugegriffen werden. (Eine Problemumgehung hierfür besteht darin, dem Array einen zusätzlichen "Zeilenvektor" von Zeigern auf die Spalten zuzuweisen.)

C99 führte "Arrays mit variabler Länge" ein, die einige, aber nicht alle Probleme mit gewöhnlichen C-Arrays lösen.

Array-Pointer-Austauschbarkeit

Die tiefgestellte Notation x[i](wobei xeinen Zeiger bezeichnet) ist syntaktischer Zucker für *(x+i). Unter Ausnutzung der Kenntnisse des Compilers über den Zeigertyp ist die Adresse, auf die x + izeigt, nicht die Basisadresse (auf die gezeigt wird x) in iBytes erhöht , sondern wird als die Basisadresse inkrementiert um imultipliziert mit der Größe eines Elements definiert, das xzeigt zu. Somit x[i]bezeichnet das i+1te Element des Arrays.

Außerdem wird in den meisten Ausdruckskontexten (eine bemerkenswerte Ausnahme ist der Operand von sizeof) der Name eines Arrays automatisch in einen Zeiger auf das erste Element des Arrays umgewandelt. Dies impliziert, dass ein Array nie als Ganzes kopiert wird, wenn es als Argument einer Funktion genannt wird, sondern nur die Adresse seines ersten Elements übergeben wird. Obwohl Funktionsaufrufe in C eine Wertübergabe- Semantik verwenden, werden Arrays daher tatsächlich als Referenz übergeben .

Die Größe eines Elements kann durch Anwenden des Operators sizeofauf jedes dereferenzierte Element von x, wie in n = sizeof *xoder n = sizeof x[0], und die Anzahl der Elemente in einem deklarierten Array Aals bestimmt werden sizeof A / sizeof A[0]. Letzteres gilt nur für Array-Namen: Variablen, die mit Indizes ( int A[20]) deklariert sind . Aufgrund der Semantik von C ist es nicht möglich, die gesamte Größe von Arrays durch Zeiger auf Arrays zu bestimmen, wie beispielsweise Arrays, die durch dynamische Zuweisung ( malloc) oder Array-Funktionsparameter erstellt wurden; Code wie sizeof arr / sizeof arr[0](wobei arreinen Zeiger bezeichnet) funktioniert nicht, da der Compiler davon ausgeht, dass die Größe des Zeigers selbst angefordert wird. Da Arraynamensargumente sizeofnicht in Zeiger umgewandelt werden, weisen sie keine solche Mehrdeutigkeit auf. Auf Arrays, die durch dynamische Zuweisung erstellt wurden, wird jedoch eher über Zeiger als über echte Array-Variablen zugegriffen, sodass sie unter den gleichen sizeofProblemen wie Array-Zeiger leiden .

Trotz dieser scheinbaren Äquivalenz zwischen Array- und Zeigervariablen muss also immer noch zwischen ihnen unterschieden werden. Obwohl der Name eines Arrays in den meisten Ausdruckskontexten in einen Zeiger (auf sein erstes Element) umgewandelt wird, belegt dieser Zeiger selbst keinen Speicherplatz; Der Array-Name ist kein L-Wert und seine Adresse ist eine Konstante, im Gegensatz zu einer Zeigervariablen. Folglich kann nicht geändert werden, worauf ein Array "zeigt", und es ist unmöglich, einem Array-Namen eine neue Adresse zuzuweisen. Array-Inhalte können jedoch über die memcpyFunktion oder durch den Zugriff auf die einzelnen Elemente kopiert werden.

Speicherverwaltung

Eine der wichtigsten Funktionen einer Programmiersprache besteht darin, Einrichtungen zum Verwalten des Speichers und der im Speicher abgelegten Objekte bereitzustellen . C bietet drei verschiedene Möglichkeiten, Speicher für Objekte zuzuweisen:

  • Statische Speicherzuweisung : Platz für das Objekt wird zur Kompilierzeit in der Binärdatei bereitgestellt; diese Objekte haben eine Ausdehnung (oder Lebensdauer), solange die Binärdatei, die sie enthält, in den Speicher geladen wird.
  • Automatische Speicherzuweisung : Temporäre Objekte können auf dem Stack gespeichert werden , und dieser Speicherplatz wird automatisch freigegeben und wiederverwendet, nachdem der Block, in dem sie deklariert wurden, verlassen wird.
  • Dynamische Speicherzuweisung : Speicherblöcke beliebiger Größe können zur Laufzeit mithilfe von Bibliotheksfunktionen angefordert werden, z. B. mallocaus einem Speicherbereich namens Heap ; diese Blöcke bleiben bestehen, bis sie anschließend durch Aufrufen der Bibliotheksfunktion zur Wiederverwendung freigegeben werden reallocoderfree

Diese drei Ansätze sind in verschiedenen Situationen geeignet und haben verschiedene Kompromisse. Zum Beispiel hat die statische Speicherzuweisung einen geringen Zuweisungs-Overhead, die automatische Zuweisung kann etwas mehr Overhead beinhalten und die dynamische Speicherzuweisung kann potenziell sowohl für die Zuweisung als auch für die Aufhebung der Zuweisung einen großen Overhead haben. Die persistente Natur statischer Objekte ist nützlich, um Zustandsinformationen über Funktionsaufrufe hinweg aufrechtzuerhalten, die automatische Zuweisung ist einfach zu verwenden, aber der Stack-Speicherplatz ist normalerweise viel begrenzter und vorübergehender als entweder statischer Speicher oder Heap-Speicher, und die dynamische Speicherzuweisung ermöglicht eine bequeme Zuweisung von Objekten, deren Größe ist nur zur Laufzeit bekannt. Die meisten C-Programme nutzen alle drei ausgiebig.

Wenn möglich, ist die automatische oder statische Zuweisung normalerweise am einfachsten, da der Speicher vom Compiler verwaltet wird, wodurch der Programmierer von der potenziell fehleranfälligen Arbeit des manuellen Zuweisens und Freigebens von Speicher befreit wird. Viele Datenstrukturen können sich jedoch zur Laufzeit in ihrer Größe ändern, und da statische Zuweisungen (und automatische Zuweisungen vor C99) zur Kompilierzeit eine feste Größe haben müssen, gibt es viele Situationen, in denen eine dynamische Zuweisung erforderlich ist. Vor dem C99-Standard waren Arrays mit variabler Größe ein übliches Beispiel dafür. ( mallocEin Beispiel für dynamisch zugewiesene Arrays finden Sie im Artikel über .) Im Gegensatz zur automatischen Zuweisung, die zur Laufzeit mit unkontrollierten Folgen fehlschlagen kann, geben die dynamischen Zuweisungsfunktionen einen Hinweis (in Form eines Nullzeigerwerts) zurück, wenn der erforderliche Speicher nicht zugeteilt werden. (Eine zu große statische Zuweisung wird normalerweise vom Linker oder Loader erkannt , bevor das Programm überhaupt mit der Ausführung beginnen kann.)

Sofern nicht anders angegeben, enthalten statische Objekte beim Programmstart Null- oder Nullzeigerwerte. Automatisch und dynamisch zugewiesene Objekte werden nur initialisiert, wenn explizit ein Anfangswert angegeben wird; andernfalls haben sie anfänglich unbestimmte Werte (typischerweise jedes Bitmuster, das zufällig im Speicher vorhanden ist , das möglicherweise nicht einmal einen gültigen Wert für diesen Typ darstellt). Wenn das Programm versucht, auf einen nicht initialisierten Wert zuzugreifen, sind die Ergebnisse undefiniert. Viele moderne Compiler versuchen, dieses Problem zu erkennen und davor zu warnen, aber es können sowohl falsch positive als auch falsch negative Ergebnisse auftreten.

Die Heap-Speicherzuweisung muss mit ihrer tatsächlichen Verwendung in jedem Programm synchronisiert werden, um so viel wie möglich wiederverwendet zu werden. Wenn beispielsweise der einzige Zeiger auf eine Heap-Speicherzuweisung den Gültigkeitsbereich verlässt oder sein Wert überschrieben wird, bevor er explizit freigegeben wird, kann dieser Speicher nicht für eine spätere Wiederverwendung wiederhergestellt werden und geht im Wesentlichen für das Programm verloren, ein Phänomen, das als Speicher bekannt ist undicht . Umgekehrt ist es möglich, dass Speicher freigegeben wird, jedoch nachträglich referenziert wird, was zu unvorhersehbaren Ergebnissen führt. Normalerweise treten die Fehlersymptome in einem Teil des Programms auf, der nichts mit dem Code zu tun hat, der den Fehler verursacht, was die Diagnose des Fehlers erschwert. Solche Probleme werden in Sprachen mit automatischer Garbage Collection behoben .

Bibliotheken

Die Programmiersprache C verwendet Bibliotheken als primäre Erweiterungsmethode. In C ist eine Bibliothek ein Satz von Funktionen, die in einer einzigen "Archiv"-Datei enthalten sind. Jede Bibliothek hat typischerweise eine Header-Datei , die die Prototypen der in der Bibliothek enthaltenen Funktionen enthält, die von einem Programm verwendet werden können, sowie Deklarationen spezieller Datentypen und Makrosymbole, die mit diesen Funktionen verwendet werden. Damit ein Programm eine Bibliothek verwenden kann, muss es die Header-Datei der Bibliothek enthalten und die Bibliothek muss mit dem Programm verbunden sein, was in vielen Fällen Compiler-Flags erfordert (zB -lm, Abkürzung für "link the math library").

Die gebräuchlichste C-Bibliothek ist die C-Standardbibliothek , die von den ISO- und ANSI-C- Standards spezifiziert wird und mit jeder C-Implementierung geliefert wird (Implementierungen, die auf eingeschränkte Umgebungen wie eingebettete Systeme abzielen, stellen möglicherweise nur einen Teil der Standardbibliothek bereit). Diese Bibliothek unterstützt Stream-Ein- und -Ausgabe, Speicherzuordnung, Mathematik, Zeichenfolgen und Zeitwerte. Mehrere separate Standardheader (z. B. stdio.h) spezifizieren die Schnittstellen für diese und andere Standardbibliotheksfunktionen.

Ein weiterer üblicher Satz von C-Bibliotheksfunktionen werden von Anwendungen verwendet, die speziell auf Unix und Unix-ähnliche Systeme ausgerichtet sind, insbesondere Funktionen, die eine Schnittstelle zum Kernel bereitstellen . Diese Funktionen sind in verschiedenen Standards wie POSIX und der Single UNIX Specification detailliert beschrieben .

Da viele Programme in C geschrieben wurden, gibt es eine Vielzahl anderer Bibliotheken. Bibliotheken werden oft in C geschrieben, weil C-Compiler effizienten Objektcode generieren ; Programmierer erstellen dann Schnittstellen zur Bibliothek, damit die Routinen von höheren Sprachen wie Java , Perl und Python verwendet werden können .

Dateihandling und Streams

Die Dateieingabe und -ausgabe (I/O) ist nicht Teil der C-Sprache selbst, sondern wird stattdessen von Bibliotheken (wie der C-Standardbibliothek) und ihren zugehörigen Header-Dateien (zB stdio.h) gehandhabt . Die Dateibehandlung wird im Allgemeinen durch High-Level-I/O implementiert, die über Streams funktioniert . Ein Stream ist aus dieser Perspektive ein geräteunabhängiger Datenfluss, während eine Datei ein konkretes Gerät ist. Die High-Level-I/O erfolgt durch die Zuordnung eines Streams zu einer Datei. In der C-Standardbibliothek wird ein Puffer (ein Speicherbereich oder eine Warteschlange) vorübergehend verwendet, um Daten zu speichern, bevor sie an das endgültige Ziel gesendet werden. Dies reduziert die Wartezeit auf langsamere Geräte, beispielsweise eine Festplatte oder ein Solid-State-Laufwerk . Low-Level-I/O-Funktionen sind nicht Teil der Standard-C-Bibliothek, sondern gehören im Allgemeinen zur "Bare-Metal"-Programmierung (Programmierung, die von jedem Betriebssystem unabhängig ist , wie die meisten Embedded-Programme ). Mit wenigen Ausnahmen umfassen Implementierungen Low-Level-I/O.

Sprachwerkzeuge

Eine Reihe von Werkzeugen wurde entwickelt, um C-Programmierern zu helfen, Anweisungen mit undefiniertem Verhalten oder möglicherweise fehlerhaften Ausdrücken zu finden und zu korrigieren, und zwar mit größerer Strenge als die vom Compiler bereitgestellte. Das Werkzeug Fussel war das erste, was zu vielen anderen.

Automatisierte Quellcodeprüfung und Auditing sind in jeder Sprache von Vorteil, und für C gibt es viele solcher Tools, wie zum Beispiel Lint . Eine gängige Praxis ist die Verwendung von Lint, um fragwürdigen Code zu erkennen, wenn ein Programm zum ersten Mal geschrieben wird. Sobald ein Programm Lint passiert, wird es mit dem C-Compiler kompiliert. Außerdem können viele Compiler optional vor syntaktisch gültigen Konstrukten warnen, die wahrscheinlich tatsächlich Fehler sind. MISRA C ist ein proprietärer Satz von Richtlinien, um solchen fragwürdigen Code zu vermeiden, der für eingebettete Systeme entwickelt wurde.

Es gibt auch Kompilierer, Bibliotheken und Betriebssystemebene Mechanismen für Aktionen auszuführen , die nicht zu einem festen Bestandteil von C sind, wie beispielsweise die Überprüfung der Grenzen für Arrays, Erkennung von Pufferüberlauf , Serialisierung , dynamischer Speicherverfolgung und automatischer Garbage Collection .

Tools wie Purify oder Valgrind und die Verknüpfung mit Bibliotheken, die spezielle Versionen der Speicherzuweisungsfunktionen enthalten, können dabei helfen, Laufzeitfehler bei der Speichernutzung aufzudecken.

Verwendet

Die TIOBE- Indexgrafik, die die Popularität verschiedener Programmiersprachen im Vergleich zeigt

C wird häufig für die Systemprogrammierung bei der Implementierung von Betriebssystemen und eingebetteten Systemanwendungen verwendet , da C-Code, wenn er für die Portabilität geschrieben ist, für die meisten Zwecke verwendet werden kann, aber bei Bedarf systemspezifischer Code verwendet werden kann, um auf bestimmte Hardwareadressen zuzugreifen und zuführen Typ punning extern auferlegten Schnittstellenanforderungen zu entsprechen, mit einem geringen Laufzeit Bedarf an Systemressourcen.

C kann für die Website-Programmierung mit dem Common Gateway Interface (CGI) als "Gateway" für Informationen zwischen der Webanwendung, dem Server und dem Browser verwendet werden. C wird aufgrund seiner Geschwindigkeit, Stabilität und nahezu universellen Verfügbarkeit häufig gegenüber interpretierten Sprachen gewählt .

Eine Folge der breiten Verfügbarkeit und Effizienz von C ist, dass Compiler , Bibliotheken und Interpreter anderer Programmiersprachen oft in C implementiert werden. Zum Beispiel sind die Referenzimplementierungen von Python , Perl , Ruby und PHP in C geschrieben.

C ermöglicht Programmierern, effiziente Implementierungen von Algorithmen und Datenstrukturen zu erstellen, da die Abstraktionsschicht von der Hardware dünn und ihr Overhead gering ist, ein wichtiges Kriterium für rechenintensive Programme. Zum Beispiel sind die GNU Multiple Precision Arithmetic Library , die GNU Scientific Library , Mathematica und MATLAB ganz oder teilweise in C geschrieben.

C wird manchmal als Zwischensprache von Implementierungen anderer Sprachen verwendet. Dieser Ansatz kann aus Gründen der Portabilität oder Bequemlichkeit verwendet werden; Durch die Verwendung von C als Zwischensprache sind keine zusätzlichen maschinenspezifischen Codegeneratoren erforderlich. C hat einige Funktionen, wie z. B. Zeilennummern-Präprozessordirektiven und optional überflüssige Kommas am Ende von Initialisierungslisten, die die Kompilierung von generiertem Code unterstützen. Einige der Unzulänglichkeiten von C haben jedoch die Entwicklung anderer C-basierter Sprachen veranlasst, die speziell für die Verwendung als Zwischensprachen entwickelt wurden, wie beispielsweise C-- .

C wurde auch häufig verwendet, um Endbenutzeranwendungen zu implementieren . Solche Anwendungen können jedoch auch in neueren, höheren Sprachen geschrieben werden.

Verwandte Sprachen

C hat viele spätere Sprachen wie C# , D , Go , Java , JavaScript , Limbo , LPC , Perl , PHP , Python und die C-Shell von Unix direkt und indirekt beeinflusst . Der weitreichendste Einfluss war syntaktisch; alle genannten Sprachen kombinieren die Anweisungs- und (mehr oder weniger erkennbar) Ausdruckssyntax von C mit Typsystemen, Datenmodellen und/oder umfangreichen Programmstrukturen, die sich teilweise radikal von denen von C unterscheiden.

Es gibt mehrere C- oder C-nahe Interpreter, darunter Ch und CINT , die auch für Skripte verwendet werden können.

Als objektorientierte Sprachen populär wurden, waren C++ und Objective-C zwei verschiedene Erweiterungen von C, die objektorientierte Fähigkeiten bereitstellten. Beide Sprachen wurden ursprünglich als Source-to-Source-Compiler implementiert ; Quellcode wurde in C übersetzt und dann mit einem C-Compiler kompiliert.

Die Programmiersprache C++ wurde von Bjarne Stroustrup entwickelt , um objektorientierte Funktionalität mit einer C-ähnlichen Syntax bereitzustellen. C++ fügt eine größere Typisierungsstärke, einen größeren Bereich und andere nützliche Tools für die objektorientierte Programmierung hinzu und ermöglicht eine generische Programmierung über Vorlagen. Fast eine Obermenge von C, C++ unterstützt jetzt den größten Teil von C, mit wenigen Ausnahmen .

Objective-C war ursprünglich eine sehr "dünne" Schicht auf C und bleibt eine strikte Obermenge von C, die objektorientierte Programmierung unter Verwendung eines hybriden dynamischen/statischen Typisierungsparadigmas ermöglicht. Objective-C leitet seine Syntax sowohl von C als auch von Smalltalk ab : Syntax, die Vorverarbeitung, Ausdrücke, Funktionsdeklarationen und Funktionsaufrufe umfasst, wird von C geerbt, während die Syntax für objektorientierte Funktionen ursprünglich von Smalltalk übernommen wurde.

Neben C++ und Objective-C sind Ch , Cilk und Unified Parallel C fast Obermengen von C.

Siehe auch

Anmerkungen

Verweise

Quellen

Weiterlesen

Externe Links