In diesem Kapitel werden die Themen Strukturen, Zeiger und dynamische Speicherverwaltung vermischt. Was auf dem ersten Blick ein wenig kompliziert aussieht und es auch manchesmal ist, erweist sich, sobald man es beherrscht, als eine enorme Erleichterung. Man könnte auch soweit gehen und sagen: Wer dieses Kapitel beherrscht wird mit C kaum noch Probleme bekommen. C C++ C/C++ Dynamische Datenstrukturen Liste verkettete Listen lineare Liste Stack Queue LIFO FIFO Dynamische Datenstrukturen - Listen verkettete Listen lineare Liste Stack Queue LIFO FIFO Kapitel 22: Zeitroutinen

In der C-Standard-Bibliothek sind einige Funktionen enthalten, mit denen Sie die Zeit bestimmen können. Die Zeit umfasst dabei das Datum und die Uhrzeit.

22.1. Die Headerdatei <time.h>            zurück  Ein Kapitel tiefer  zum Inhaltsverzeichnis

Es folgen einige Standardfunktionen der Headerdatei <time.h>, in denen Routinen für Zeit und Datum deklariert sind. Dazu ein kurzer Überblick über die speziellen (primitiven) Datentypen in dieser Headerdatei und ihre Bedeutungen:

Typ Bedeutung
size_t arithmetischer Datentyp für Größenangaben
clock_t arithmetischer Datentyp für CPU-Zeit
time_t arithmetischer Datentyp für Datum- und Zeitangabe
struct tm enthält alle zu einer Kalenderzeit (gregorianische) relevanten Komponenten

Tabelle 22.1: (Primitive) Datentypen und Struktur für Datum und Zeit

Laut ANSI C-Standard sollten in der Struktur tm folgende Komponenten enthalten sein:
struct tm-Variable Bedeutung
int tm_sec; Sekunden (0-59)
int tm_min; Minuten (0-59)
int tm_hour; Stunden (0-23)
int tm_mday; Monatstag (1-31)
int tm_mon; Monate (0-11) (Januar = 0)
int tm_year; ab 1900
int tm_wday; Tag seit Sonntag (0-6) (Sonntag =0)
int tm_yday; Tag seit 1. Januar (0-365) (1.Januar =0)
int tm_isdst; Sommerzeit (tm_isdst > 0)Winterzeit (tm_istdst == 0)nicht verfügbar (tm_isdst < 0)

Tabelle 22.2: Bedeutung der Strukturvariablen in struct tm

Auf Linux-Systemen sind außerdem noch folgende Komponenten vorhanden:

long int tm_gmtoff;

tm_gmtoff gibt die Sekunden östlich von UTC bzw. den negativen Wert westlich von UTC für die Zeitzonen an. UTC steht für Universal Time Clock und dient als Bezeichnung für eine auf der gesamten Erde einheitliche Zeitskala. Die Universal Time ist identisch mit der Greenwich Mean Time (GMT). Diese Angabe kann aber auch unter

long int __tm_gmtoff

vorliegen. Ebenfalls nur bei Linux ist folgende Komponente enthalten:

const char *tm_zone;

Diese Variable enthält den Namen der aktuellen Zeitzone. Diese kann auch in folgender Schreibweise gegeben sein:

const char *__tm_zone;

22.1.1 Konstanten in der Headerdatei <time.h>
Folgende zwei Konstanten sind in der Headerdatei <time.h> deklariert:

22.1.2 Datums - und Zeitfunktionen in <time.h>
Die Zeit, mit welcher der Systemkern arbeitet, ist die Anzahl vergangener Sekunden, die seit 1.Januar 1970, 00:00:00 Uhr vergangen sind. Diese Zeit wird immer mit dem Datentyp time_t dargestellt und enthält das Datum und die Uhrzeit. Diese Zeit kann mit der Funktion

time_t time(time_t *zeitzeiger);

ermittelt werden. Wird für den Parameter zeitzeiger kein NULL-Zeiger verwendet, befindet sich an dieser Adresse die aktuelle Systemzeit. Hierzu ein kleines Listing, das die Zeit in Sekunden fortlaufend seit dem 1.Januar 1970 um 00:00:00 Uhr mithilfe der Funktion time() ausgibt:

#include <stdio.h>
#include <time.h>

#ifdef  __unix__
   #define clrscr() printf("\x1B[2J")
#else
   #include <stdlib.h>
   #define clrscr() system("cls")
#endif

int main()
{
   time_t t;
   time(&t);
   while(1)
      {
         clrscr();
         printf("%ld\n",t);
         printf("Mit <STRG><C> bzw. <STRG><D> beenden!! ");
         time(&t);
      }
   return 0;
}

Nach dem "Jahr 2000"-Problem steht bis zum Jahre 2038 das nächste Problem an, sollte bis dahin noch mit 32-Bit-Rechnern gearbeitet werden. Mittlerweile steht - beim Schreiben dieses Textes - die Sekundenzahl bei etwa einer Milliarde. time_t ist als long implementiert. Es gibt also Platz für etwa 2 Milliarden Sekunden. Sicherlich wird dieses Problem wieder im letzten Moment angegangen.

localtime() und gmtime() - Umwandeln von time_t in struct tm
Die Ausgabe der Sekunden als Zeitformat ist nicht gerade originell. Sie könnten jetzt anfangen, Funktionen zu schreiben, mit denen der Rückgabewert der Funktion time() in ein entsprechendes Format umgerechnet wird. Oder Sie verwenden bereits geschriebene Standardfunktionen wie:

struct tm *localtime(time_t *zeitzeiger);
struct tm *gmtime(time_t *zeitzeiger);

Beide Funktionen liefern als Rückgabewert die Adresse einer Zeitangabe vom Typ struct tm. Diese Struktur wurde bereits zu Beginn dieses Kapitels behandelt. Die Funktion localtime() wandelt die Kalenderzeit der Adresse time_t *zeitzeiger in lokaler Ortszeit um - unter der Berücksichtigung von Sommer- und Winterzeit. gmtime() dagegen wandelt die Kalenderzeit in die UTC-Zeit um.

Hierzu ein Beispiel, welches die Eingabe eines Geburtsdatums erwartet und anschließend das Alter in Jahren, Monaten und Tagen ausgibt:

#include <stdio.h>
#include <time.h>

struct tm *tmnow;

long today(void)
{
   time_t tnow;

   time(&tnow);
   tmnow = localtime(&tnow);
   printf("Heute ist der ");
   printf("%d.%d.%d\n",tmnow->tm_mday,
                       tmnow->tm_mon + 1,
                       tmnow->tm_year + 1900);
   return 1;
}

int main()
{
   int tag,monat,jahr;
   unsigned int i=0,tmp;
   printf("Bitte gib Deinen Geburtstag ein!\n");
   printf("Tag : ");
   scanf("%d",&tag);
   printf("Monat : ");
   scanf("%d",&monat);
   printf("Jahr (jjjj) : ");
   scanf("%d",&jahr);

   today();

   if(tmnow->tm_mon < monat)
      {
         i=1;
         tmp=tmnow->tm_mon+1-monat;
         monat=tmp+12;
      }
   else
      {
         tmp=tmnow->tm_mon+1-monat;
         monat=tmp;
      }
   printf("Sie sind %d Jahre %d Monate %d Tage alt\n",
           tmnow->tm_year+1900-jahr-i,monat, tmnow->tm_mday-tag);
   return 0;
}

Abbildung 22.1: Verwendung der Funktion localtime()
Abbildung 22.1: Verwendung der Funktion localtime()

Eine Anmerkung zur if-else-Bedingung im Programm. Diese war erforderlich, damit im Monatsdatum kein negativer Wert zurückgegeben wird, und Sie nicht auf einmal 1 Jahr älter sind.

mktime() - Umwandeln von struct tm zu time_t
Jetzt zum Gegenstück der Funktionen localtime() und gmtime():

time_t mktime(struct tm *zeitzeiger);

Auf diese Weise wird eine Zeit im struct tm-Format wieder umgewandelt in eine Zeit im time_t-Format. Ist die Kalenderzeit nicht darstellbar, gibt diese Funktion -1 zurück. Die echten Werte der Komponenten tm_yday und tm_wday in zeitzeiger werden ignoriert. Die ursprünglichen Werte der Felder, tm_sec, tm_min, tm_hour, tm_mday und tm_mon, sind nicht auf den durch die tm-Struktur festgelegten Bereich beschränkt. Befinden sich die Felder nicht im korrekten Bereich, werden diese angepasst.

Das heißt konkret: Wird z.B. das Datum 38.3.2001 eingegeben, muss die Funktion mktime() dieses Datum richtig setzen. Bei richtiger Rückgabe erhalten Sie entsprechende Werte für tm_yday und tm_wday. Der zulässige Bereich für die Kalenderzeit liegt zwischen dem 1. Januar 1970 00:00:00 und dem 19. Januar 2038 03:14:07.

Ein Beispiel soll zeigen, wie Sie den genauen Wochentag durch diese Funktion ermitteln können:

#include <stdio.h>
#include <time.h>

char *wday[] = {
            "Sonntag", "Montag", "Dienstag", "Mittwoch",
            "Donnerstag", "Freitag", "Samstag","??????"
               };

int main(void)
{
   struct tm time_check;
   int year, month, day;

   /*Jahr, Monat und Tag eingeben zum
     Herausfinden des Wochentags*/
   printf("Jahr : ");
   scanf("%d", &year);
   printf("Monat: ");
   scanf("%d", &month);
   printf("Tag  : ");
   scanf("%d", &day);

   /*Wir füllen unsere Struktur struct tm time_check
     mit Werten*/
   time_check.tm_year = year - 1900;
   time_check.tm_mon = month - 1;
   time_check.tm_mday = day;

   /* 00:00:01 Uhr*/
   time_check.tm_hour = 0;
   time_check.tm_min = 0;
   time_check.tm_sec = 1;
   time_check.tm_isdst = -1;

   if(mktime(&time_check) == -1)
      time_check.tm_wday = 7;  /*==Unbekannter Tag*/

   /* Der Tag des Datums wird ausgegeben */
   printf("Dieser Tag ist/wahr ein %s\n",
                             wday[time_check.tm_wday]);
   return 0;
}

asctime() und ctime() - Umwandeln von Zeitformaten in einen String
Mit zwei Funktionen können die beiden Zeitformen struct tm und time_t in einen String konvertiert werden. Hier die Syntax der beiden:

char *asctime(struct tm *zeitzeiger);
char *ctime(time_t *zeitzeiger);

Auch dazu ein kleines Beispiel in einem Listing:

#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
   FILE *datei;
   time_t time1;
   struct tm *time2;
   char zeit[25];
   int c;

   if(argc<2)
      {
         printf("Bitte Eingeben : %s textdatei.txt\n",*argv);
         exit(0);
      }
   if((datei = fopen(*++argv,"w+")) == NULL)
      {
         printf("Konnte Datei : %s nicht öffnen!!!!\n",*argv);
         exit(0);
      }
   printf("Eingabe machen (mit '#' beenden)\n>");
   /*Wir schreiben in unsere Datei und beenden diese
     mit dem Zeichen # */
   while((c=getchar()) != '#')
      putc(c, datei);
   putc('\n', datei);

   /*Zuerst time_t-Format*/
   time(&time1);
   printf("Heute ist %s und Sie haben eben die "
          "Datei %s geschlossen\n",ctime(&time1),*argv);

   /*Jetzt struct tm-Format mit asctime() */
   time1=time(NULL);
   time2=localtime(&time1);
   strcpy(zeit,asctime(time2));

   /* Das Datum schreiben wir in die Datei...*/
   fprintf(datei,"%s\n",zeit);
   fclose(datei);
   return 0;
}

Dieses Listing gibt zum einen das heutige Datum mit der Funktion ctime() auf dem Bildschirm aus und schreibt den Rückgabewert der Funktion asctime() in eine Textdatei.

difftime() - Differenz zweier Zeiten
Wird eine Differenz zwischen zwei Zeiten benötigt, lässt sich dies mit der folgenden Funktion ermitteln:

double difftime(time_t zeit1, time_t zeit0); 

Diese Funktion liefert die Differenz von zeit1 minus zeit0 als double-Wert zurück. Hierzu ein einfaches und kurzes Beispiel:

#include <stdio.h>
#include <time.h>

int main()
{
   time_t start, stop;
   double diff;
   printf("Einen Augenblick bitte ...\n");
   start=time(NULL);

   while((diff=difftime(stop=time(NULL),start)) !=5);
   printf("%.1f sek. vorbei!!\n",diff);
   return 0;
}

Das Programm wartet fünf Sekunden, bis es einen entsprechenden Text ausgibt. Bei

while((diff=difftime(stop=time(NULL),start)) !=5);  

wurde die Funktion time() gleich in der Funktion difftime() ausgeführt. Natürlich ist dies nicht so gut lesbar, aber es erfüllt denselben Zweck wie:

while((diff=difftime(stop,start)) !=5)
   stop=time(NULL);

clock() - Verbrauchte CPU-Zeit für ein Programm
Eine weitere häufig gestellte Frage lautet: Wie kann ich herausfinden, wie lange das Programm schon läuft? Sie können dies mit folgender Funktion herausfinden:

clock_t clock();

Diese Funktion liefert die verbrauchte CPU-Zeit seit dem Programmstart zurück. Falls die CPU-Zeit nicht verfügbar ist, gibt diese Funktion -1 zurück. Wenn Sie die CPU-Zeit in Sekunden benötigen, muss der Rückgabewert dieser Funktion durch CLOCKS_PER_SEC dividiert werden. Beispiel:

#include <stdio.h>
#include <time.h>

int main()
{
   clock_t prgstart, prgende;
   int c;

   prgstart=clock();

   printf("Bitte geben Sie irgendetwas ein und "
          "beenden Sie mit '#'!!\n");
   printf("\n > ");

   while((c=getchar())!= '#')
      putchar(c);

   prgende=clock();
   printf("Die Programmlaufzeit betrug %.2f Sekunden\n",
            (float)(prgende-prgstart) / CLOCKS_PER_SEC);
   return 0;
}

Abbildung 22.2: Verbrauchte Zeit eines Programms mit clock() ermitteln
Abbildung 22.2: Verbrauchte Zeit eines Programms mit clock() ermitteln

Damit dürfte es Ihnen nicht schwer fallen, die Nutzungsdauer eines Programms herauszubekommen.

strftime() - Umwandeln von struct tm in einen benutzerdefinierten String
Als Nächstes folgt eine Funktion, welche Sie als die sprintf()-Funktion für Zeit- und Datumswerte ansehen können. Die Syntax lautet:

size_t strftime(char *puffer, int maxzeichen,
                const char *format, struct tm *zeitzeiger);

So kann die Kalenderzeit aus struct tm *zeitzeiger in ein entsprechendes Format in die Adresse puffer geschrieben werden. Folgende Umwandlungsvorgaben können Sie dabei verwenden:

Format …wird ersetzt durch … Beispiel
%a Wochenname (gekürzt) Sat
%A Wochenname (ausgeschrieben) Saturday
%b Monatsname (gekürzt) Jan
%B Monatsname (ausgeschrieben) January
%c Entsprechende lokale Zeit- und Datumsdarstellung Sat Jan 22 22:22:22 MET 2003
%d Monatstag (1-31) 22
%H Stunde im 24-Stunden-Format (0-23) 23
%I Stunde im 12-Stunden-Format (1-12) 5
%j Tag des Jahres (1-366) 133
%m Monat (1-12) 5
%M Minute (0-59) 40
%p AM- oder PM-Zeitangabe; Indikator für das 12-Stunden-Format (USA) PM
%S Sekunden (0-69) 55
%U Wochennummer (0-53) (Sonntag als erster Tag der Woche) 33
%w Wochentag (0-6, Sonntag = 0) 3
%W Wochennummer (0-53) (Montag als erster Tag der Woche) 4
%x lokale Datumsdarstellung 02/20/02
%X lokale Zeitdarstellung 20:15:00
%y Jahreszahl (ohne Jahrhundertzahl 0-99) 01 (2001)
%Y Jahreszahl (mit Jahrhundertzahl YYYY) 2001
%Z, %z Zeitzone (gibt nichts aus, wenn Zeitzone unbekannt) MET
%% Prozentzeichen %

Tabelle 22.3: Formatierungszeichen für eine benutzerdefinierte Zeitangabe

Auch hierzu ein Listing, wie diese Angaben verwendet werden können:

#include <stdio.h>
#include <time.h>

int main()
{
   struct tm *zeit;
   time_t sekunde;
   char string[80];

   time(&sekunde);
   zeit = localtime(&sekunde);
   strftime(string, 80,
   "Es ist %H Uhr und %M Minuten (%Z) %A, %B %Y",zeit);
   printf("%s\n",string);
   return 0;
}

Es wird übrigens empfohlen, bei der formatierten Zeitausgabe des Jahres %Y statt %y zu verwenden, um Probleme mit dem Datum ab dem Jahr 2000 zu vermeiden.

Die folgende Grafik soll alle Funktionen, die Sie hier kennen gelernt haben, anhand ihrer Beziehungen zueinander zusammenfassen:

Abbildung 22.3: Datums- und Zeitfunktionen im Überblick
Abbildung 22.3: Datums- und Zeitfunktionen im Überblick

22.2. Laufzeitmessungen (Profiling)            zurück  Ein Kapitel höher  zum Inhaltsverzeichnis

Für kleinere Programme können Sie eine Laufzeitmessung mit der Funktion clock() vornehmen. Für größere und umfangreiche Projekte ist diese Funktion aber weniger geeignet. Bei solch speziellen Fällen sollten Sie extra Programme einsetzen, die für diese geschrieben wurden, so genannte Profiler.

Ein Profiler ist ein eigenständiges Programm, welches Sie zur Laufzeitanalyse verwenden können. Der Vorteil dieses Werkzeugs ist, dass Sie mit ihm auch einzelne Funktionen analysieren können. So lässt sich schnell herausfinden, welcher Teil des Quellcodes mehr Zeit als gewöhnlich beansprucht. Ein Profiler ist ebenfalls ein Standardwerkzeug für Codetuning-Freaks.

Bei den kommerziellen Entwicklungsumgebungen ist der Profiler im Normalfall mit dabei. Es gibt aber auch einen kostenlosen Kommandozeilen-Profiler, den GNU-Profiler gprof, der für alle gängigen Systeme erhältlich ist.

Es ist nicht die Aufgabe dieses Buchs, Ihnen die Werkzeuge der Programmierung näher zu erläutern, dennoch soll kurz auf die Verwendung des Profilers gprof unter Einsatz des gcc-Compilers eingegangen werden. Für andere Profiler lesen Sie bitte die jeweilige Dokumentation der Online-Hilfe.

Als Erstes benötigen Sie einen fehlerfreien Quellcode, den Sie analysieren wollen. Dann müssen Sie den Quellcode mit dem Compilerschalter -pg übersetzen:

gcc -pg programmname.c 

Jetzt befindet sich im Verzeichnis eine Datei namens "a.out" (unter Windows/MS-DOS auch "a.exe"). Diese Datei ist die ausführbare Datei für Ihren Quellcode. Starten Sie jetzt das ausführbare Programm "a.out". Nun werden die Profiling-Informationen in die Datei "gmon.out" geschrieben, die sich jetzt ebenfalls im Verzeichnis befindet. Nach Programmende können Sie gprof zur Auswertung der Datei "gmon.out" aufrufen. Die Ausgabe, welche häufig etwas länger ist, leiten Sie am besten in eine Datei um:

gprof ./a.out > test_prof.txt 

Die Textdatei "test_prof.txt" können Sie jetzt mit einem Editor Ihrer Wahl öffnen. Diese Datei beinhaltet wiederum zwei Dateien. Der erste Teil nennt

Im zweiten Teil befindet sich die Verteilung der Rechenzeit von Funktionen, auf die von ihnen aufgerufenen Unterfunktionen. Mehr zum Werkzeug gprof erfahren Sie in der entsprechenden Dokumentation.

Weiter mit 23. Weitere Headerdateien und deren Funktionen (ANSI C)            zum Inhaltsverzeichnis