| #include <pronix.de> |
|
|
23.5. <setjmp.h>
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Name | Bedeutung |
| SIGABRT | Dieses Signal signalisiert, dass sich das Programm abnormal beendet hat (abort()). |
| SIGFPE | Dieses Signal wird z.B.angezeigt bei einer Division durch 0 oder einem Überlauf einer Zahl. |
| SIGILL | Dieses Signal wird angezeigt, wenn ein illegaler Hardware-Befehl ausgeführt wird. |
| SIGINT | Dieses Signal wird an alle Prozesse geschickt, wenn die Tastenkombination STRG + C gedrückt wurde. |
| SIGSEGV | Wird dies angezeigt, wurde versucht, auf eine unerlaubte Speicherstelle zu schreiben oder zu lesen. |
| SIGTERM | Beendigung eines Programms |
Unter Linux gibt es deutlich mehr Signale (ca. 30). Mit dem Befehl
kill -l
wird eine Liste der Signale unter Linux/UNIX ausgegeben.
Tritt ein Signal auf, haben Sie folgende Möglichkeiten, darauf zu reagieren:
Um auf die Signale zu reagieren, existiert ein so genanntes Signalkonzept. Dabei richtet ein Prozess einen so genannten Signalhandler ein. Dieser Signalhandler teilt - wenn das Signal auftritt - dem Systemkern mit, was er zu tun hat. Ein solcher Handler kann mit der Funktion signal() eingerichtet werden. Hier ihre Syntax:
#include <signal.h> void(*signal(int signr, void(*sighandler)(int)))(int);
Einen solchen Prototypen zu lesen, ist fast unmöglich. Aus diesem Grund wurde die Funktion in der Headerdatei <signal.h> wie folgt vereinfacht:
typedef void (*__p_sig_fn_t)(int); __p_sig_fn_t signal(int, __p_sig_fn_t);
Somit sieht der Prototyp folgendermaßen aus:
signalfunktion *signal(int signalnummer,
signalfunktion *sighandler);
Mit dem Parameter signalnummer legen Sie die Nummer des Signals fest, für die ein Signalhandler eingerichtet werden soll. Dies ist dann eines der Signale, welche Sie soeben in der Tabelle kennen gelernt haben (bzw. unter Linux diejenigen, die mit kill -l aufgelistet wurden).
Für den Parameter sighandler sind zwei Konstanten in der Headerdatei <signal.h> deklariert, SIG_DFL und SIG_IGN. Mit SIG_DFL wird die Default-Aktion ausgeführt, was meist die Beendigung des Prozesses bedeutet. Ein Beispiel:
signal(SIGINT,SIG_DFL);
Falls Sie die Tastenkombination STRG + C drücken, wird die Default-Einstellung des Signals SIGINT ausgeführt. Und die Default-Einstellung schreibt vor, dass das Programm beendet wird. Als zweite Möglichkeit können Sie Folgendes eingeben:
signal(SIGINT,SIG_IGN);
Drücken Sie jetzt die Tastenkombination STRG + C, passiert gar nichts. Das Signal SIGINT wird mit der Angabe von SIG_IGN ignoriert. Als dritte Möglichkeit können Sie das Signal SIGINT abfangen und die Adresse einer eigenen Funktion übergeben, welche ausgeführt werden soll, wenn die Tastenkombination STRG + C betätigt wurde:
signal(SIGINT,funktionsaufruf);
Jetzt wird es Zeit, zu sehen, wie die Funktion signal() in der Praxis eingesetzt wird:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void sigfunc(int sig)
{
int c;
if(sig != SIGINT)
return;
else
{
printf("\nWollen Sie das Programm beenden (j/n) : ");
c=getchar();
if(c == 'j')
exit (0);
else
return;
}
}
int main()
{
int i;
signal(SIGINT,sigfunc);
while(1)
{
printf("Mit STRG+C beenden");
for(i=0;i<=48;i++)
printf("\b");
}
return 0;
}
Mit der Anweisung
signal(SIGINT,sigfunc);
wird ein Signalhandler für das Signal SIGINT eingerichtet, der beim Auftreten dieses Signals die Funktion sigfunc aufrufen soll.
Ein einfaches Beispiel bietet auch das Erstellen einer eigenen kleinen Shell. Die einzelnen Shellbefehle werden in einer Endlosschleife abgearbeitet. Mit der Tastenkombination STRG + C lösen Sie dabei einen Neustart der Shell aus. Dieser Sprung (Neustart) wird mit den Funktionen der Headerdatei <setjmp.h> realisiert. Hier das Beispiel dazu:
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>
#include <stdlib.h>
#define MAX 255
#define OK 0
jmp_buf restart;
void ctrlc(int sig)
{
signal(sig,ctrlc);
/* Zurück zur Kommandozeile */
longjmp(restart,1);
return;
}
int main()
{
char *command;
/* Install signal handler */
signal(SIGINT,ctrlc);
if(setjmp(restart) != 0)
printf("\n\nShell neu gestartet...\n\n");
else
printf("\n\nShell gestartet...\n\n");
for (;;)
{/* Hier können Sie machen was Sie wollen */
char puffer[MAX];
printf("$~> ");
fgets(puffer, MAX, stdin);
command = strtok(puffer, "\n");
if( strcmp(command, "test") == OK )
printf("Ihr Befehl lautete \"test\"\n");
else if( strcmp(command, "help") == OK )
printf("Brauchen Sie Hilfe?\n");
/* usw. eine Menge mehr Shellbefehle …*/
else if( strcmp(command, "exit") == OK )
exit(0);
else
{
printf("\nUnbekannter Shellbefehl\n");
printf("Bekannte Befehle: test, help, exit\n\n");
}
}
return 0;
}
Dies ist eine einfache Schnittstelle einer eigenen Shell. Logischerweise müssen Sie statt der Ausgabe von Texten Ihre selbst geschriebenen Funktionen implementieren.
Ein weiteres Beispiel zu signal() mit dem Signal SIGABRT.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void sigfunc(int sig)
{
if(sig==SIGABRT)
printf("Demonstration von SIGABRT\n");
}
int main()
{
signal(SIGABRT,sigfunc);
abort();
return 0;
}
Um zu testen, ob der Aufruf der Funktion signal() überhaupt erfolgreich war, befindet sich in der Headerdatei <signal.h> der Fehlercode SIG_ERR, der mit dem Wert -1 definiert ist. Wollen Sie also die Funktion signal() auf Fehler überprüfen, sollte dies so aussehen:
if( signal(SIGINT,sigfunc) == SIG_ERR)
{ /* Fehler beim Aufruf von signal */
Es ist auch möglich, ein Signal an ein ausführendes Programm mit der Funktion raise() zu senden. Die Syntax der Funktion:
int raise(int signr);
Damit können Sie ein Signal mit der signr an das Programm senden. Ein kurzes Beispiel:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void sigfunc(int sig)
{
if(sig==SIGINT)
printf("SIGINT wurde ausgelöst\n");
}
int main()
{
signal(SIGINT,sigfunc);
/* SIGINT auslösen */
raise(SIGINT);
return 0;
}
Für Signale unter Linux/UNIX ist dies hier allerdings nur die Grundlage.
Die meisten Stringfunktionen wurden bereits an früherer Stelle behandelt. Aber einige Funktionen habe ich Ihnen bislang noch unterschlagen. Mit den mem...-Funktionen in der Headerdatei <string.h> können Sie ganze Speicherblöcke kopieren, vergleichen, initialisieren und durchsuchen.
23.7.1 memchr() - Suche nach einzelnen Zeichen
Die Syntax:
void *memchr(const void *buffer, int c, size_t n);
Diese Funktion sucht in den ersten n Bytes in buffer nach dem Zeichen c. Sollten Sie den ganzen String durchsuchen wollen, können Sie die Funktion strchr() verwenden. Tritt dabei ein Fehler auf oder wird das Zeichen nicht gefunden, gibt diese NULL zurück. Ein Beispiel:
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "Have a lot of fun";
char *p;
char ch = 'l';
p = (char *) memchr( str, ch, 10);
if(NULL == p)
printf("%c kommt nicht in den ersten 10 Bytes vor\n",ch);
else
printf("%c gefunden an Pos. %d\n",ch, p-str);
return 0;
}
23.7.2 memcmp() - Bestimmte Anzahl von Bytes vergleichen
Die Syntax:
int memcmp( const void *s1, const void *s2, size_t n);
Mit memcmp() werden die ersten n Bytes im Puffer s1 mit dem Puffer s2 lexografisch verglichen. Der Rückgabewert ist derselbe wie schon bei strcmp(). Ist s1 größer als s2, ist der Rückgabewert kleiner als 0. Ist s2 größer als s1, ist die Rückgabe größer als 0, und bei Gleichheit beider Speicherbereiche wird 0 zurückgegeben.
#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = "Have a lot of fun";
char str2[] = "Have more than a lot of fun";
int check, i;
for(i = 4; i <= 10; i+=6)
{
check = memcmp( str1, str2, i);
if(check == 0)
printf("Vergleich %d Bytes: "
"Beide Strings sind gleich\n",i);
else
printf("Die ersten %d Bytes sind "
"unterschiedlich\n",i);
}
return 0;
}
23.7.3 memcpy() - Bestimmte Anzahl von Bytes kopieren
Die Syntax:
void *memcpy(void *dest, const void *src, size_t n);
Mit der Funktion memcpy() können Sie n Bytes aus dem Puffer src in den Puffer dest kopieren. Die Funktion gibt die Anfangsadresse von dest zurück.
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "Ein Wort was hier nicht hingehört: Mist!";
char replace[] = "M***";
char *ptr;
ptr = strstr(str, "Mist");
memcpy(ptr, replace, strlen(replace));
printf("%s\n",str);
return 0;
}
23.7.4 memmove() - Bestimmte Anzahl von Bytes kopieren
Die Syntax:
void *memmove(void *dest, const void* src, size_t n);
Die Funktion erfüllt denselben Zweck wie die Funktion memcp(), mit einem einzigen, aber gravierenden Unterschied. memmove() stellt sicher, dass im Fall einer Überlappung der Speicherbereiche der Überlappungsbereich zuerst gelesen und dann überschrieben wird. Auch die Rückgabewerte sind bei memmove() dieselben wie bei memcpy().
#include <stdio.h>
#include <string.h>
int main()
{
char str[21] = "1234567890";
/* Den kompletten String nehmen und
10 Bytes weiter als Kopie ablegen */
memmove(str+10, str, sizeof(str)-1);
printf("%s\n",str);
return 0;
}
23.7.5 memset() - Speicherbereich mit bestimmten Zeichen auffüllen
Die Syntax:
void *memset(void *dest, int ch, unsigned int n);
Mit dieser Funktion füllen Sie die ersten n Bytes der Adresse dest mit den Zeichen ch auf.
#include <stdio.h>
#include <string.h>
int main()
{
char credit_card[21] = "123456-aiex";
char *ptr = strchr(credit_card, '-');
*ptr++;
/* Die letzten vier Zeichen der Kreditkartennummer
nicht angeben */
memset(ptr, '*', 4);
printf("%s\n",credit_card);
return 0;
}
Ursprünglich war für den erweiterten ANSI-C-Standard ein extra Kapitel vorgesehen. Aber um den Umfang des Buchs nicht zu sprengen und Ihre Geduld nicht allzu sehr zu strapazieren, werden diese Erweiterungen hier in einer kurzen Zusammenfassung erwähnt. Sie stellt keine Referenz der Erweiterungen dar, sondern fasst lediglich das Wichtigste in Kürze zusammen.
|
Hinweis |
|
Es kann sein, dass dieser erweiterte ANSI C99-Standard bei vielen Compilern noch gar nicht implementiert ist und deshalb auch nicht funktioniert. |
|
23.8.1 Neue elementare Datentypen
Zur Darstellung der boolschen Werte 1 und 0 wurde der Typ _Bool eingeführt. Der Wertebereich von _Bool beträgt 0 und 1. Wenn Sie die Headerdatei <stdbool.h> inkludieren, können Sie auch bool statt _Bool schreiben und die Bezeichner true für 1 und false für 0 verwenden.
Weitere zwei Ganzzahltypen, die eingeführt wurden, sind long long und unsigned long long, welche beide 8 Bytes Speicherplatz belegen. Hier ein Überblick der neuen Ganzzahltypen des C99-Standards:
| Typ | Speicherplatz | Wertebereich |
| _Bool | 1 Byte | 0 und 1 |
| long long | 8 Byte | -9223372036854775808 bis 9223372036854775807 |
| unsigned long long | 8 Byte | 0 bis 18446744073709551615 |
23.8.2 <stdint.h> - Ganzzahlige Typen mit vorgegebener Breite
In der Headerdatei <stdint.h> befinden sich weitere Ganzzahldatentypen, die mit vorgegebener Breite verwendet werden. Mit vorgegebener Breite ist die Anzahl der Bits zur Darstellung des Werts gemeint, welche dieser Typ verwenden darf. Hier die Typen im Überblick:
| Typ | Bedeutung |
| intN_t | Ein int-Wert mit einer Breite von exakt N Bits(erlaubte Werte für N: 8, 16, 32, 64) |
| int_leastN_t | Ein int-Wert mit einer Breite von mindestens N Bits(erlaubte Werte für N: 8, 16, 32, 64) |
| int_fastN_t | Der schnellste int-Typ mit mind. einer Breite von N Bits(erlaubte Werte für N: 8, 16, 32, 64) |
| intmax_t | Größtmöglicher ganzzahliger Typ (Wert ist in der Konstante INT64_MAX bzw. UINT64_MAX deklariert) |
| intptr_t | Max. Breite, um den Wert eines Zeigers zu speichern |
Zu allen diesen Ganzzahltypen gibt es jeweils einen unsigned-Bruder, der nur ein u vorangestellt hat (z.B. uintptr_t). Die maximalen und minimalen Limits dieser Ganzzahltypen sind ebenfalls in der Headerdatei <stdint.h> deklariert.
23.8.3 Komplexe Gleitpunkttypen
Zur Darstellung von komplexen und imaginären Zahlen wurden mit ANSI C99 weitere Gleitpunkttypen eingeführt. Die komplexe Zahl wird dabei mit dem Real- und Imaginärteil dargestellt, und zwar als float, double oder long double-Wert. Die Schreibweise dieser Gleitpunkttypen sieht wie folgt aus:
23.8.4 <iso646.h> - Symbolische Konstanten für Operatoren
In der Headerdatei <iso646.h> befinden sich einige symbolische Konstanten, welche Sie als Alternativen zu einigen Operatoren nutzen können. Hierzu eine Tabelle mit der jeweiligen symbolischen Konstante:
| Konstante in <iso646.h> | Operator |
| and | && (logisches UND) |
| or | || (logisches ODER) |
| not | ! (logisches NICHT) |
| bitand | & (bitweises UND) |
| bitor | | (bitweises ODER) |
| xor | ^ (bitweises Exklusiv-ODER) |
| compl | ~ (bitweises NICHT) |
| and_eq | &= (bitweises UND mit Zuweisung) |
| or_eq | |= (bitweises ODER mit Zuweisung) |
| xor_eq | ^= (bitweises Exklusiv-ODER mit Zuweisung) |
| not_eq | != (logisches NICHT mit Zuweisung) |
23.8.5 Deklaration von Bezeichnern
Neu beim ANSI C99-Standard ist, dass die Deklaration im Anweisungsblock frei platziert werden kann und nicht mehr am Anfang des Blocks erfolgen muss.
23.8.6 inline-Funktionen
inline-Funktionen sind dem C++-Programmierer ja bereits wohlbekannt und stehen mit dem C99-Standard auch dem C-Programmierer zur Verfügung. Um eine Funktion als inline-Funktion anzugeben, muss dieser nur das Schlüsselwort inline vorangestellt werden:
#include <stdio.h>
inline void xchange(int *z1, int *z2)
{
int tmp;
tmp = *z2;
*z2 = *z1;
*z1 = tmp;
}
inline void print(int *z1, int *z2)
{
printf("%d :: %d\n", *z1, *z2);
}
int main()
{
int zahl1 = 123, zahl2 = 321;
print(&zahl1, &zahl2);
xchange(&zahl1, &zahl2);
print(&zahl1, &zahl2);
return 0;
}
inline-Funktionen stellen eine sinnvolle Alternative zu parametrisierten define-Makros dar. Der Code der inline-Funktion wird vom Compiler direkt an der stelle eingefügt, an der der Aufruf stattfindet. Damit entfällt der Sprung in ein Unterprogramm, das heißt, der Aufwand mit dem Stack entfällt.
Die inline-Funktionen sollten aber möglichst klein gehalten werden. Werden zu viele Anweisungen verwendet, kann der Compiler das Schlüsselwort inline auch ignorieren und es als eine normale Funktion behandeln.
23.8.7 Vordefinierte Makros
Außer den bisher bekannten vordefinierten Makros, wie __LINE__, __FILE__, __DATE__, __TIME__ und __STDC__, sind weitere drei Makros hinzugekommen.
| Makro | Bedeutung |
| __func__ | Gibt den Namen der Funktion aus, in der dieses Makro verwendet wird. |
| __STD_HOSTED__ | Wenn es sich um eine Hosted-Implementierung der Standardbibliothek handelt, ist diese Konstante 1, ansonsten 0. |
| __STD_VERSION__ | Wenn der ANSI C99 Standard unterstützt wird, ist diese Konstante 199901L (1999 Januar). |
Ein Beispiel mit dem Makro __func__, welches sich prima zum Debuggen von Programmen eignet.
#include <stdio.h>
void eine_funktion()
{
printf("Name der Funktion: %s\n",__func__);
}
int main()
{
eine_funktion();
return 0;
}
23.8.8 <math.h> - Neue Funktionen
Enorm erweitert wurde die Headerdatei <math.h>. Darin befinden sich jetzt noch mehr Funktionen und Makros als zuvor. Zu allen Funktionen, die Sie bereits kennen und denen, welche Sie in diesem Abschnitt noch kennen lernen, wurden Versionen herausgebracht, die jetzt auch für die Datentypen float und long double anwendbar sind. Bisher waren die Funktionen dieser Headerdatei ja nur mit double angegeben. Um die für den Datentyp passende Funktion zu verwenden, müssen Sie nur ein entsprechendes Suffix notieren. f steht für float, l für long double und keine Angabe steht - wie gehabt - für double-Gleitpunktzahlen. Als Beispiel die Funktion sqrt():
float sqrtf(float zahl); double sqrt(double zahl); long double sqrtl(long double zahl);
In der folgenden Tabelle finden Sie einige Funktionen, die neu in die Headerdatei <math.h> hinzugekommen sind. Zu all diesen Funktionen gibt es auch schon verschiedene Versionen. Es hängt davon ab, welches Suffix Sie verwenden.
| Funktionen | Bedeutung |
| double round (double); double trunc (double); double rint (double x) |
Funktionen zum Runden von Zahlen |
| double fmax (double, double); double fmin (double, double); |
Maximum, Minimum |
| double log2 (double _x); double logb (double); |
Logarithmus |
| double copysign (double, double); | Vorzeichen kopieren |
| double scalb (double, long); extern double fma (double,double, double); |
Laufzeitoptimierte Berechnungen |
| double hypot (double, double); | Wurzel |
Sehr interessant dürften die Makros für den Vergleich von Gleitpunktzahlen sein, welche ebenfalls hinzugekommen sind:
| Makro | Bedeutung |
| isgreater(x, y) | x größer als y |
| isgreaterequal(x, y) | x größer als oder gleich y |
| isless(x, y) | x kleiner als y |
| islessequal(x, y) | x kleiner als oder gleich y |
| islessgreater(x, y) | x kleiner als y ODER x größer als y |
| isunordered(x, y) | Sind x und y nicht miteinander vergleichbar, gibt dieses Makro 1 zurück, ansonsten 0 |
Ein weiteres interessantes Feature sind Makros zu Bestimmung der Kategorie von Gleitpunktzahlen. In ANSI C werden die Gleitpunktzahlen in folgende fünf Kategorieren unterteilt (Konstanten aus der Headerdatei <math.h>):
| Konstante | Kategorie |
| FP_NAN | NAN steht für Not a Number und bedeutet, dass es sich bei dem Wert um keine gültige Gleitpunktdarstellung handelt. |
| FP_NORMAL | Eine Gleitpunktzahl in normaler Darstellung |
| FP_INFINITE | Die Gleitpunktzahl wird als unendlicher Wert dargestellt. |
| FP_ZERO | Eine Gleitpunktzahl mit dem Wert 0 |
| FP_SUBNORMAL | Eine Gleitpunktzahl, mit der besonders kleine Zahlen dargestellt werden können |
Abfragen, in welche Kategorie eine bestimmte Gleitpunktzahl fällt, können Sie mit den folgenden Makros vornehmen:
| Makro | Bedeutung |
| isnan(x) | Ist die Zahl gleich FP_NAN, wird 1 zurückgegeben, ansonsten 0. |
| isnormal(x) | Ist die Zahl gleich FP_NORMAL, wird 1 zurückgegeben, ansonsten 0. |
| isfinite(x) | Ist die Zahl eine Unendliche, wird 1 zurückgegeben, ansonsten 0. |
| isinf(x) | Ist die Zahl gleich FP_INFINITE, wird 1 zurückgegeben, ansonsten 0. |
Intern werden alle diese Makros jedoch zum Teil mit Hilfe des Makros fpclassify() ausgewertet:
fpclassify(x) == FP_INFINITE //isinf(x) fpclassify(x) == FP_NORMAL //isnormal(x)
23.8.9 Zusammenfassung
Dies soll es nun gewesen sein bezüglich der Erweiterungen des neuen ANSI C99-Standards. Es gibt noch einige mehr, die in diesem Abschnitt aber nicht angesprochen wurden.
Einige Leser werden sich wohl fragen: Wo bleibt denn der neue ANSI C99-Standard? Immerhin sind jetzt schon einige Jahre vergangen, und es gibt immer noch keinen Compiler, der dem ANSI C99-Standard vollständig entspricht. Der gcc-Compiler wird diesen Standard wohl in ferner Zukunft erfüllen, aber bei den Compilern für Microsoft-Systeme sind in dieser Hinsicht keine Ansätze zu sehen. Es hat also den Anschein, dass sich der C99-Standard nicht mehr - wie noch der C89-Standard - auf den verschiedensten Systemen und Compilern flächendeckend verbreiten wird.