1 Theorie | |
→ | 1.1 Die Programmiersprache C |
Bevor der Compiler mit der eigentlichen Verarbeitung beginnt, wird mit dem Präprozessor eine Vorverarbeitung durchgeführt.
Mit
#include <datei.h>
wird anstelle der #include-Zeile der Inhalt der Datei "datei.h"
eingefügt.
Dabei wird in verschiedenen Verzeichnissen nach der Datei "datei.h"
gesucht.
Im Include-Pfad (Liste der zu durchsuchenden Verzeichnisse) sind
meist die Verzeichnisse für die *.h-Dateien, die Bestandteil der
Entwicklungsumgebung sind, bereits enthalten. Auf Linux-Systemen
ist auch /usr/include mit den Betriebssystem-eigenen Header-Dateien
schon aufgeführt.
Der Include-Pfad kann in den Projekteinstellungen der IDE
bearbeitet werden. Wird der Compiler per Kommandozeile gestartet,
kann die Option "-IVerzeichnis" genutzt werden, um ein
Verzeichnis hinzuzufügen.
Die zweite Form
#include "datei.h"
sucht die Datei "datei.h" zunächst im aktuellen Verzeichnis. Wird die Datei hier nicht gefunden, wird der Include-Pfad benutzt.
Header-Dateien enthalten meist Datentyp-Definitionen, Prototypen von Funktionen und Definitionen von Konstanten und Makros.
Die nachfolgende Tabelle zeigt einige ausgewählte, häufig genutzte Include-Dateien, die in den Beispielen des Einführungskurses verwendet werden.
stdio.h | Standardeingabe, Standardausgabe, Ein-
und Ausgabe in Dateien. Wird fast immer verwendet. Unter Windows der #include-Zeile #define _CRT_SECURE_NO_WARNINGS 1voranstellen. |
conio.h | Nur unter Windows: Mit _getch() Zeichen von Tastatur lesen, weitere Ein-/Ausgabefunktionen. |
io.h | Windows-Dateiattribute, Durchsuchen von Verzeichnissen unter Windows. |
fcntl.h | Konstanten für Dateitypen, Dateieigenschaften... |
stdlib.h | Funktionen der Standardbibliothek,
Konstanten EXIT_SUCCESS und EXIT_FAILURE für Status am
Programmende. Auf Nicht-Windows-Systemen Deklaration der Funktion exit(). |
process.h | Funktion exit() auf Windows-Systemen. |
errno.h | Fehlercodes der C-Runtime-Library. |
string.h | Funktionen für die Arbeit mit Strings. |
ctype.h | Klassifizierung und Umwandlung von einzelnen Zeichen. |
limits.h | Minimal- und Maximalwerte für verschiedene Integer-Datentypen. |
stdint.h | Weitere Integer-Datentypen (z.B. intmax_t/uintmax_t) und deren Minimal- und Maxwerte, u.a. SIZE_MAX. |
math.h | Berechnungen mit Gleitkommazahlen. Unter Windows der #include-Zeile #define _USE_MATH_DEFINES 1voranstellen. |
float.h (nur Windows) fenv.h |
Abfrage des Floating-Point-Statusregisters zur Prüfung auf Exceptions. |
Mit der #define-Anweisung können Konstanten oder Makros definiert werden.
#define Name Wert
Beispiel:
#define ZEHN 10 ... for (i = 0; i < ZEHN; i++) { printf("i = %d\n", i); }
Gängige Coding Standards empfehlen, den Präprozessor so wenig
wie möglich zu nutzen.
Zur Definition von int-Konstanten sollte besser ein anonymes enum
benutzt werden, z.B.:
enum { ZEHN = 10 };
Ein Makro kann analog zu einer Funktion verwendet werden, jedoch wird hier nicht Code für eine Funktion erstellt und dann aufgerufen sondern stattdessen per Text-Ersetzung der Code eingefügt.
Beispiel:
#define BERECHNUNG(a,b,x) a * x + b i = BERECHNUNG(2.5,3.0,4); j = BERECHNUNG(k,l,m);
wird vom Präprozessor ersetzt — als reine Text-Ersetzung — durch
i = 2.5 * 4 + 3.0; j = k * m + l;
Ein Makro kann sich über mehrere Zeilen erstrecken. Ein Backslash am Zeilenende gibt an, dass das Makro in der nächsten Zeile fortgesetzt wird.
Mehrfach vorkommende Programmteile sollten besser in einer
Funktion zusammengefasst werden.
Aus verschiedenen Gründen — siehe Abschnitt Coding
Standards unten — wird die Verwendung von Makros für diesen
Zweck nicht empfohlen.
Eine Reihe von Konstanten ist jeweils durch den Compiler selbst schon vordefiniert:
Konstante | Bedeutung |
---|---|
__cplusplus | Diese Konstante ist definiert, wenn ein C++-Compiler benutzt wird bzw. der Compiler von Visual Studio sich im C++-Mode befindet. |
_WIN32 | Diese Konstante ist definiert, wenn der
Compiler von Visual Studio benutzt wird. Zum Teil definieren auch andere Compiler diese Konstante, wenn für die Zielplattform Windows compiliert wird. |
Weitere vordefinierte Konstanten bei der Arbeit mit MS Visual Studio finden Sie mit einer Suche nach:
predefined macros site:microsoft.com
Sie sollten die englischsprachigen Suchergebnisse bevorzugen, da die Übersetzung in deutsche Sprache maschinell erfolgte.
Die Programmiersprache C ist auf unterschiedlichen Hardwarearchitekturen und unterschiedlichen Betriebssystemen nutzbar, z.B.:
Diese verschiedenen Computer sind unterschiedlich
leistungsfähig. Manche Bibliotheken, Funktionen und zugehörige
Header-Dateien sind nur auf einem Teil dieser Computer
vorhanden.
Teilweise existieren auf den Systemen Funktionen mit gleicher
Funktionalität aber unterschiedlichem Namen, auch können Prototypen
in unterschiedlichen Header-Dateien deklariert sein.
Mit bedingter Compilierung kann wahlweise nur ein Teil des Quelltextes compiliert werden.
#ifdef KONSTANTE /* Quelltext hier wird compiliert, falls KONSTANTE definiert. */ #else /* Quelltext hier wird compiliert, falls KONSTANTE nicht definiert. */ #endif
#ifndef KONSTANTE /* Quelltext hier wird compiliert, falls KONSTANTE nicht definiert. */ #else /* Quelltext hier wird compiliert, falls KONSTANTE definiert. */ #endif
#if KONSTANTE /* Quelltext hier wird compiliert, falls KONSTANTE definiert und nicht 0. */ #else /* Andernfalls wird Quelltext hier verwendet. */ #endif
#if !KONSTANTE /* Quelltext hier wird compiliert, falls KONSTANTE nicht definiert oder 0. */ #else /* Andernfalls wird Quelltext hier verwendet. */ #endif
#if Bedingung /* Quelltext hier wird compiliert, falls die Bedingung erfuellt ist. */ #else /* Andernfalls wird der Quelltext hier verwendet. */ #endif
#ifdef KONSTANTE /* Quelltext hier wird compiliert, falls KONSTANTE definiert. */ #endif
#ifndef KONSTANTE /* Quelltext hier wird compiliert, falls KONSTANTE nicht definiert. */ #endif
#if KONSTANTE /* Quelltext hier wird compiliert, falls KONSTANTE definiert und nicht 0. */ #endif
#if !KONSTANTE /* Quelltext hier wird compiliert, falls KONSTANTE nicht definiert oder 0. */ #endif
#if Bedingung /* Quelltext hier wird compiliert, falls die Bedingung erfuellt ist. */ #endif
Soll ein Teil des Quelltextes von der Compilierung ausgenommen werden, bietet sich auf den ersten Blick das Auskommentieren an.
Beispiel:
Der Quelltext
int i = 0; /* Schleifenvariable */ k = ???
soll deaktiviert werden, da die Berechnung von k noch bearbeitet werden muss.
Man könnte nun mit einem Kommentar arbeiten, z.B.:
/* int i = 0; /* Schleifenvariable */ k = ??? */
Verschachtelte Kommentare sind laut C-Standard
unzulässig!
Einige Compiler können zwar mit verschachtelten Kommentaren
umgehen, jedoch nicht alle.
Insbesondere beim Auskommentieren größerer Code-Stücke kann es
vorkommen, dass vorhandene Kommentare übersehen werden und
unbeabsichtigt verschachtelte Kommentare erzeugt werden.
Zur Deaktivierung von Code sollte besser der Präprozessor verwendet werden:
#if 0 int i = 0; /* Schleifenvariable */ k = ??? #endif
Die Datei he-conf.h überprüft, ob
eine der für C-Compiler unter Windows typischen Konstanten WIN32,
WIN64, _WIN32 oder _WIN64 definiert ist.
Falls ja, wird die Konstante HE_ON_WINDOWS auf 1 definiert und die
Header-Datei he-windows.h
vearbeitet.
Falls keine der vier Konstanten definiert ist, wird der Quelltext auf einem Nicht-Windows-System benutzt. In diesem Fall wird die Konstante HE_ON_WINDOWS auf 0 definiert und entweder die von einem configure-Script erzeugte Header-Datei he-config.h oder die Header-Datei he-defs.h mit Standardeinstellungen für POSIX-Systeme vearbeitet.
Die Dateien he-windows.h, he-config.h bzw. he-defs.h definieren
Konstanten wie z.B. HAVE_PROCESS_H zu 1 oder 0, je nachdem ob eine
Header-Datei "process.h" verfügbar ist oder nicht.
Derartige Konstanten erkennen Sie daran, dass der Name mit "HAVE_"
beginnt und auf "_H" endet.
Andere Konstanten wie z.B. "HAVE_STRTOL", die nicht auf "_H" enden, zeigen an, ob bestimmte Funktionen — hier strtol() — vorhanden sind oder nicht.
Manche Funktionen wie z.B. exit() sind auf einigen
Systemen in der Header-Datei "process.h" bzw. "stdlib.h"
deklariert, auf anderen Systemen in "unistd.h"
Um portabel zu programmieren — d.h. um den Quelltext möglichst
problemlos auf andere Systeme übertragen zu können — werden mit
#if HAVE_STDLIB_H #include <stdlib.h> #endif #if HAVE_UNISTD_H #include <unistd.h> #endif #if HAVE_PROCESS_H #include <process.h> #endif
alle in Frage kommenden Header-Dateien benutzt, falls sie vorhanden sind.
Trifft der Präprozessor auf eine Direktive
#error "Fehlermeldung"
so wird die Fehlermeldung angezeigt und das Compilieren abgebrochen.
Die meisten Compiler bieten zusätzliche Direktiven, die mit
"#pragma" beginnen. Häufig werden diese verwendet, um bestimmte
Compiler-Warnungen ein- oder auszuschalten.
Da diese Direktiven compilerspezifisch sind, werden sie hier nicht
näher erläutert.
Quelle: CERT C Coding Standard 〈1〉
#define MAX(a,b) ((a > b) ? a : b)Versucht man nun, das Maximum von i und j zu finden und beide Variablen um 1 zu erhöhen, könnte man auf die Idee kommen,
maxwert = MAX(i++,j++);zu verwenden. Dies schlägt fehl, da die Text-Substitution
maxwert = ((i++ > j++) ? i++ : j++);ergibt, was zu einer Erhöhung der Variablen jeweils um 2 führt.
maxwerrt = MAX( #if TEST_KONSTANTE 3, #else 4, #endif 5 );Richtig:
#if TEST_KONSTANTE maxwert = MAX(3,5); #else maxwert = MAX(4,5); #endif
#define QUADRAT(x) x * x /* Soll 25 ergeben, ... */ y = QUADRAT(2+3); /* ... ergibt aber 11, da Ergebnis der Textersetzung */ y = 2 + 3 * 2 + 3;Besser:
#define QUADRAT(x) (x) * (x) y = QUADRAT(2+3); /* ergibt nach Textersetzung */ y = (2+3) * (2+3);
#define QUADRAT(x) (x) * (x) i = 100 / QUADRAT(2+3); /* ergibt nicht 4, wie erwartet sondern 100 wegen Textersetzung */ i = 100 / (2+3) * (2+3);Richtig:
#define QUADRAT(x) ((x) * (x)) i = 100 / QUADRAT(2+3); /* ergibt das richtige Ergebnis */ i = 100 / ((2+3) * (2+3));
#define uchar unsigned charBesser:
typedef unsigned char uchar;
#define SWAP(x,y) tmp = x; x = y; y = tmp /* Wenn a < b, sollen die Inhalte der Variablen a und b getauscht werden. */ if (a < b) SWAP(a,b);ergibt
if (a < b) tmp = x; x = y; y = tmp;Die if-Anweisung führt dazu, dass nur die Ausführung von
tmp = x;von der Bedingung a<b abhängt. Die Anweisungen
x = y; y = tmp;werden unabhängig von der Bedingung ausgeführt, dies ist nicht beabsichtigt.
#define SWAP(x,y) do { tmp = x; x = y; y = tmp; } while(0) /* Wenn a < b, sollen die Inhalte der Variablen a und b getauscht werden. */ if (a < b) SWAP(a,b);ergibt
if (a < b) do { tmp = a; a = b; b = tmp; } while(0);Hier wird die do-while-Schleife mit den drei Anweisungen entweder ganz oder gar nicht ausgeführt, abhängig von der Bedingung.
1 | http://www.securecoding.cert.org |