Funktionen und Programmstruktur

| ### Programmieren in C von Prof. Dr. Rainer Thomas

Funktionen und Programmstruktur

### 7.1 Funktionsaufruf und Parameterübergabe
7.2 Funktionswert und "return"-Anweisung
7.3 Speicherklassen
7.4 Blockstruktur
7.5 Rekursive Funktionen
7.6 ANSI-C-Standartbibliothek
7.7 Externe ASM-Module in TURBO/BORLAND-C (C-Assembler-Interface)

## 7.1 Funktionsaufruf und Parameterübergabe

### Funktionen in C - Funktionsaufruf und Parameterübergabe

- Funktionsaufruf

- Syntax-Diagramm

- Ein Funktionsaufruf erfolgt stets als Operand in einem Ausdruck.
Auch ein alleiniger Funktionsaufruf ohne Verbindung mit anderen Operanden stellt einen Ausdruck dar.
Mit einem abschließenden ';' wird daraus eine Ausdrucksanweisung.

z.B. `printf("Hallo !\n"); /* eine Ausdrucksanweisung ! */`

In diesem Fall wird der Funktionswert ignoriert.
Funktionen, die keinen Wert zurückgeben (Funktionen vom Typ void), lassen sich nur in dieser Form der Ausdrucksanweisung anwenden.

- Die Anzahl der aktuellen Parameter muß mit der Anzahl der formalen Parameter übereinstimmen.
(Ausnahme : Funktionen mit variabler Parameteranzahl)
- Der Typ eines aktuellen Parameters muß mit dem Typ des korrespondierenden formalen Parameters mit folgenden Kompatibilitätsausnahmen übereinstimmen :
mit Function Prototype :

- Es besteht Kompatibilität entsprechend den Regeln der impliziten Typumwandlung bei Ausdrücken
(Umwandlung des aktuellen Parameterwerts in den Typ des formalen Parameters)

z.B. darf ein aktueller int-Wert für einen formalen double-Parameter übergeben werden

ohne Function Prototype :

- da char- und short-Werte als int-Werte übergeben werden, sind char, short und int miteinander kompatibel
- da float-Werte als double-Werte übergeben werden, sind float und double miteinander kompatibel
- die Übergabe eines aktuellen int-Wertes für einen formalen double-Parameter führt dagegen zu einem Fehler

- Durch den Compiler kann eine Parameteranzahl und -typ-Überprüfung nur erfolgen, wenn Function Prototypes (ANSI-C) vorhanden sind.

- Parameterübergabe

- Die Parameterübergabe erfolgt in C immer als Wertübergabe (Call by Value). --> aktuelle Parameter sind Ausdrücke.
Die Übergabe von Array-Parametern erscheint als Namensübergabe (Call by Reference).
Tatsächlich stellt ein Array-Name aber eine Adreßkonstante dar, so daß die Adresse des 1. Array-Elementes als Wert übergeben wird.
- Die Reihenfolge der Auswertung der aktuellen Parameter muß nicht mit der Auflistungsreihenfolge der Parameter in der Parameterliste über- einstimmen.

--> | Die Ausnutzung von Seiteneffekten in den aktuellen Parameterausdrücken sollte unterbleiben. z.B. `c=power(a=2,a+3);` Abhängig von der vom Compiler verwendeten Auswertungsreihenfolge werden verschiedene Werte als aktuelle Parameter übergeben.

- Die Parameterübergabe erfolgt i.a. über den Stack
(es gibt auch Compiler, bei denen einige Parameter über Register übergeben werden, z.B. bei Atari-PURE-C).
- C-Konvention der Parameterübergabe (bei Stack-Übergabe) :

- der letzte Parameter in der Parameterliste wird als erster auf dem Stack abgelegt. --> Ablage von rechts nach links
- die Parameter werden durch die aufrufende Funktion vom Stack entfernt

- In einigen anderen Programmiersprachen (z.B. PASCAL) gilt eine andere Art der Parameterübergabe.
-->PASCAL-Konvention der Parameterübergabe :

- Ablage der Parameter in der Reihenfolge der Auflistung
(von links nach rechts)
- Entfernung der Parameter durch die aufgerufene Funktion

- Um die Erstellung gemischtsprachiger Programme zu ermöglichen, stellen einige C-Systeme (z.B. TURBO-C) die Modifizierer

pascal
und
cdecl

zu Verfügung.
Ein Modifizierer kann bei Funktionsdefinitionen und -deklarationen zusätzlich zwischen der Typangabe und dem Funktionsnamen angegeben werden.

Der Modifizierer pascal legt für die betreffende Funktion die PASCAL-Konvention der Parameterübergabe fest.
Diese Funktion kann in C oder in einer anderen Sprache geschrieben sein. z.B. `void pascal tsort(int *ifeld, unsigned laenge);`

Da mittels eines Compilerschalters ein gesamtes C-Modul gemäß der PASCAL-Konvention übersetzt werden kann, wurde der Modifizierer cdecl zur expliziten Festlegung der C-Konvention für einzelne Funktionen eingeführt (z.B. notwendig für Extern-Deklarationen von C-Bibliotheksfunktionen).

### Beispiel zur Parameterübergabe in C (hier: Turbo-C)

- C-Quellprogramm (Programm CADD)

    #include <stdio.h>

    void main(void)
    { int a=23,b=47,c;
      int add(int, int);
      c=add(a,b);
      printf("\n%d\n",c);
    }

    int add(int z1, int z2)
    { int z3;
      z3=z1+z2;
      return (z3);
    }

- Vom Compiler erzeugter Code (in ASM-Darstellung)

    _main   proc    near
            push    bp
            mov     bp,sp
            sub     sp,6                  ; Platz für lokale Variable
            mov     word ptr [bp-2],23    ; lokale Variable a
            mov     word ptr [bp-4],47    ; lokale Variable b
            push    word ptr [bp-4]       ; Parameter b (z2) --> Stack
            push    word ptr [bp-2]       ; Parameter a (z1) --> Stack
            call    near ptr _add
            pop     cx                    ; Entfernung Parameter
            pop     cx                    ; vom Stack
            mov     word ptr [bp-6],ax    ; Funktionswert --> c
            push    word ptr [bp-6]       ; 2. Par. für printf (c) --> Stack
            mov     ax,offset DGROUP:s@
            push    ax                    ; 1. Par. für printf --> Stack
            call    near ptr _printf
            pop     cx                    ; Entfernung Parameter
            pop     cx                    ; vom Stack
            mov     sp,bp
            pop     bp
            ret
    _main   endp

    _add    proc    near
            push    bp
            mov     bp,sp
            sub     sp,2                  ; Platz für lokale Variable z3
            mov     ax,word ptr [bp+4]    ; Zugriff zu Parameter z1
            add     ax,word ptr [bp+6]    ; Zugriff zu Parameter z2
            mov     word ptr [bp-2],ax    ; Additionsergebnis --> z3
            mov     ax,word ptr [bp-2]    ; Funktionswert --> AX
            mov     sp,bp
            pop     bp
            ret
    _add    endp

- Zugehöriger Stack

### Beispiel zur Parameterübergabe nach der PASCAL-Konvention (Turbo-C)

- C-Quellprogramm (Programm PADD)

    #include <stdio.h>

    void main(void)
    { int a=23,b=47,c;
      int pascal add(int, int);
      c=add(a,b);
      printf("\n%d\n",c);
    }

    int pascal add(int z1, int z2)
    { int z3;
      z3=z1+z2;
      return (z3);
    }

- Vom Compiler erzeugter Code (in ASM-Darstellung)

    _main   proc    near
            push    bp
            mov     bp,sp
            sub     sp,6                  ; Platz für lokale Variable
            mov     word ptr [bp-2],23    ; lokale Variable a
            mov     word ptr [bp-4],47    ; lokale Variable b
            push    word ptr [bp-2]       ; Parameter a (z1) --> Stack
            push    word ptr [bp-4]       ; Parameter b (z2) --> Stack
            call    near ptr ADD
                                          ; keine Parameterentfernung !
            mov     word ptr [bp-6],ax    ; Funktionswert --> c
            push    word ptr [bp-6]       ; 2. Par. für printf (c) --> Stack
            mov     ax,offset DGROUP:s@
            push    ax                    ; 1. Par. für printf --> Stack
            call    near ptr _printf
            pop     cx                    ; Entfernung Parameter
            pop     cx                    ; vom Stack
            mov     sp,bp
            pop     bp
            ret
    _main   endp

    ADD     proc    near
            push    bp
            mov     bp,sp
            sub     sp,2                  ; Platz für lokale Variable z3
            mov     ax,word ptr [bp+6]    ; Zugriff zu Parameter z1
            add     ax,word ptr [bp+4]    ; Zugriff zu Parameter z2
            mov     word ptr [bp-2],ax    ; Additionsergebnis --> z3
            mov     ax,word ptr [bp-2]    ; Funktionswert --> AX
            mov     sp,bp
            pop     bp
            ret     4                     ; Rückspr. u. Parameterentfernung
    ADD     endp

## 7.2 Funktionswert und "return"-Anweisung

### Funktionen in C - Funktionswert

- Eine Funktion kann mit der rufenden Funktion über 3 verschiedene Wege kommunizieren :

- über Parameter :

  rufende Fkt ----- Info ----> gerufene Fkt
  für Zeiger als Parameter :
rufende Fkt <---- Info ----> gerufene Fkt

- über den Funktionswert :

  rufende Fkt <---- Info ----- gerufene Fkt

- über globale Größen :

  rufende Fkt <---- Info ----> gerufene Fkt

- Die Erzeugung und Rückgabe eines Funktionswertes erfolgt mittels der return-Anweisung.

- Syntax-Diagramm

- Beispiel : `return a+b;`
- Hinweis : Der Ausdruck nach return muß nicht in runden Klammern stehen.
- Wirkung :

  Der Ausdruck wird ausgewertet und gegebebenfalls entsprechend den Regeln für die implizite Typumwandlung bei Ausdrücken in den definierten Typ des Funktionswertes umgewandelt.
Anschließend wird die Funktion beendet und in die rufende Funktion zurückgekehrt, wobei der - u.U. typgewandelte - Wert des Außdrucks an vorgesehener Stelle als Funktionswert übergeben wird.

- Eine Funktion kann auch mehrere return-Anweisungen enthalten
- Wenn der Ausdruck in der return-Anweisung fehlt, ist der Funktionswert undefiniert.
- In einer Funktion kann eine return-Anweisung auch ganz fehlen.
Die Funktion wird dann nach der letzten ausgeführten Anweisung beendet.
Auch in diesem Fall ist der Funktionswert undefiniert.
- Manche Compiler erzeugen bei undefiniertem Funktionswert den Wert 0.
- Funktionen mit undefiniertem Funktionswert sollten grundsätzlich als vom Typ void definiert werden (erst ab ANCI-C möglich !).
Dadurch kann eine versehentliche - zu Fehlern führende - Verwendung einer derartigen Funktion als Operand in einem Ausdruck, dessen Wert verwendet wird, vom Compiler festgestellt werden.
- Beispiel :

    void pr_reverse(char s[])
    { int i;
      for (i=strlen(s)-1; i>=0; i--) putchar(s[i]);
    }

### Programmbeispiel : Einfacher "Taschen"-Rechner

    /* ------------------------------------------------------------------ */
    /* Programm AUFADD                                                    */
    /* ------------------------------------------------------------------ */
    /* Einfacher "Taschenrechner"                                         */
    /* Addition zeilenweise eingegebener u.U. negativer Realzahlen,       */
    /* Ausgabe der jeweiligen Teilsumme nach jeder Eingabe,               */
    /* Ende bei leerer Eingabe                                            */
    /* ------------------------------------------------------------------ */

    #include <stdio.h>
    #define MAXCHAR 136

    void main(void)
    { double AzuF(char []);
      int GetLine(char [], int);
      double dSumme=0;
      char acZeile[MAXCHAR+1];
      while (printf("? "), GetLine(acZeile,MAXCHAR)>0)
        printf("= %f\n",dSumme+=AzuF(acZeile));
    }

    int GetLine(char acZl[],int iMax)   /* Einlesen einer zeile von stdin */
                                        /* nach acStr (ohne NL)           */
    { int iNextZeich, i=0;              /* Funktionswert=Zeilenlaenge     */
      while(--iMax>=0 && (iNextZeich=getchar())!=EOF && iNextZeich!='\n')
        acZl[i++]=iNextZeich;
      acZl[i]='\0';
      if (i==0 && iNextZeich==EOF) return(-1);
      else return (i);
    }

    double AzuF(char acStr[])                  /* string s --> double */
    { double dWert, dBruch=0, dSkalFakt=1;
      int iSign=1, i;
      for (i=0; acStr[i]==' ' || acStr[i]=='\t'; i++);
      if (acStr[i]=='-')
      { iSign=-1; i++; }
      else
        if (acStr[i]=='+') i++;
      for (dWert=0; acStr[i]>='0' && acStr[i]<='9'; i++)
        dWert=10*dWert+acStr[i]-'0';
      if (acStr[i]=='.') i++;
      for ( ; acStr[i]>='0' && acStr[i]<='9'; i++)
      { dBruch=dBruch*10+acStr[i]-'0';
        dSkalFakt*=10;
      }
      return iSign*(dWert + dBruch/dSkalFakt);
    }

    /* ------------------------------------------------------------------ */

Bildschirmausgabe :

    E:\RT\C\VORL>aufadd? 23.5
    = 23.500000
    ? +12.3
    = 35.800000
    -5.9
    = 29.900000
    ?

## 7.3 Speicherklassen

- Neben dem Typ kennt C als weiteres Attribut für statische Objekte (Variable und Funktionen) die Speicherklasse (storage class).
Die Speicherklasse legt fest :

- den Verfügbarkeitsbereich eines Objektes (scope)
- die Lebensdauer eines Objektes (duration)
- den Speicherort eines Objektes (nur für Variable)

- Verfügbarkeitsbereich (Geltungsbereich, scope)
Bereich eines Programms, in dem zu einem Objekt zugegriffen werden kann. In C werden 4 - hierarchisch zusammenhängende - Verfügbarkeitsbereiche unterschieden :

- | gesamtes Programm
- | Modul (Datei), das Objektdefinition enthält
- | Funktion, die Objektdefinition enthält
- | Verbundanweisung (Block), die Objektdefinition enthält
} | --> | globale Objekte
} | --> | lokale Objekte

Lokales Objekt :

Definition innerhalb eines Blockes (Funktion bzw. Verbundanw.). Lokale Objekte können nur Variable sein.

Globales Objekt :

Definition außerhalb jeder Funktion.
Funktionen sind immer global.

- Lebensdauer (duration)
Dauer der Zuordnung eines Speicherplatzes zu einem Objekt.
Man unterscheidet:

- gesamte Programmlaufzeit (fixed duration)
Der Speicherbereich wird dem Objekt zu Programmbeginn zugeordnet.
Die Zuordnung wird erst mit dem Programmende wieder aufgehoben
- Ausführungszeit des Blocks (Funktion bzw Verbundanweisung), in dem das Objekt definiert ist (automatic duration)
Der Speicherbereich wird dem Objekt erst zu Beginn der Ausführung des Blocks "automatisch" zugeordnet.
Mit dem Verlassen des Blocks wird die Zuordnung wieder aufgehoben.

Globale Objekte - also damit auch alle Funktionen - "leben" grundsätzlich während der gesamten Programmlaufzeit.
Lokale Objekte (immer Variable !) können sowohl eine Programmlaufzeit- Lebensdauer als auch eine auf die Block-Ausführungszeit begrenzte Lebensdauer haben.

- Speicherort

- normaler Arbeitsspeicher (Datensegment)
- "automatisch" nach Bedarf belegter Bereich im Arbeitsspeicher (Stack)
- CPU-Register

- Zur Kennzeichnung der Speicherklasse kennt C 4 Speicherklassen-Spezifizierer, die bei Definitionen bzw Deklarationen verwendet werden können:

- extern
- static
- auto
- register

- Ueberblick

- Eigenschaften der Speicherklassen

- Überblick über die Verwendung der &qout;storage class specifier"

*) nur mit Initialisierung

- Speicherklasse extern

Verfügbarkeitsbereich | : | gesamtes Programm
Lebensdauer | : | Programmlaufzeit
Speicherort | : | normaler Arbeitsspeicher (für Variable : Datensegment)

- Default-Speicherklasse für globale Verfügbarkeit.
Speicherklasse für

- alle Funktionen,
- alle außerhalb jeder Funktion definierten Variablen,

für die nicht explizit die Speicherklasse static definiert ist.
- Um Objekte, die in einem anderen Modul oder im gleichen Modul später definiert sind, verwenden zu können, sind entsprechende Deklarationen erforderlich.
- für Variable :

Default-Initialisierung | : | mit 0
explizite Initialisierung | : | mit konstantem Ausdruck (durch Compiler)

- Speicherklasse auto

Verfügbarkeitsbereich | : | Block, in dem definiert
Lebensdauer | : | Block-Ausführungszeit
Speicherort | : | Stack

- Default-Speicherklasse für lokale Verfügbarkeit.
Speicherklasse für

- alle Variablen, die innerhalb eines Blockes definiert sind und für die nicht explizit die Speicherklasse register oder static definiert ist,
- alle formalen Funktionsparameter, für die nicht explizit die Speicherklasse register definiert ist.

- Default-Initialisierung :

- undefiniert

explizite Initialisierung:

- zur Laufzeit bei Blockeintritt
- einfache Typen und Pointer durch beliebigen Ausdruck
- arrays, structures und unions durch konstanten Ausdruck

### Beispiel zur Extern-Deklaration

    /* ----------------------------------------------------------------- */
    /* Programm EXTDEM */
    /* ----------------------------------------------------------------- */
    /* Demonstrationsprogramm zur Extern-Deklaration von Variablen und   */
    /* Funktionen                                                        */
    /* ----------------------------------------------------------------- */

    /* ----------------------------------------------------------------- */
    /*                         Source-File #1                            */
    /* ----------------------------------------------------------------- */

    #include <stdio.h>

    void main(void)
    { extern int i;             /* Extern-Deklaration von i */
      void next(void);          /* Vorwärts-Deklaration von next() */
      printf("\n%d\n",++i);
      next();
    }

    int i=3;                    /* Definition von i mit Initialisierung */

    void  next(void)
    {                           /* Extern-Deklaration von i nicht notwendig */
      extern void other(void);  /* Extern-Deklaration von other() */
      printf("%d\n",++i);
      other();
    }

    /* ----------------------------------------------------------------- */

    /* ----------------------------------------------------------------- */
    /*                         Source-File #2                            */
    /* ----------------------------------------------------------------- */

    #include <stdio.h>

    extern int i;              /* Extern-Deklaration von i */

    void other(void)
    {                          /* Extern-Deklaration von i koennte auch
                                      hier stehen */
      printf("%d\n",++i);
    }

    /* ----------------------------------------------------------------- */

Bildschirmausgabe :

    E:\RT\C\VORL>extdem

    4
    5
    6

- Speicherklasse static

- Zwei Formen :

- static local (statisch-lokal) : auf lokales Objekt angewendet
- static global (statisch-global) : auf globales Objekt angewendet

- .

Verfügbarkeitsbereich | : | Block, in dem definiert; wenn statisch-lokal Modul,in dem definiert; wenn statisch-global
Lebensdauer | : | Programmlaufzeit
Speicherort | : | normaler Arbeitsspeicher (Datensegment)

- Statisch-lokale Variable behalten im Unterschied zu auto-Variablen zwischen zwei Ausführungen ihres Blockes (z.B. zwei Funktionsaufrufen) ihren Wert.
Eine Initialisierung erfolgt nur einmal und nicht bei jeder erneuten Ausführung des Blocks.
Daher sinnvoll anzuwenden für Variable, die zwischen zwei verschiedenen Aufrufen des zugehörigen Blocks ihren Wert behalten sollen, zudenen außerhalb des Blocks aber nicht zugegriffen werden darf.
- Statisch-globale Objekte (Variable oder Funktionen) ermöglichen es, Programmteile (Daten und Code) einerseits von gewissen Programmteilen gemeinsam zu nutzen, andererseits aber vor anderen Programmteilen zu verbergen.
Sehr sinnvol bei modularer Programmierung zur Vermeidung von Namenskonflikten und möglichen Seiteneffekten.
(Beispiele : Zahlenfolgegenerator, Speicherallokationsfunktionen)
- für Variable :
Default-Initialisierung : mit 0
explizite Initialisierung: mit konstantem Ausdruck (durch Compiler)

### Beispiel zu statisch-lokalen Variablen

    /* ------------------------------------------------------------------ */
    /* Programm LSTATDEM                                                  */
    /* ------------------------------------------------------------------ */
    /* Demonstrationsprogramm zu statisch-lokalen Variablen               */
    /* ------------------------------------------------------------------ */

    #include <stdio.h>

    void IncStatic(void)
    {
      static int i=0;
      printf("%d\n",++i);
    }

    void IncAuto(void)
    {
      int i=0;
      printf("%d\n",++i);
    }

    void main(void)
    {
      printf("\nIncrement statisch-lokale Variable:\n\n");
      IncStatic();
      IncStatic();
      IncStatic();
      printf("\nIncrement auto-Variable:\n\n");
      IncAuto();
      IncAuto();
      IncAuto();
    }

    /* ------------------------------------------------------------------ */

Bildschirmausgabe :

    E:\RT\C\VORL>lstatdem

    Increment statisch-lokale Variable:

    1
    2
    3

    Increment auto-Variable:

    1
    1
    1

### Beispiel zu statisch-globalen Variablen

    /*--------------------------------------------------------------------*/
    /*                           Source-File #1                           */
    /*--------------------------------------------------------------------*/
    /* Modul ZAHLFOLG                                                     */
    /*--------------------------------------------------------------------*/
    /* Initialisierung und Erzeugung von Werten einer Zahlenfolge         */
    /*--------------------------------------------------------------------*/

    #define OFFSET 23

    static int zahl;

    int zahl_next(void)       /* Erzeugung des naechsten Wertes der Folge */
    {
      zahl=zahl+OFFSET;
      return zahl;
    }

    void zahl_init(int start)     /* Festlegung des Startwerts der Folge */
    {
      zahl=start-OFFSET;
    }

    /*--------------------------------------------------------------------*/

    /*--------------------------------------------------------------------*/
    /*                           Source-File #2                           */
    /*--------------------------------------------------------------------*/
    /* Program GSTATDEM                                                   */
    /* -------------------------------------------------------------------*/
    /* Demonstrationsprogramm zu statisch-globalen Variablen              */
    /* Erzeugung einer Zahlenfolge                                        */
    /* Verwendung des Moduls ZAHLFOLG                                     */
    /* -------------------------------------------------------------------*/

    #include <stdio.h>

    void main(void)
    {
      extern void zahl_init(int);
      extern int zahl_next(void);

      int start, anzahl;

      printf("\nErzeugung einer Zahlenfolge\n");
      printf("\nStartwert und Anzahl ? ");
      scanf("%d%d", &start, &anzahl);
      zahl_init(start);
      for (;anzahl>0;anzahl--)
        printf("\n%d",zahl_next());
      putchar('\n');
    }

    /*--------------------------------------------------------------------*/

- Speicherklasse register

- .

Verfügbarkeitsbereich | : | Block, in dem definiert
Lebensdauer | : | Block-Ausführungszeit
Speicherort | : | CPU-Register (wenn möglich)

- Spezialfall der lokalen Verfügbarkeit:
Nur anwendbar auf innerhalb eines Blocks definierte Variable und aufformale Funktionsparameter.
- Ursprünglich (KuR-C) :

vorgesehen für char- und int-Variable zur Speicherung in CPU-Registern

ANSI-C :

Erweiterung der Anwendbarkeit auf Variable beliebigen Typs
Änderung der Bedeutung: "Speicherung so, daß ein schnellst- möglicher Zugriff möglich ist".

Praktisch aber weiterhin bevorzugt für char- und int-Variable (und u.U. Pointer-Variable) zur Speicherung in CPU-Registern verwendet.
- Es ist zulässig, daß ein Compiler die Speicherklassenangabe register ignoriert.
Die betreffenden Variablen sind dann von der Speicherklasse auto.
- Die Anzahl der tatsächlich in CPU-Register speicherbaren Variablen ist immer begrenzt.
In TURBO-C :

max. 2 register-Variable (in SI und DI) vom Typ char, short, int und Near Pointer

Werden zuviel Variable als register definiert, so wählt der Compiler die tatsächlich in CPU-Registern gespeicherten aus. Die übrigen be- kommen die Speicherklasse auto.
- Es ist nicht möglich von als register definierten Variablen eine Adresse zu verwenden (unabhängig davon, ob eine Speicherung tatsächlich in Registern erfolgt oder nicht).
- Manche Compiler plazieren lokale int- bzw char-Variable, die nicht als register definiert sind, deren Adresse aber nicht verwendet wird, von sich aus in Registern (falls noch Register hierfür frei sind).
- Die Speicherklasse register ist vor allem für Variable sinnvoll, zu denen häufig zugegriffen wird (z.B. für in Schleifen verwendete Variable).
- Default-Initialisierung :

- undefiniert

explizite Initialisierung :

- zur Laufzeit bei Blockeintritt
- einfache Typen und Pointer durch beliebigen Ausdruck
- arrays, structures und unions durch konstanten Ausdruck

### Beispiel zur Verwendung von register-Variablen

- Wirkung des Einsatzes einer register-Variablen als Schleifen-Kontroll- Variable (Schleifen-Variable)

    /* Programm RVTEST1          */

    /* Schleifenvariable hat die */
    /* Speicherklasse register   */

    #include <stdio.h>

    void main(void)
    { int r1=75;
      int r2=25;
      int a=0, b=r1+r2;
      register int i;
      for (i=1;i<=b;i++)
        a+=i;
      printf("\nErgebnis : %d\n",a);
    }

    _main  proc   near
           push   bp
           mov    bp,sp
           sub    sp,8
           push   si
           mov    word ptr [bp-2],75 ;r1
           mov    word ptr [bp-4],25 ;r2
           mov    word ptr [bp-6],0  ;a
           mov    ax,word ptr [bp-2]
           add    ax,word ptr [bp-4]
           mov    word ptr [bp-8],ax ;b
           mov    si,1               ;i
           jmp    short @1@114
    @1@58:

           add    word ptr [bp-6],si
           inc    si
    @1@114:

            cmp    si,word ptr [bp-8]
            jle    short @1@58

            push   word ptr [bp-6]
            mov    ax,offset DGROUP:s@
            push   ax
            call   near ptr _printf
            pop    cx
            pop    cx
            pop    si
            mov    sp,bp
            pop    bp
            ret
    _main  endp
    /* Programm RVTEST2          */

    /* Schleifenvariable hat die */
    /* Speicherklasse auto       */

    #include <stdio.h>

    void main(void)
    { int r1=75;
      int r2=25;
      int a=0, b=r1+r2;
      int i;
      for (i=1;i<=b;i++)
        a+=i;
      printf("\nErgebnis : %d\n",a);
    }

    _main  proc   near
           push   bp
           mov    bp,sp
           sub    sp,10

           mov    word ptr [bp-2],75 ;r1
           mov    word ptr [bp-4],25 ;r2
           mov    word ptr [bp-6],0  ;a
           mov    ax,word ptr [bp-2]
           add    ax,word ptr [bp-4]
           mov    word ptr [bp-8],ax ;b
           jmp    short @1@114
    @1@58:
           mov    ax,word ptr [bp-10]
           add    word ptr [bp-6],ax
           inc    word ptr [bp-10]
    @1@114:
           mov    ax,word ptr [bp-10]
           cmp    ax,word ptr [bp-8]
           jle    short @1@58

           push   word ptr [bp-6]
           mov    ax,offset DGROUP:s@
           push   ax
           call   near ptr _printf
           pop    cx
           pop    cx

           mov    sp,bp
           pop    bp
           ret
    _main  endp

## 7.4 Blockstruktur

- C-Programme weisen eine Blockstruktur auf.
Allerdings unterscheidet sich diese Blockstruktur teilweise von der Blockstruktur in PASCAL.
- Statische Blockstruktur

- Bezüglich Funktionen :
keine Blockschachtelung (Funktionen können nicht innerhalb von Funktionen definiert werden) nur nebeneinanderliegende gleichberechtigte Blöcke.
Auf der Quellcode-Ebene existiert auch kein übergeordneter "Hauptprogrammblock".
Dieser wird durch den vom C-System bereitgestellten und vom Linker hinzugebundenen Startup-Code gebildet.
- Bezüglich Variablen :
Da zu Beginn jeder Verbundanweisung (=Block) und damit auch zu Beginn jedes Funktionsrumpfes Variablendefinitionen stehen können ist eine beliebige Blockschachtelung möglich.
Jede Definition (allg. Vereinbarung) gilt nur innerhalb des Blocks, an dessen Anfang sie steht. --> blocklokale Größen
Es existiert auch ein übergeordneter "Hauptprogrammblock" --> globale Größen

Namenskonflikte (= gleiche Namen) zwischen Variablen in ineinandergeschachtelten Blöcken werden folgendermaßen gelöst:

- trotz gleichen Namens liegen unterschiedliche Variable vor.
- die im inneren Block definierte Variable hat "Vorrang".
--> im inneren Block kann nur die dort definierte Variable angesprochen werden.
Eine in einem äußeren Block definierte Variable existiert zwar, sie ist aber unsichtbar, d.h. sie kann nicht über ihren Namen ( wohl aber über Pointer) angesprochen werden.

- Dynamische Blockstruktur
Da Funktionen beliebige andere Funktionen aufrufen können existiert eine entsprechende beliebige dynamische Blockschachtelung auch für Funktionen.
Innerhalb dynamisch geschachtelter Funktionen richtet sich die Zugriffsmöglichkeit zu Variablen natürlich nach der statischen Blockstruktur

### Programmbeispiel zur Blockstruktur in C

    /* ------------------------------------------------------------------ */
    /* Programm BLOCKDEM                                                  */
    /* ------------------------------------------------------------------ */
    /* Beispiel zur Blockstruktur                                         */
    /* ------------------------------------------------------------------ */

    #include <stdio.h>

    int x=10;

    void main(void)
    {
      int x=20, i;
      void weiter(void);

      printf("\nmain   : x = %d\n\n",x);
      for (i=0; i<4; i++)
      { int x=30;
        static int y=30;
        printf("for    : x = %d   y = %d\n",x++,y++);
      }
      weiter();
    }

    void weiter(void)
    {
      int y=40;
      printf("\nweiter : x = %d   y = %d\n",x,y);
    }

    /* ------------------------------------------------------------------ */

Bildschirmausgabe :

    E:\RT\C\VORL>blockdem

    main   : x = 20

    for    : x = 30   y = 30
    for    : x = 30   y = 31
    for    : x = 30   y = 32
    for    : x = 30   y = 33

    weiter : x = 10   y = 40

## 7.5 Rekursive Funktionen

- C erlaubt den rekursiven Aufruf von Funktionen.
- Rekursion liegt vor, wenn die Definition eines Objektes unter Verwendung des Objekts selbst erfolgt.
Auf Funktionen (Algorithmen) bezogen bedeutet das, daß sich eine Funktion während ihrer Ausführung (ein Algorithmus während seiner Abarbeitung) selbst aufruft.
- Man unterscheidet :

- direkte Rekursion :

Der Funktionsaufruf ist direkt in der Funktion enthalten
(--> rekursive Funktion)

- indirekte Rekursion :

Der Funktionsaufruf erfolgt indirekt über wenigstens eine weitere Funktion.
Z.B. f() ruft b() auf und b() ruft ihrerseits f() auf.
(--> rekursiv verwendete Funktion)

- Eine Alternative zur Rekursion ist die Iteration.
Hierunter versteht man die wiederholte Ausführung einer Anweisungsfolge, wobei die Anzahl der Wiederholungen durch bestimmte Variable gesteuert wird.
- Bei jedem erneuten Aufruf (Inkarnation) einer rekursiven bzw rekursiv verwendeten Funktion werden die lokalen auto- und register-Variablen und die formalen Parameter neu angelegt, d.h. auf dem Stack wird für sie neuer Speicherplatz allokiert (bzw werden die alten register-Variablen auf dem Stack zwischengespeichert).
--> zu den verschiedenen Funktionsinkarnationen gehören verschiedene Variablen- und Parametersätze.
(Problem : Gefahr des Stack-Überlaufs)

Statisch-lokale Variable werden dagegen nicht jedesmal neu angelegt.
Sie existieren nur einmal und sind in allen Inkarnationen zugänglich.

- Eine rekursive bzw rekursiv verwendete Funktion wird erst beendet, wenn alle in ihr bzw durch sie erfolgten erneuten Funktionsaufrufe ihrerseits beendet worden sind.
Da jede sinnvolle Rekursion einmal enden muß (Zeit, Stack !) muß die Funktion eine Abbruchbedingung, die zu keinem erneuten Funktionsaufruf führt, enthalten.

Die Abbruchbedingung muß durch wenigstens eine Variable gesteuert werden, mit der Information von einer Funktionsinkarnation zur nächsten übergeben wird.
Hierfür läßt sich eine lokale statische Variable oder ein Parameter ein- setzen.

- Forderungen an eine rekursive bzw rekursiv verwendete Funktion :

- Die Abbruchbedingung muß bei jedem Funktionsaufruf überprüft werden
- Wenigstens eine der die Abbruchbedingung steuernden Variablen muß bei jedem erneuten Funktionsaufruf einen neuen Wert, im Sinne einer Annäherung an die Abbruchbedingung, bekommen.
- Die Abbruchbedingung muß nach einer sinnvollen Anzahl von Funktions- aufrufen erfüllt sein.

- Beispiel für einen rekursiven Algorithmus
Ermittlung der Fakultät fac(n) (n!)

- rekursive Definition :
`fac(0) = 1` `fac(n) = n * fac(n-1)`
- Formulierung als C-Funktion :

    unsigned fac(unsigned n)
    {
      if (n==0)     /* Abbruchbedingung */
      return(1);
      else
      return(n*fac(n-1));
    }

- Untersuchung der Funktionsaufrufe und Funktionswerte für fac(4) :

- Anwendung rekursiver Algorithmen

- Rekursive Algorithmen sind häufig leichter zu formulieren und klarer zu verstehen als entsprechende iterative Algorithmen.
- Trotzdem ist Rekursion nicht in jedem fall vorteilhafter. Häufig führt sie weder zu einem merklich geringeren Codeumfang noch zu einer besseren Speicherausnutzung (Stack !). Vielfach ist sogar die Laufzeit wegen der vielfachen Funktionsaufrufe länger.
- Andererseits lassen sich manche Probleme mittels eines rekursiven Algorithmus wesentlich effektiver - auch bezüglich des Zeitaufwands - lösen, als mit einem iterativen Algorithmus.

(Beispiel : Potenz n einer reellen Zahl x
  iterativ : (n-1) Multiplikationen
  rekursiv : ld n Multiplikationen )

In einem solchen Fall ist i.a. Rekursion vorzuziehen.
- Weiterhin sollte Rekursion dann angewendet werden,

- wenn ein iterativer Algorithmus nur sehr schwierig und umständlich zu formulieren ist, ein entsprechender rekursiver Algorithmus da- gegen leicht zu finden und anschaulich zu verstehen ist.
- wenn die zugrundeliegenden Datenstrukturen ihrerseits rekursiv sind
(z.B. lineare Listen, Bäume usw)

### Programmbeispiel : Dezimal-Dualwandlung mittels rekursiver Funktion

    /* ------------------------------------------------------------------ */
    /* Programm DEZDUAL2 */
    /* ------------------------------------------------------------------ */
    /* Modifikation des Programms DEZDUAL zur Dezimal-Dualwandlung        */
    /* positiver ganzer Zahlen                                            */
    /*                                                                    */
    /* Ausgabe einer positiven Integerzahl als Dualzahl ohne Zwischen-    */
    /* speicherung der ermittelten Dualziffern                            */
    /* Einsatz einer rekursiven Funktion                                  */
    /* ------------------------------------------------------------------ */

    #include <stdio.h>

    #define BASIS   2

    void printdual(unsigned i)
    {
      unsigned zahl;
      if ((zahl=i/BASIS) != 0)
        printdual(zahl);
      putchar(i%BASIS + '0');
      return;
    }

    void main(void)
    {
      unsigned zahl;
      printf("\npositive ganze Dezimalzahl ? ");
      scanf("%d",&zahl);
      printf("\numgewandelt in Dualzahl    : ");
      printdual(zahl);
      putchar('\n');
    }

    /* ------------------------------------------------------------------ */

Bildschirmanzeige :

    E:\RT\C\VORL>dezdual2
    positive ganze Dezimalzahl ? 135

    umgewandelt in Dualzahl    : 10000111

## 7.6 ANSI-C-Standartbibliothek

### ANSI/ISO-C-Standardbibliothek

- Die Programmiersprache C ist eine "kleine" Sprache.
U.a. enthält sie keinerlei Funktionen.
--> Jede effektive Implementierung von C erfordert die Bereitstellung einer Bibliothek mit häufig benötigten Funktionen (z.B. zur Ein- u. Ausgabe).

Mit ANSIISO-C wurde nicht nur die Sprache C selbst sondern auch eine Standard-Bibliothek genormt.
Eine ausschließliche Verwendung von Standard-Bibliotheks-Funktionen erleichert stark die Portabilität eines C-Programms.

- Die Standard-Bibliothek umfaßt im wesentlichen

- Ein-/Ausgabe-Funktionen
- Funktionen zum Zeichenklassentest
- Funktionen zur Stringbearbeitung
- Diverse Utility-Funktionen (z.B. zur Speicherallokation)
- Diagnose-Funktion
- Funktionen zum Zugriff zu einer variablen Parameterliste
- Funktionen zur Ermöglichung nicht-lokaler Sprünge
- Funktionen zur Exception-Behandlung
- Datums- und Zeitfunktionen
- Funktionen zur Auswahl länderspezifischer Darstellungen

- Die Anwendung einiger Funktionen setzt speziell definierte Konstanten und Typen voraus.
- Ein Teil der "Funktionen" ist tatsächlich nicht als Funktion sondern als Makro realisiert.
- Mit der Standard-Bibliothek eng assoziiert ist eine Gruppe von Standard-Header-Dateien.
Diese Standard-Header-Dateien enthalten:

- die für die Verwendung der Standard-Bibliotheks-Funktionen notwendigen Extern-Deklarationen (Function Prototypes)
- die Definitionen von Makros, die anstelle von Funktionen realisiert sind
- die Definitionen der speziellen Konstanten und Typen, die für die Anwendung der Funktionen benötigt werden
- die Definitionen weiterer implementierungsabhängiger Größen (z.B. Fehlernummern, numerische Konstante)
- die Externdeklarationen einiger Systemvariabler

- Funktionell zusammengehörige Definitionen und Deklarationen sind jeweils in einer spezifischen Header-Datei zusammengefaßt.
Einige Definitionen und Deklarationen sind auch mehrfach, d.h.in mehreren Header-Dateien enthalten.
- Jede Bibliotheks-Funktion ist mit einer oder mehreren Header-Dateien assoziiert.
Diese Header-Datei(en) ist (sind) bei Verwendung der Funktion sinnvollerweise mittels #include <datname> in das Quellprogramm einzubinden.
- Die Header-Dateien sind so aufgebaut, daß sie ohne Probleme in jeder beliebiger Reihenfolge und auch mehrfach in ein C-Quell-Modul eingebunden werden können.
- Die ANSI/ISO-C-Norm (einschließlich Amendment 1) schreibt die Namen und den Inhalt von 18 Header-Dateien vor (--> Standard-Header-Dateien).
- Reale ANSI-C-Implementierungen enthalten i.a. darüberhinaus weitere Header-Dateien entsprechend einer Erweiterung des Funktionsumfangs der Bibliothek.
Außerdem können einzelne Standard-Header-Dateien auch weitere - in der Norm nicht vorgeschriebene - Deklarationen und Definitionen enthalten.

### Die Standard-Header-Dateien von ANSI/ISO-C

<assert.h> | Deklaration einer Diagnose-Funktion bzw. Definition eines die Funktion ersetzenden Makros
<ctype.h> | Deklaration von Funktionen zum Zeichenklassentest bzw. Definition ersetzender Makros
<errno.h> | Deklaration der System-Fehlervariablen und Definition von System-Fehlercodes
<float.h> | Definition gleitkommaarithmetikbezogener Konstanten
<iso646.h> | Definition von Ersatzdarstellungen einiger Operatorsymbole
<limits.h> | Definition von Konstanten zur Beschreibung des Wertebereichs der Ganzzahl-Typen
<locale.h> | Definition von Konstanten und eines Typs sowie Deklaration von Funktionen zur Auswahl länderspezifischer Darstellungen
<math.h> | Deklaration mathematischer Funktionen
<setjmp.h> | Deklaration von Funktionen zur Ermöglichung nicht-lokaler Sprünge
<signal.h> | Definition von Konstanten und Deklaration von Funktionen zur Exception-Behandlung
<stdarg.h> | Definition von Typen und Makros zum Zugriff zu einer variablen Parameterliste
<stddef.h> | Definition einiger Typen und der Konstanten NULL, sowie Deklaration der System-Fehlervariablen errno
<stdio.h> | Definition von Konstanten und Typen, sowie Deklaration von Funktionen zur Ein- und Ausgabe
<stdlib.h> | Definition von Konstanten und Typen, sowie Deklaration diverser Utility-Funktionen (z.B. String-Konvertierungsfunktionen, Speicherverwaltungsfunktionen, Environment-Funktionen, Such- und Sortier-Funktionen, Integer-Arithmetik-Funktionen, Multibyte-Zeichen-Funktionen)
<string.h> | Deklaration von Funktionen zur Stringbearbeitung und Speicherbearbeitung
<time.h> | Definition von Konstanten und Typen, sowie Deklaration von Zeit- und Datums-Funktionen
<wchar.h> | Definition von Konstanten und Makros sowie Deklaration von Funktionen zur Bearbeitung von "wide characters" (Ein-/Ausgabe, Stringbearbeitung)
<wctype.h> | Deklaration von Funktionen zum Test und Klassifizieren von "wide characters"

### Überblick über die I/O-Funktionen der ANSI-C-Standardbibliothek

- Funktionen zur Dateibearbeitung (ohne Schreiben und Lesen)

`int fclose(FILE *fp);` | Schließen einer Datei
`int fflush(FILE *fp);` | Herausschreiben eines Dateipuffers
`int fgetpos(FILE *fp, fpos_t *ptr);` | Ermitteln der aktuellen Bearbeitungsposition
`FILE *fopen(char *path, char *mode);` | Öffnen einer Datei
`FILE *freopen(char *path, char *mode, FILE *fp);` | Erneutes Öffnen einer offenen Datei
`int fseek(FILE *fp, long offs, int org);` | Verändern der Bearbeitungsposition
`int fsetpos(FILE *fp, fpos_t *ptr);` | Verändern der Berabeitungsposition
`long ftell(FILE *fp);` | Ermitteln der aktuellen Bearbeitungsposition |
`void rewind(FILE *fp);` | Zurücksetzen auf den Dateianfang
`void setbuf(FILE *fp, char *buf);` | Bereitstellen eines benutzerdefinierten Dateipuffers
`int setvbuf(FILE *fp, char *buf, int mode, size_t size);` | Bereitstellen eines benutzerdefinierten Dateipuffers

- Funktionen zur Dateiverwaltung

`int remove(char *path);` | Löschen einer Datei
`int rename(char *oldpath, char *newpath);` | Umbenennen einer Datei
`FILE *tmpfile(void);` | Erzeugen und Öffnen einer temporären Datei
`char *tmpnam(char s[L_tmpnam]);` | Erzeugen eines noch nicht vorhandenen Dateinamens

- Funktionen zur Fehlerbehandlung

`void clearerr(FILE *fp);` | Rücksetzen des Fehler- und des EOF-Flags
`int feof(FILE *fp);` | Überprüfung des EOF-Flags
`int ferror(FILE *fp);` | Überprüfung des Fehler-Flags
`void perror(char *s);` | Ausgabe von Fehlermeldungen nach stderr

- Funktionen zur Ein-und Ausgabe von/nach stdin und stdout

`int getchar(void);` | zeichenweise Eingabe von stdin
`char *gets(char *s);` | zeilenweise Eingabe von stdin
`int printf(char *ctrl, ...);` | formatierte Ausgabe nach stdout
`int putchar(int c);` | zeichenweise Ausgabe nach stdout
`int puts(char *s);` | zeilenweise Ausgabe nach stdout
`int scanf(char *ctrl, ...);` | formatierte Eingabe von stdin
`int vprintf(char *ctrl, va_list arg);` | formatierte Ausgabe nach stdout

- Funktionen zur Eingabe aus beliebigen Dateien

`int fgetc(FILE *fp);` | zeichenweise Eingabe
`char *fgets(char *s, int n, FILE *fp);` | zeilenweise Eingabe
`int fprintf(FILE *fp, char *ctrl, ...);` | formatierte Ausgabe
`int fputc(int c, FILE *fp);` | zeichenweise Ausgabe
`int fputs(char *s, FILE *fp);` | stringweise Ausgabe
`size_t fread(void *ptr, size_t size, size_t count, FILE *fp);` | datenobjektorientierte Eingabe
`int fscanf(FILE *fp, char *ctrl, ...);` | formatierte Eingabe
`size_t fwrite(void *ptr, size_t size, size_t count, FILE *fp);` | datenobjektorientierte Ausgabe
`int getc(FILE *fp);` | zeichenweise Eingabe
`int putc(int c, FILE *fp);` | zeichenweise Ausgabe
`int ungetc(int c, FILE *fp);` | Rückgabe eines Zeichens in Eingabepuffer
`int vfprintf(FILE * fp, char *ctrl, va_list arg);`

- Funktionen zur Ein-/Ausgabe aus/in Strings

`int sprintf(char *s, char *ctrl, ...);` | format. Ausgabe in einen String
`int sscanf(char *s, char *ctrl, ...);` | format. Eingabe aus einem String
`int vsprintf(char *s, char *ctrl, va_list arg);` | format. Ausgabe in einen String

### Funktionen der ANSI-C-Standardbibliothek zum Zeichenklassentest

- Die ANSI-C-Standardbibliothek stellt

- 11 Funktionen zur Überprüfung eines Zeichens auf Zugehörigkeit zu einer bestimmten Zeichenklasse, sowie
- 2 Funktionen zur Umwandlung von Großbuchstaben in Kleinbuchstaben und umgekehrt zur Verfügung

- Diese Funktionen sind unabhängig von dem der jeweiligen Implementierung zugrundeliegenden Zeichencode definiert.
D.h. sie sollten auch in Implementierungen, die einen anderen Zeichencode als den ASCII-Code (z.B. EBCDI-Code) verwenden, einwandfrei funktionieren.
- Häufig sind diese Funktionen als Makros realisiert.
- Die entsprechenden Funktionsdeklarationen bzw Makrodefinitionen sind in der Standard-Header-Datei <ctype.h> enthalten.
Diese ist daher bei Verwendung der Funktionen mittels
`#include <ctype.h>`
einzubinden.
- Alle Funktionen haben einen int-Parameter.
Der übergebene aktuelle int-Parameter muß in den Wertebereich von `unsigned char` fallen oder `EOF` sein.
- Die Funktionen zur Überprüfung eines Zeichens auf eine bestimmte Klassenzugehörigkeit liefern als int-Funktionswert

- einen Wert != 0 (TRUE), wenn der übergebene aktuelle Parameter in die jeweils überprüfte Zeichenklasse fällt,
- den Wert 0 (FALSE), wenn der übergebene aktuelle Parameter nicht in die überprüfte Zeichenklasse fällt
- einen undefinierten Wert, wenn der übergebene aktuelle Parameter nicht als unsigned char darstellbar und nicht gleich EOF ist.

- Die Funktionen zur Zeichenumwandlung liefern als int-Funktionswert

- das umgewandelte Zeichen, falls eine Umwandlung relevant ist,
- den als aktuellen Parameter übergebenen Wert, falls eine Umwandlung nicht relevant ist.

- Bei Verwendung einer der folgenden Funktionen muß die zugehörige Standard-Header-Datei mittels
`#define <ctype.h>`

in das Quellmodul eingebunden werden.

- Funktionen zur Überprüfung eines Zeichens auf eine bestimmte Klassen- zugehörigkeit

`int isalnum(int c);` | Überprüfung auf Buchstabe (Groß- oder Klein-) der (Dezimal-)Ziffer
`int isalpha(int c);` | Überprüfung auf Buchstabe (Groß- oder Klein-)
`int iscntrl(int c);` | Überprüfung auf Steuerzeichen
`int isdigit(int c);` | Überprüfung auf (Dezimal-)Ziffer
`int isgraph(int c);` | Überprüfung auf darstellbare Zeichen, ausgenommen BLANK (SPACE)
`int islower(int c);` | Überprüfung auf Klein-Buchstaben
`int isprint(int c);` | Überprüfung auf darstellbare Zeichen, einschließlich BLANK (SPACE)
`int ispunct(int c);` | Überprüfung auf darstellbare Zeichen, ausgenommen BLANK, Buchstaben und (Dezimal-)Ziffern
`int isspace(int c);` | Überprüfung auf BLANK (SPACE), FF, NEWLINE, CR, HT und VT
`int isupper(int c);` | Überprüfung auf Groß-Buchstaben
`int isxdigit(int c);` | Überprüfung auf Seddezimal-Ziffer

- Funktionen zur Zeichenumwandlung

`int tolower(int c);` | Umwandlung von Groß- in Klein-Buchstaben
`int toupper(int c);` | Umwandlung von Klein- in Groß-Buchstaben

### Funktionen der ANSI-C-Standardbibliothek zur Stringbearbeitung

Überblick

- Funktions-Deklarationen (Function Prototypes) in :
`<string.h>`
- Funktionen zum Kopieren von Strings und Zeichenarrays

`void *memcpy(void *s1, const void *s2, size_t n);` | Kopieren von n Zeichen vom Array s2 in das Array s1
`void *memmove(void *s1, const void *s2, size_t n);` | Kopieren von n Zeichen vom Array s2 in das Array s1 (Überlappung ist zulässig)
`char *strcpy(char *s1, const char *s2);` | Kopieren des Strings s2 in den String s1.
char *strncpy(char *s1, const char *s2, size_t n); | Kopieren von maximal n Zeichen vom String s2 in den String s1 (ggfls kein Abschluß mit '\0')

- Funktionen zum Aneinanderhängen von Strings

`char *strcat(char *s1, const char *s2);` | Anhängen von String s2 an den String s1
`char *strncat(char *s1, const char *s2, size_t n);` | Anhängen von maximal n Zeichen des Strings s2 an den Str. s1 (Abschluß immer mit '\0')

- Funktionen zum Vergleich von Strings und Zeichenarrays

`int memcmp(const void *s1, const void *s2, size_t n);` | zeichenweiser Vergleich der Arrays s1 und s2 (max. n Zeichen)
`int strcmp(const char *s1, const char *s2);` | Vergleich der Strings s1 und s2
`int strncmp(const char *s1, const char *s2, size_t n);` | Vergleich der Strings s1 und s2 (max. n Zeichen)
`int strcoll(const char *s1, const char *s2);` | Vergleich der Strings s1 und s2 gemäß dem mit setlocale() festgelegten länderspezifischen Zeichencode
`size_t strxfrm(char *s1, const char *s2, size_t n);` | Code-Transformation von maximal n Zeichen des Strings s2 in den String s1 (Quellcode : länderspezifisch Zielcode : der von strcmp() verwendete Zeichencode)

- Funktionen zum Suchen in Strings und Arrays

`void *memchr(const void *s, int c, size_t n);` | Durchsuchen von max n Zeichen des Arrays s nach dem ersten Auftritt des Zeichens c
`char *strchr(const char *s, int c);` | Durchsuchen des Strings s nach dem ersten Auftreten des Zeichens c
`char *strrchr(const char *s, int c);` | Durchsuchen des Strings s nach dem letzten Auftreten des Zeichens c
`char *strpbrk(const char *s1, const char *s2);` | Durchsuchen des Strings s1 nach dem ersten Auftreten irgendeines der im String s2 enthaltenen Zeichen
`char *strstr(const char *s1, const char *s2);` | Durchsuchen des Strings s1 nach dem ersten Auftreten des Strings s2
`size_t strspn(const char *s1, const char *s2);` | Ermittlung der Länge des Teilstrings von s1, der nur aus in s2 enthaltenen Zeichen besteht
`size_t strcspn(const char *s1, const char *s2);` | Ermittlung der Länge des Teilstrings von s1, der nur aus in s2 nicht enthaltenen Zeichen besteht
`char *strtok(char *s1, const char *s2);` | Zerlegen des Strings s1 in die Teilstrings, die durch im String s2 enthaltene Zeichen getrennt werden

- Funktion zum Füllen eines Arrays mit einem Zeichen

`void *memset(void *s, int c, size_t n);` | Ablage des Zeichens c in n aufeinanderfolgende Elemente des (char-) Arrays s

- Funktion zur Ermittlung der Länge eines Strings

`size_t strlen(const char *s);` | Ermittlung der Länge des Strings s

- Funktion zur Ermittlung einer Fehlermeldung

`char *strerror(int errnum);` | Ermittlung der Fehlermeldung, die zur Fehlernummer errnum gehört

### Funktionen der ANSI-C-Standardbibliothek zur Stringbearbeitung

    /* -------------------------------------------------------------------- */
    /*    strtok      -     Zerlegung eines Strings in Teilstrings   */
    /* -------------------------------------------------------------------- */

    #include <string.h>     /* enthaelt Funktionsdeklaration */

    char *strtok(char *str1, const char *str2);
                            /* str1     zu zerlegender String               */
                            /* str2     String, der die für die Zerlegung
                                        maßgeblichen Trennzeichen enthält   */

"strtok" wird üblicherweise mehrfach aufgerufen.
Bei jedem Aufruf liefert die Funktion einen Pointer auf den jeweils nächsten Teilstring ("token").
Jeder Teilstring besteht dabei nur aus Zeichen, die nicht in "str2" enthalten sind.

Beim ersten Aufruf durchsucht "strtok" den String "str1" nach dem ersten nicht im String "str2" enthaltenen Zeichen.
Dieses Zeichen ist der Beginn des ersten Teilstrings ("token").

Besteht "str1" nur aus Zeichen, die auch in "str2" enthalten sind, so gibt es keinen Teilstring und die Funktion wird mit dem NULL-Pointer als Funktionswert beendet.

Wird jedoch ein nicht in "str2" enthaltenes Zeichen gefunden, so sucht die Funktion in "str1" das nächste in "str2" enthaltene Zeichen (d.h. das nächste Trennzeichen).
Dieses wird durch den NULL-Character ersetzt (der String "str1" wird also verändert !), womit der Teilstring abgeschlossen ist.
In einer lokalen statischen Variablen merkt sich die Funktion dann einen Pointer auf das nächste Zeichen in "str1".
Anschließend wird die Funktion mit dem Pointer auf den Beginn des Teil- strings als Funktionswert beendet.

Bei jedem folgenden Aufruf, mit dem der nächste Teilstring ermittelt werden soll, muß der Funktion der NULL-Pointer als aktueller Parameter für "str1" übergeben werden.
Die Funktion durchsucht dann den "str1" ab der intern gemerkten Position nach dem nächsten Teilstring.
Wird ein derartiger Teilstring gefunden, so gibt die Funktion wiederum einen Pointer auf seinen Beginn als Funktionswert zurück.
Wird kein weiterer Teilstring mehr gefunden, so kehrt die Funktion mit dem NULL-Pointer als Funktionswert zurück.

Funktionswert :
  Pointer auf den Beginn des jeweils nächsten Teilstrings bzw. NULL-Pointer, wenn kein Teilstring ("token") mehr gefunden wird.

### Demonstrationsbeispiel zur Funktion `strtok()`

    /*---------------------------------------------------------------*/
    /* Programm TOKTEST                                              */
    /*---------------------------------------------------------------*/

    /* Demonstrationsprogramm zur Funktion strtok()                  */

    /* Zerlegung eines einzugebenden Satzes in einzelne Worte        */

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

    #define  MAX       132
    #define  TRENNER   " ,.;:!?-\n"

    void main(void)
    {
      char satz[MAX];
      char *token;

      printf("\nSatz ? ");
      gets(satz);
      token=strtok(satz,TRENNER);
      while (token != NULL)
      { printf("\n%s",token);
        token=strtok(NULL,TRENNER);
      }
      putchar('\n');
    }

Bildschirmausgabe :

    E:\RT\C>toktest

    Satz ? Frage: Was soll das, wenn Du nicht magst ?

    Frage
    Was
    soll
    das
    wenn
    Du
    nicht
    magst

### Funktionen der ANSI-C-Standardbibliothek zur Bearbeitung allgemeiner Zeichenfolgen (Speicherbereiche)

    /* -------------------------------------------------------------------- */
    /*      memcpy     -    Kopieren einer Zeichenfolge bestimmter Länge    */
    /* -------------------------------------------------------------------- */

    #include <string.h>       /* enthält Funktionsdeklaration */

    void *memcpy(void *s1, const void *s2, size_t n);

                   /* s1  Pointer auf Ziel-Datenobjekt (Zielzeichenfolge)   */
                   /*     (Speicherbereich in den kopiert wird)             */
                   /* s2  Pointer auf Quell-Datenobjekt (Quellzeichenfolge) */
                   /*     (Speicherbereich aus dem kopiert wird)            */
                   /* n   Anzahl der zu kopierenden Zeichen                 */

"memcpy" kopiert "n" Zeichen aus dem durch "s2" referierten - als Zeichenfolge aufgefaßten - Datenobjekt (Quell-Speicherbereich) in das durch "s1" referierte - als Zeichenfolge aufgefaßte - Datenobjekt (Ziel-Speicherbereich).
Quell- und Ziel-Speicherbereich dürfen sich nicht überlappen.

Funktionswert :
  Pointer auf den Ziel-Speicherbereich, d.h. "s1" ( = 1. Parameter )

    /* -------------------------------------------------------------------- */
    /*      memcmp     -    Vergleich zweier Zeichenfolgen bestimmter Länge */
    /* -------------------------------------------------------------------- */

    #include <string.h>       /* enthält Funktionsdeklaration    */

    int *memcmp(const void *s1, const void *s2, size_t n);

                   /* s1  Pointer auf erstes zu vergleichendes Datenobjekt  */
                   /*     (erste Zeichenfolge)                              */
                   /* s2  Pointer auf zweites zu vergleichendes Datenobjekt */
                   /*     (zweite Zeichenfolge)                             */
                   /* n   Anzahl der zu vergleichenden Zeichen              */

"memcmp" vergleicht die ersten n Zeichen des durch "s1" referierten - als Zeichenfolge aufgefaßten - Datenobjekts lexikographisch mit dem durch "s2" referierten - als Zeichenfolge aufgefaßten - Datenobjekts.
Für den Vergleich werden die Zeichen als unsigned char interpretiert.

Funktionswert :
  < 0 , wenn das erste unterschiedliche Zeichen bei der durch "s1" referierten Zeichenfolge kleiner als bei bei der durch "s2" referierten Zeichenfolge ist
  =0 , wenn die ersten n Zeichen in beiden Zeichenfolgen gleich sind
  > 0 , wenn das erste unterschiedliche Zeichen bei der durch "s1" referierten Zeichenfolge größer als bei der durch "s2" referierten Zeichenfolge ist

    /* -------------------------------------------------------------------- */
    /*  memchr  -  Suchen eines Zeichens in einer Zeichenfolge best. Länge  */
    /* -------------------------------------------------------------------- */

    #include <string.h>       /* enthält Funktionsdeklaration */

    void *memchr(const void *s, int c, size_t n);

                    /* s  Pointer auf zu durchsuchendes Datenobjekt          */
                    /*    (zu durchsuchende Zeichenfolge)                    */
                    /*    ( = Speicherbereich, der durchsucht wird)          */
                    /* c  Zeichen, nach dem gesucht wird                     */
                    /* n  Anz. der bei der Suche zu berücksichtigen Zeichen  */
                    /*    ( = Länge der zu durchsuchenden Zeichenfolge)      */

"memchr" durchsucht die ersten "n" Zeichen des durch "s%quot; referierten - als Zeichenfolge aufgefaßten - Datenobjekts nach dem ersten Auftritt des - in unsigned char umgewandelten - Zeichens "c".

Funktionswert :
  Pointer auf die Stelle, an der das Zeichen "c" erstmals auftritt,
bzw. NULL-Pointer, wenn das Zeichen "c" nicht gefunden wird

    /* -------------------------------------------------------------------- */
    /*    memset     -     Füllen eines Speicherbereichs mit einem Zeichen  */
    /* -------------------------------------------------------------------- */

    #include <string.h>       /* enthält Funktionsdeklaration    */

    void *memset(void *s, int c, size_t n);

                   /* s  Pointer auf den zu füllenden Speicherbereich       */
                   /* c  Zeichen, mit dem der Speicherbereich gefüllt wird  */
                   /* n  Anzahl der Zeichen des zu füllenden Speicherber.   */

"memset" setzt die ersten "n" Zeichen des durch "s" referierten Speicherbereichs auf den Wert des - in unsigned char umgewandelten - Zeichens c

Funktionswert :
  Pointer auf gefüllten Speicherbereich (= Parameter "s")

### Allgemeine Utility-Funktionen der ANSI-C-Standardbibliothek

- In der Standard-Header-Datei <stdlib.h> sind die Deklarationen einer Reihe von Funktionen enthalten, die man zusammenfassend als Allgemeine Utility-Funktionen bezeichnen kann.
Diese Funktionen lassen sich in die folgenden Gruppen einteilen :

- Funktionen zur Umwandlung von Strings in numerische Werte
- Funktionen zur Erzeugung von Pseudo-Zufallszahlen
- Funktionen zur dynamischen Speicherverwaltung
- Environment-Funktionen
- Such- und Sortierfunktionen
- Integer-Arithmetik-Funktionen
- Funktionen zur Verarbeitung erweiterter Zeichensätze
(nicht in allen Implementierungen enthalten)

- Bei Verwendung dieser Funktionen ist die Standard-Header-Datei <stdlib.h> mittels
#include <stdlib.h>
einzubinden.

In dieser sind weiterhin die folgenden Typen und Konstanten definiert:

Typ | `div_t` | ein Structure-Typ für den Funktionswert von `div()`
Typ | `ldiv_t` | ein Structure-Typ für den Funktionswert von `ldiv()`
Typ | `size_t` | der "natürliche" unsigned-Typ, d.h. der Typ, den der `sizeof`-Operator liefert
Typ | `wchar_t` | ein ganzzahliger Typ, dessen Wertebereich den größten vom Compiler unterstützten Zeichensatz darstellen kann (nur definiert, wenn die Funktionen zur Verarbeitung erweiterter Zeichensätze implementiert sind)
Konstante | `EXIT_FAILURE` | ein ganzzahliger Wert zur Kennzeichnung einer fehlerhaften Programmbeendigung (Rückgabewert von `exit()`)
Konstante | `EXIT_SUCCESS` | ein ganzzahliger Wert zur Kennzeichnung einer erfolgreichen Programmbeendigung (Rückgabewert von `exit()`)
Konstante | `MB_CUR_MAX` | ein positiver gannzahliger Wert, der angibt aus wieviel Bytes ein Multi-Byte-Character maximal bestehen kann (nur definiert, wenn die Funktionen zur Verarbeitung erweiterter Zeichensätze implementiert sind)
Konstante | `RAND_MAX` | der größte von rand() erzeugte Zufallswert (ganzzahlig)

- Funktionen zur Umwandlung von Strings in numerische Werte

`double atof(const char *s);` | Umwandlung des Strings s in einen double-Wert
`int atoi(const char *s);` | Umwandlung des Strings s in einen int-Wert
`long atol(const char *s);` | Umwandlung des Strings s in einen long-Wert
`double strtod(const char *s, char **endp);` | Umwandlung des Strings s in einen double-Wert, Speicherung eines Pointers auf das erste nicht umgewandelte Zeichen von s in `*endp` (nur wenn endp != NULL)
`long strtol(const char *s, char **endp, int base);` | Umwandlung des Strings s in einen long-Wert mit der Basis base, Speicherung eines Pointers auf das erste nicht umgewandelte Zeichen von s in `*endp` (nur wenn endp != NULL)
`unsigned long strtoul(char *s, char **endp, int base);` | Umwandlung des Strings s in einen unsigned long-Wert mit der Basis base, Speicherung eines Pointers auf das erste nicht umgewandelte Zeichen von s in `*endp` (nur wenn endp != NULL)

- Funktionen zur Erzeugung von Pseudo-Zufallszahlen

`int rand(void);` | Erzeugung einer ganzzahligen Pseudo-Zufallszahl im Bereich 0 .. `RAND_MAX`
`void srand(unsigned int seed);` | Setzen des Initialisierungswertes für die durch `rand()` erzeugte Zufallszahlenfolge (default : 1)

- Funktionen zur dynamischen Speicherverwaltung

`void *calloc(size_t nobj, size_t size);` | Allokation eines Speicherblocks für `nobj`-Objekte jeder Länge size, Initialisierung aller Speicherplätze mit 0
`void free(void *p);` | Freigabe des Speicherbereichs auf den p zeigt
`void *malloc(size_t size);` | Allokation eines Speicherblocks der Länge `size` (keine Initialisierung)
`void *realloc(void *p, size_t size);` | Änderung der Größe eines bereits vorher allokierten - durch p referierten Speicherblocks - auf den neuen Wert `size`

- Environment-Funktionen

`void abort(void);` | Anormale Beendigung eines Programms
`int atexit(void (*fcn)(void));` | Registrierung der Funktion `(*fcn)()` als eine Funktion, die bei Programmbeendigung mittels `exit()` aufgerufen wird
`void exit(int status);` | Normale Beendigung eines Programms
`char *getenv(const char *name);` | Ermittlung des zur Environment-Variablen name gehörenden Strings (Wertder Environment-Variablen) (implementierungsabhängig)
`int system(const char *s);` | Übergabe des - ein Kommando bedeutenden - Strings s an die Betriebssystem-Umgebung zur Ausführung

- Such- und Sortier-Funktionen

`void *bsearch(const void *key, const void *base, size_t n, size_t size, int (*cmp)(const void *, const void *));` | Durchsuchen eines in aufsteigender Ordnung sortierten - durch `base` referierten - Feldes von n Objekten der Länge `size` auf Übereinstimmung mit dem Suchschlüssel `*key`. Der Vergleich erfolgt mittels der Funktion `(*cmp)()`
`void qsort(void *base, size_t n, size_t size, int (*cmp)(const void *, const void *));` | Sortieren des - durch base referierten - Feldes von n Objekten der Länge `size` in aufsteigender Ordnung mittels der Vergleichsfunktion `(*cmp)()` |

- Integer-Arithmetik-Funktionen

`int abs(int i);` | Ermittlung des Absolutwertes von i (int-Wert)
`div_t div(int dend, int disor);` | Ermittlung des ganzzahligen Quotienten und des Restes der Division von dend durch `disor` (int-Werte)
`long labs(long n);` | Ermittlung des Absolutwertes von n (long-Wert)
`ldiv_t ldiv(long dend, long disor);` | Ermittlung des gannzahligen Quotienten und des Restes der Division von dend durch `disor` (long-Werte)

### Environment-Funktionen der ANSI-C-Std-Bibliothek

    /* -------------------------------------------------------------------- */
    /*          exit          -        Beendigung des Programms             */
    /* -------------------------------------------------------------------- */

    #include <stdlib.h>          /* enthält  Funktionsdeklaration */

    void exit(int status);

                    /*  status    Rückgabewert des Programms (Return Code)  */

`exit()` beendet das laufende Programm (normale Beendigung).
Vor der Rückkehr zum Aufrufer des Programms (i.a. die Shell des Betriebssystems) werden gegebenenfalls alle mit `atexit()` festgelegten Funktionen aufgerufen, sowie alle noch nicht herausgeschriebenen Datei-Schreib-Puffer herausgeschrieben und alle offenen Dateien geschlossen.
Der Parameter `status` wird als Program Return Code dem Aufrufer des Programms übergeben. (Unter MS-DOS kann dieser z.B. auf der Kommandoprozessor- Ebene mit ERRORLEVEL abgefragt werden).
I.a. ist es üblich eine fehlerfreie Programmbeendigung mit dem Return Code 0 zu kennzeichnen, Werte != 0 stehen für Fehlergründe, die zur Programmbeendigung geführt haben.

Anmerkung :
  Die Funktion `exit()` sollte i.a. - wenn überhaupt - nur aufgerufen werden, wenn das Programm aus einer anderen Funktion als main() heraus beendet werden soll (was aber einen - nur wenn wohl durchdacht auch tolerierbaren - Verstoß gegen Grundsätze der strukturierten Programmierung darstellt). Normalerwiese gibt es keinen Grund `exit()` von `main()` aus aufzurufen.

Funktionswert :
  keiner, da die Funktion nicht zurückkehrt

    /* -------------------------------------------------------------------- */
    /*  system - Ausführ. eines Programms/Kommandos durch das Betriebssystem*/
    /* -------------------------------------------------------------------- */

    #include <stdlib.h>          /* enthält  Funktionsdeklaration */

    int system(const char *s);

             /* s   String, der die Kommandozeile (Kommando  einschließlich */
             /*     Parameter) darstellt                                    */
             /*     (genauer : Pointer auf die Kommandozeile)               */

`system()` ruft den Kommandoprozessor des Betriebssystems auf und übergibt diesem die durch "s" referierte Kommandozeile zur Ausführung.
Ein Aufruf mit s=NULL kann benutzt werden, um das Vorhandensein eines Kommandoprozessors zu überprüfen.

Funktionswert :

- falls die Funktion mit s=NULL aufgerufen wurde :
== 0, wenn kein Kommandoprozessor verfügbar ist
!= 0, wenn ein Kommandoprozessor verfügbar ist
- falls die Funktion mit s!=NULL aufgerufen wurde :
implementierungsabhängiger Wert.
Für MS-DOS gilt : Return Code von `COMMAND.COM`
(=0, wenn fehlerfreie Ausführung von `COMMAND.COM`)

### Mathematische Funktionen der ANSI-C-Standardbibliothek

- Die ANSI-C-Standardbibliothek stellt auch eine Anzahl häufig benötigter mathematischer Funktionen zur Verfügung.
Sie lassen sich in die folgenden Klassen einteilen :

- Trigonometrische und hyperbolische Funktionen
- Exponential- und logarithmische Funktionen
- Sonstige mathematische Funktionen

- Die entsprechenden Funktions-Deklarationen befinden sich in der Standard-Header-Datei <math.h>.
Bei Verwendung der Funktionen ist diese daher mittels
`#include <math.h>`
einzubinden.

In `<math.h>` ist außerdem definiert :

Makro | `HUGE_VAL` | double-Wert, der zur Kennzeichnung des Überschreitens des darstellbaren Wertebereichs dient (kann "unendlich" repräsentieren).

Die Definition kann als Konstante oder als Variable erfolgen
- Alle mathematischen Funktionen erzeugen einen Funktionswert vom Typ double.
Die Parameter sind i.a. ebenfalls vom Typ double.
- Alle Funktionen erkennen Bereichsüberschreitungen sowohl für die übergebenen aktuellen Parameter als auch für den Funktionswert :

- liegt ein aktueller Parameterwert außerhalb des zulässigen Bereichs (--> domain error),
so wird die globale Fehlervariable errno auf den Wert EDOM gesetzt.
Der Funktionswert ist in diesem Fall implementierungsabhängig.
- liegt der sich ergebende Funktionswert außerhalb des darstellbaren Wertebereichs des Typs double (--> range error),
so wird die globale Fehlervariable errno auf den Wert `ERANGE` gesetzt.
Als Funktionswert wird zurückgegeben :

- der Wert 0, wenn ein Bereichs-Unterlauf auftritt, bzw.
- der Wert `HUGE_VAL` (gegebenenfalls mit einem Vorzeichen versehen), wenn ein Bereichs-Überlauf auftritt

Die Deklaration der Fehlervariablen errno und die Definition der Konstanten `EDOM` und `ERANGE` ist in der Standard-Header-Datei `<errno.h>` enthalten.
- Die trigonometrischen Funktionen arbeiten mit Parametern im Bogenmaß.
Deren Wert sollte nicht zu weit außerhalb des Intervalls [-2&pi; .. 2&pi;] liegen, da die Funktionen für Parameter mit großem Betrag häufig ungenaue Werte liefern.
Gegebenenfalls sind die Parameter modulo &pi; zu übergeben.

    /* -------------------------------------------------------------------- */
    /*  Trigonometrische und hyperbolische Funktionen                       */
    /* -------------------------------------------------------------------- */

    #include <math.h>    /* enthält Funktionsdeklarationen */

`double acos(double x);` | Arcus-Cosinus von x -1 <= x <= 1 Funktionswert: [0 .. &pi;]
`double asin(double x);` | Arcus-Sinus von x -1 <= x <= 1 Funktionswert : [-&pi;/2 .. &pi;/2] Ý
`double atan(double x);` | Arcus-Tangens von x Funktionswert : [-&pi;/2 .. &pi;/2]
`double atan2(double y, double x);` | Arcus-Tangens von y/x Funktionswert : [-&pi; .. &pi;] Die Ermittlung des Quadranten,in dem der Funktionswert liegt, erfolgt unter Berücksichtigung des Vorzeichens beider Parameter
`double cos(double x);` | Cosinus von x
`double cosh(double x);` | Cosinus Hyperbolicus von x
`double sin(double x);` | Sinus von x
`double sinh(double x);` | Sinus Hyperbolicus von x
`double tan(double x);` | Tangens von x
`double tanh(double x);` | Tangens Hyperbolicus von x

    /* --------------------------------------------------------------------- */
    /*  Exponential- und logarithmische Funktionen                           */
    /* --------------------------------------------------------------------- */

    #include <math.h>    /* enthält Funktionsdeklarationen */

`double exp(double x);` | Exponentialfunktion "e hoch x"
`double frexp(double x, int *ex);` | Zerlegung von x in die normalisierte Darstellung x = m * (2 ** n) mit 0.5 <= m < 1 Für x=0 : m=0 und n=0 m --> Funktionswert n --> Ablage in *ex
`double ldexp(double x, int n);` | Funktion x*(2**n)
`double log(double x);` | natürlicher Logarithmus ln(x), x>0
`double log10(double x);` | dekadischer Logarithmus lg(x), x>0
`double modf(double x, int *ip);` | Zerlegung von x in einen ganzzahligen und einen gebrochenen Teil gebrochener Teil --> Funktionswert ganzzahl. Teil --> Ablage in *ip
`double pow(double x, double y);` | "x hoch y"
`double sqrt(double x);` | Positive Qaudratwurzel von x, x>=0

    /* --------------------------------------------------------------------- */
    /*  Sonstige mathematische Funktionen                                    */
    /* --------------------------------------------------------------------- */

    #include <math.h>    /* enthält Funktionsdeklarationen */

`double ceil(double x);` | kleinste ganze Zahl >= x
`double fabs(double x);` | Absolutwert von x
`double floor(double x);` | größte ganze Zahl <=x
`double fmod(double x, double y);` | Gleitpunkt-Rest der Division x/y Funktionswert hat das Vorzeichen von x Falls y=0 --> Funktionswert implementierungsabhängig

### Zugriff zu einer variablen Parameterliste

- In C ist es möglich, Funktionen mit einer variablen Anzahl von Parametern zu definieren.
Eine derartige Funktion muß mindestens einen festen Parameter haben. Daß die Funktion weitere - als die aufgeführten festen - Parameter ver- arbeiten kann, ist in der Parameterliste durch die Angabe von "..." nach dem letzten festen Parameter auszudrücken.
Beispiel für einen entsprechenden Function Prototype :
`void f1(int arg1, int arg2, ...);`
- Da die weiteren möglichen Parameter bei der Funktionsdefinition nicht angegeben werden können, kann man innerhalb der Funktion auch nicht über Namen auf sie zugreifen.
- Die Standard-Header-Datei `<stdarg.h>` stellt eine portable Möglichkeit für den Zugriff zu den variablen Parametern zur Verfügung.
In dieser Header-Datei sind definiert bzw deklariert :

Typ | `va_list` | Array-Typ geeignet zur Aufname der Informationen, die zur Beschaffung der Parameter benötigt werden
Macro | `void va_start(va_list ap, last_parm);` | Initialisierung der Variablen ap für die folgende Verwendung durch `va_arg()` und `va_end()`. `last_parm` ist der letzte feste Parameter der Funktion. Muß vor dem ersten Zugriff zur variablen Parameterliste aufgerufen werden
Macro | `type va_arg(va_list ap, type);` | Liefert als Wert den nächsten Parameter aus der Parameterliste, für die ap initialisiert wurde. Der angegebene Typ type muß der Typ des Parameters sein.
Function oder Macro | `void va_end(va_list ap);` | Abschluß des Zugriffs zu der variablen Parameterliste. Durch den Aufruf von `va_end()` wird eine normale Funktionsbeendigung sichergestellt

- Einer Funktion mit einer variablen Parameterliste muß bei jedem Aufruf mitgeteilt werden,

- wieviel Parameter aktuell übergeben werden
- und gegebenenfalls von welchem Typ diese Parameter jeweils sind
(falls die Parameter von unterschiedlichen Typ sein können)

Diese Informationen können z.B. über die festen Parameter übergeben werden.
Die Anzahl der aktuell übergebenen Parameter kann der Funktion auch über einen speziellen die Parameterliste abschließenden Wert mitgeteilt werden.

Bei den Funktionen `printf()` und `scanf()`, die beide mit einer variablen Parameterliste arbeiten, werden sowohl die Anzahl als auch der jeweilige Typ der folgenden Parameter durch die im ersten Parameter (Controlstring) enthaltenen Umwandlungsspezifikationen mitgeteilt.

- Allgemeine Vorgehensweise zur Erstellung von Funktionen mit variabler Parameterliste

- Die Funktion muß wenigstens einen festen Parameter vor der variablen Parameterliste haben.
Der letzte - d.h. am weitesten rechts stehende - feste Parameter habe den Namen `last_parm`.
- Es muß eine Variable vom Typ `va_list` definiert werden:
`va_list argp;`
- Vor dem ersten Zugriff zu der variablen Parameterliste muß die Variable vom Typ va_list durch den Aufruf von va_start() initialisiert werden:
`va_start(argp, last_parm);`
- Anschließend bringt jeder Aufruf von `va_arg()` den jeweils nächsten Parameter aus der variablen Parameterliste zurück.
`va_arg()` muß dabei jeweils mit der richtigen Typ-Angabe für den nächsten Parameter aufgerufen werden.

`next = va_arg(argp, int); /* int-Parameter angenommen */`

- Nach dem Zugriff zu allen Parametern muß - vor Beendigung der Funktion - `va_end()` aufgerufen werden.
Dadurch wird eine einwandfreie Wiederherstellung des Stacks sichergestellt.

`va_end(argp);`

- Beispiel

    /*--------------------------------------------------------------------*/
    /* Programm VARPL                                                     */
    /*--------------------------------------------------------------------*/
    /* Beispiel für eine Funktion mit variabler Parameterliste            */

    #include <stdarg.h>
    #include <stdio.h>

    void main(void)
    { float a,b,c;
      int d;
      float m;
      double vmax(int anz,...);
      printf("\nGeben Sie 4 Zahlen ein (letzte int) : ");
      scanf("%e%e%e%d",&a,&b,&c,&d);
      m=vmax(2,a,b);
      printf("\nMaximum der ersten beiden Zahlen : %f",m);
      m=vmax(3,a,b,c);
      printf("\nMaximum der ersten drei Zahlen   : %f",m);
      m=vmax(4,a,b,c,(float)d);
      printf("\nMaximum aller vier Zahlen        : %f",m);
      putchar('\n');
    }

    /*--------------------------------------------------------------------*/
    /* Funktion vmax()                                                    */
    /*--------------------------------------------------------------------*/
    /* Ermittlung des Maximums einer variablen Anzahl von double-Werten   */
    /* Variable Anzahl von Parametern                                     */
    /* Erster Parameter gibt die Anzahl der folgenden Parameter vom Typ   */
    /* double an                                                          */
    /*--------------------------------------------------------------------*/

    double vmax(int anz,...)
    {
      va_list argp;                    /* Definition von argp             */
      double wert,max=0;
      if (anz>0)
      { va_start(argp,anz);            /* Initialisierung von argp        */
        max=va_arg(argp,double);       /* Zugriff zum 1. variablen Param. */
        while (--anz > 0)
        { wert=va_arg(argp,double);    /* Zugriff zu weiteren Parametern  */
          if (wert > max)
            max=wert;
        }
        va_end(argp);                  /* Abschluß des Zugriffs           */
      }
      return max;
    }

Bildschirmausgabe :

    E:\RT\C>varpl

    Geben Sie 4 Zahlen ein (letzte int) : 2.9 -4.7 8.1 3

    Maximum der ersten beiden Zahlen: 2.900000
    Maximum der ersten drei Zahlen  : 8.100000
    Maximum aller vier Zahlen       : 8.100000

### Datums- und Zeitfunktionen

- Die ANSI-C-Standard-Bibliothek stellt Funktionen, die einen Zugriff zur Systemuhr ermöglichen, zur Verfügung :

- Funktionen zur Ermittlung von Zeiten und Zeitdifferenzen
- Funktionen zur Umwandlung von Zeitdarstellungen
- Funktionen zur Darstellung von Zeiten als Strings

- Es werden drei verschiedenen Zeitdarstellungen verwendet :

- Prozessorzeit (processor time), ausgedrückt in Systemzeit-Perioden
- Kalenderzeit (calendar time), gemäß dem Gregorianischen Kalender, in einer implementierungsspezifischen unstrukturierten Darstellung
- Ortszeit (local time), die für eine spezifische Zeitzone geltende Kalenderzeit,
gegebenenfalls modifiziert als Sommerzeit (daylight saving time), in einer strukturierten Darstellung

- Die entsprechenden Funktionsdefinitionen befinden sich in der Standard-Header-Datei `<time.h>`.
Diese ist daher bei Verwendung der Funktionen mittels
`#include <time.h>`
einzubinden.

In `<time.h>` sind außerdem definiert :

Konstante | `CLK_TCK` | Anzahl der Perioden (ticks) der Systemzeit pro Sekunde
Typ | `clock_t` | Arithmetischer Datentyp zur Darstellung der Zeit (bei TURBO-C : = long)
Typ | `time_t` | Arithmetischer Datentyp zur Darstellung der Zeit (bei TURBO-C : = long)
Typ | `tm` | Structure-Typ zur Darstellung der Kalenderzeit

Der Typ tm muß wenigstens die folgenden Komponenten enthalten :

    int tm_sec;     /* Sekunden nach der Minute  [0 .. 59] */
    int tm_min;     /* Minuten nach der Stunde   [0 .. 59] */
    int tm_hour;    /* Stunden seit Mitternacht  [0 .. 23] */
    int tm_mday;    /* Tag des Monats            [0 .. 31] */
    int tm_mon;     /* Monate seit Januar        [0 .. 11] */
    int tm_year;    /* Jahre seit 1900                     */
    int tm_wday;    /* Tage seit Sonntag         [0 ..  6] */
    int tm_yday;    /* Tage seit 1.Januar        [0 ..365] */
    int tm_isdst;   /* Sommerzeit-Flag                     */

Für den Wert des Sommerzeit-Flags `tm_isdst` gilt :

>0 wenn die Sommerzeit in Kraft ist
=0 wenn die Sommerzeit nicht in Kraft ist
<0 wenn die Information nicht verfügbar ist

- Funktionen zur Ermittlung von Zeiten und Zeitdifferenzen

- Funktionen zur Umwandlung von Zeitdarstellungen

`struct tm *gmtime(const time_t *tp);` | Umwandlung der in implementierungsspezifischer Darstellung vorliegenden Kalenderzeit `*tp` in eine strukturierte Darstellung der Coordinated Universal Time (UTC). Falls die UTC nicht ermittelt werden kann, ist der Funktionswert = NULL
`struct tm *localtime(const time_t *tp);` | Umwandlung der in implementierungsspezifischer Darstellung vorliegenden Kalenderzeit `*tp` in eine strukturierte Darstellung der Ortszeit
`time_t mktime(struct tm *tptr);` | Umwandlung der in strukturierter Darstellung vorliegenden Ortszeit `*tptr` in die implementierungsspezifische Darstellung der Kalenderzeit. Die Werte der einzelnen Strukturkomponenten müssen nicht normiert sein. Die Funktion führt zusätzlich ihre Normierung durch und berechnet die Werte für die Komponenten `tm_wday` und `tm_yday`. Falls keine darstellbare Kalenderzeit ermittelt werden kann erzeugt die Funktion den Wert (time_t)-1.

- Funktionen zur Darstellung von Zeiten als Strings

`char *asctime(const struct tm *tptr);` | Umwandlung der in strukturierter Darstellung vorliegenden Ortszeit `*tptr` in einen String der folgenden Form : `Sun Apr 14 11:23:22 1991\n\0` Funktionswert ist Pointer auf diesen String
`char *ctime(const time_t *tp);` | Umwandlung der in implementierungsspezifischer Darstellung vorliegen den Kalenderzeit `*tp` in einen String der folgenden Form: `Sun Apr 14 11:23:22 1991\n\0` Funktionswert ist Pointer auf diesen String. `ctime(tp)` ist äquivalent zu `asctime(localtime(tp))`.
`size_t strftime(char *s, size_t smax, const char *fmt, const struct tm *tptr);` | Ablage von in `*tptr` enthaltenen Zeit- und Datumsinformationen in den String s gemäß den im String `fmt` enthaltenen Formatspezifikationen. Jeder sonstiger Text in `fmt` wird nach s übernommen. Der String s darf maximal smax Zeichen lang werden. Funktionswert : Anzahl der in s abgelegten Zeichen (ohne abschließendes '\0'), wenn diese <=smax ist, 0, wenn s länger als smax Zeichen werden würde. Der Inhalt von s ist in diesem Fall undefiniert Anm.: In TURBO-C 2.0 nicht realisiert
Format-Spezifier für `strftime()` | `%a` abgekürzter Name des Wochentags `%A` ausgeschriebener Name des Wochentags `%b` abgekürzter Name des Monats `%B` ausgeschriebener Name des Monats `%c` geeignete Darstellung der lokalen Zeit (Datum und Zeit) `%d` Tag des Monats als Dezimalzahl (01 .. 31) `%H` Stunde als Dezimalzahl (24-Std-Uhr) (00 .. 23) `%I` Stunde als Dezimalzahl (12-Std-Uhr) (01 .. 12) `%j` Tag des Jahres als Dezimalzahl (001 .. 386) `%m` Monat als Dezimalzahl (01 .. 12) `%M` Minute als Dezimalzahl (00 .. 59) `%p` PM oder AM (oder landesspezifisches Äquivalent) `%S` Sekunde als Dezimalzahl (00 .. 59) `%U` Woche des Jahres als Dezimalzahl (Sonntag ist der erste Tag der Woche) (00 .. 53) `%w` Wochentag als Dezimalzahl (Sonntag = 0) (0 .. 6) `%W` Woche des Jahres als Dezimalzahl (Montag ist der erste Tag der Woche) (00 .. 53) `%x` geeignete des lokalen Datums `%X` geeignete Darstellung der lokalen Zeit `%y` (Jahr - Jahrhundert) als Dezimalzahl (00 .. 99) `%Y` Jahr als Dezimalzahl (einschließlich Jahrhundert) `%Z` Name der Zeitzone, falls ein Name existiert `%%` Das Zeichen %

`clock_t clock(void);` | Ermittlung der Prozessorzeit, die seit Programmstart vergangen ist, in Systemzeit-Perioden (ticks). `clock()/CLK_TCK` ist die Zeit in Sekunden. Funktionswert = (clock_t)-1, wenn Prozessorzeit nicht verfügbar ist.
`time_t time(time_t *timer);` | Ermittlung der Kalenderzeit in einer implementierungsspezifischen Darstellung. Falls timer!=NULL wird der Funktionswert auch `*timer` zugewiesen. Funktionswert = (time_t)-1, wenn Kalenderzeit nicht verfügbar ist.
`double difftime(time_t t2, time_t t1);` | Bildung der Zeitdifferenz t2 - t1 ausgedrückt in Sekunden (t2 und t1 enthalten Zeiten in implementierungsspezifischer Darstellung !)

### Demonstrationsbeispiel zu den ANSI-C-Datums- und Zeitfunktionen

    /*--------------------------------------------------------------------*/
    /* Programm TIMEDEMO                                                  */
    /*--------------------------------------------------------------------*/
    /* Demonstrationsprogramm zur Verwendung der Zeit- und Datumsfunk-    */
    /* tionen der ANSI-C-Standardbibliothek                               */
    /*--------------------------------------------------------------------*/

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

    void main(void)
    {
      time_t zeit1,zeit2;
      clock_t clk;
      double ctsec, ttdiff;
      struct tm *lzeit;
      char zeit_str[80];
      void proc_cal_time_out(clock_t, time_t);
      void warten(void);
      void loc_zeit_aus(struct tm *);

      clk=clock();
      zeit1=time(NULL);
      proc_cal_time_out(clk,zeit1);
      warten();
      clk=clock();
      zeit2=time(NULL);
      proc_cal_time_out(clk, zeit2);
      ctsec=clk/CLK_TCK;
      ttdiff=difftime(zeit2, zeit1);
      printf("\nWartezeit : %f Sek. (ermittelt über Prozessorzeit)",ctsec);
      printf("\n            %f Sek. (ermittelt über Kalenderzeit)\n",ttdiff);
      lzeit=localtime(&zeit2);
      loc_zeit_aus(lzeit);
      strcpy(zeit_str,asctime(lzeit));
      printf("\nZeit-String : %s\n",zeit_str);
    }

    void proc_cal_time_out(clock_t ct, time_t tt)
    {
      printf("\nProzessorzeit = %ld Ticks",ct);
      printf("\nKalenderzeit  = %ld\n",tt);
    }

    void warten(void)
    {
      printf("\nWarten bis Eingabe CR ");
      getchar();
    }

    void loc_zeit_aus(struct tm *ltp)
    {
      printf("\nOrtszeit : Jahr   = %d",ltp->tm_year);
      printf("\n           Monat  = %d",ltp->tm_mon);
      printf("\n           Tag    = %d",ltp->tm_mday);
      printf("\n           Zeit   = %d:%d:%d",ltp->tm_hour,ltp->tm_min,
                                                                    ltp->tm_sec);
      printf("\nSommerzeit-Flag   : %d\n",ltp->tm_isdst);
    }

Bildschirmausgabe :

        G:\>timedemo

        Prozessorzeit = 0 Ticks
        Kalenderzeit  = 671662140

        Warten bis Eingabe CR

        Prozessorzeit = 39 Ticks
        Kalenderzeit  = 671662142

        Wartezeit : 2.142857 Sek. (ermittelt über Prozessorzeit)
                    2.000000 Sek. (ermittelt über Kalenderzeit)

        Ortszeit : Jahr   = 91
                   Monat  = 3
                   Tag    = 14
                   Zeit   = 16:49:2
                   Sommerzeit-Flag   : 1

        Zeit-String : Sun Apr 14 16:49:02 1991

        G:\>

### Behandlung von Signalen in ANSI-C

- Das Auftreten besonderer - i.a. nicht vorhersehbarer - Ereignisse während der Programmausführung (Interrupts, Exceptions wie z.B. Division durch 0) wird - in der C-Terminologie - als Signal bezeichnet.
Zur Reaktion auf derartige Signale sind vom Betriebssystem bzw von der C-Implementierung üblicherweise entsprechende Routinen (Interrupt Service Routine) vorgesehen. In der Regel führen diese nach einer entsprechenden Fehlermeldung zu einem Programmabbruch.
- In der Standard-Header-Datei `<signal.h>` sind Funktionen, Makros und Konstanten deklariert bzw definiert, mit deren Hilfe in einem Programm eigene Bearbeitungsroutinen als Reaktion auf das Auftreten von Signalen installiert werden können.
Bei Verwendung dieser Möglichkeit ist daher die Header-Datei mittels
`#include <signal.h>`
einzubinden.
- Im einzelnen sind in `<signal.h>` definiert :

- Funktion `void (*signal(int sig, void (*handler)(int)))(int);`

Sie dient zur Installation der eigenen Routine handler die als Reaktion auf das Signal sig ausgeführt werden soll

- Funktion `int raise(int sig);`

Sie dient zur Erzeugung des Signales sig (--> SW-Interrupt)

- Als Makros definierte Funktions-Pointer, die als Parameter bzw Funktionswert von signal() dienen können

`SIG_DFL` | Default-Bearbeitung des Signales (durch die vom System vorgesehene Bearbeitungsroutine) (Parameter von `signal()`)
`SIG_IGN` | Kennzeichnung, daß das Signal ignoriert werden soll (Parameter von `signal()`)
`SIG_ERR` | Kennzeichnung, daß `signal()` mit einem Fehler beendet wurde (Funktionswert von `signal()`)

- Konstante zur Verwendung als Signalwerte

`SIGABRT` | annormale Programmbeendigung (z.B. durch `abort()`)
`SIGFPE` | Floating Point Exception (z.B. Division durch 0)
`SIGILL` | unzulässiger Op-Code (illegal Instruction)
`SIGINT` | Programmunterbrechung von außen (interrupt)
`SIGSEGV` | unzulässiger Speicherzugriff (segment violation)
`SIGTERM` | Programmabbruch von außen (termination request)

- Es können weitere Konstante für implementierungsspezifische Signalwerte definiert sein.

- Die Standardbibliotheks-Funktion signal()

    /*------------------------------------------------------------------*/
    /* signal   - Installation einer eigenen Signalbearbeitungs-Routine */
    /*------------------------------------------------------------------*/

    #include <signal.h>        /* enthält Funktionsdeklaration */

    void (*signal(int sig, void (*handler)(int)))(int);

             /* sig        "Wert" des Signals, für das die Routine in-
                           stalliert werden soll                        */
             /* handler    Pointer auf die zu installierende Bearbei-
                           tungsroutine (Funktion)                      */

Die Funktion signal installiert die durch den Pointer handler gekennzeichnete Funktion als neue Bearbeitungsroutine für das Signal sig

Die Funktion, auf die handler zeigt, muß eine Funktion sein, die einen int-Parameter erwartet und keinen Funktionswert erzeugt.

Für handler kann auch übergeben werden :

`SIG_DFL` | --> | die implementierungsabhängige Default-Routine soll ausgeführt werden
`SIG_IGN` | --> | das Signal soll ignoriert werden

Der Funktionswert von signal ist ein Pointer auf eine Funktion, die einen int-Parameter hat und keinen Funktionswert liefert.

Funktionswert :

- Pointer auf die alte Routine zur Bearbeitung des Signals, falls die Installation der neuen Routine erfolgreich war
- `SIG_ERR`, falls ein Fehler aufgetreten ist.
In diesem Fall wird außerdem die Fehlervariable errno entsprechend gesetzt.

Anmerkungen :

- Bei Auftreten des durch sig gekennzeichneten Signals wird die installierte Routine mit sig als Parameter aufgerufen
- Nach einer Ausführung der neu installierten Routine wird die alte Routine wieder hergestellt.

--> | Soll eine neu installierte Routine "auf Dauer" wirksam sein, so muß sie jeweils erneut wieder mit signal installiert werden.

### Beispiel für die Installation einer Signalbehandlungs-Routine in ANSI-C

    /*--------------------------------------------------------------------*/
    /* Programm SIGDEM2                                                   */
    /*--------------------------------------------------------------------*/
    /* Demonstrationsprogramm zur Signalbehandlung mit den Möglichkeiten  */
    /* der ANSI-C-Standardbibliothek                                      */
    /*--------------------------------------------------------------------*/
    /* Ausschalten des User-Breaks mit CTRL-C bzw CTRL-BREAK              */
    /*--------------------------------------------------------------------*/

    #include <signal.h>
    #include <stdio.h>

    void main(void)
    {
      int c;
      void meldung(int);

      signal(SIGINT,meldung);
      while ((c=getchar())!=EOF)
        putchar(c);
    }

    void meldung(int err)
    {
      if (err==SIG_INT)
      { printf("\nUser-Break ausgeschaltet\n");
        signal(SIGINT,meldung);
        return;
      }
      else
      { printf("\nunbekannter Fehler\n");
        exit(1);
      }
    }

## 7.7 Externe ASM-Module TURBO/BORLAND-C (C-Assembler-Interface)

### C-Assembler-Interface

- C unterstützt die modulare Programmentwicklung

| --> | getrennte Erstellung und Compilierung einzelner Programmteile (Module) Zusammenbinden der erzeugten Code-Module (zusammen mit dem vom System bereitgestellten Startup-Code-Modul und den Bibliotheksroutinen) durch den Linker.

- Einzelne der zusammenzubindenden Code-Module können auch aus anderen Sprachen übersetzt worden sein.
- Insbesondere kann es interessant bzw u.U. sogar notwendig sein, in Assembler geschriebene Module (ASM-Module) zu verwenden.
Gründe für das Einbinden von ASM-Modulen :

- Erhöhung der Programmeffizienz (Laufzeit, durch Code belegter Speicherplatz !)
- Realisierung hardwarenaher Funktionen, die in C nicht realisierbar sind
- Verwendung bereits vorliegender - von dritter Stelle bereitgestellter - ASM-Routinen

- Bei der Verwendung von ASM-Modulen in C-Programmen sind zwangsläufig prozessor- und compilerspezifische Aspekte zu berücksichtigen.
Grundsätzlich sind zu beachten

- die vom jeweiligen Compiler verwendete Aufrufkonvention für Funktionen :
i.a. wird das die C-Konvention sein :

- Parameterübergabe über Stack, Parameterablage von rechts nach links
- Entfernung der Parameter vom Stack durch die aufrufende Funktion

- die vom Compiler verwendete und vom Linker erwartete Schreibweise externer Namen (z.B. Groß-/Kleinschreibung, spezielles Anfangszeichen usw.)
- prozessorspezifische Gesichtspunkte bei der Arbeitsspeichernutzung.
Die Prozessoren der Intel-80x86-Familie weisen z.B. eine Segmentierung des Arbeitsspeichers auf.

--> | - richtige Wahl des Speichermodells - richtige Wahl der vom Compiler vorgegebenen Segmentnamen

- Manche C-Compiler stellen als Alternative zum Einbinden von ASM-Modulen die Möglichkeit der Verwendung von Assembler-Inline-Code zur Verfügung: ASM-Anweisungen lassen sich - durch ein spezielles Schlüsselwort einge- leitet - direkt in den C-Quellcode einfügen.
z.B. TURBO-C : `asm`-Anweisung (nur in Kdo-zeilenversion verfügbar)

z.B.: `asm mov ax, word ptr [bp+4]`

### Die Speichermodelle von TURBO-

###

### Externe ASM-Module in Turbo-C-Programmen Struktur eines einbindbaren ASM-Moduls

- Verwendung der Standard-Segmentanweisungen

- Speichermodelle Tiny, Small und Compact

`DGROUP` | `GROUP _DATA, _BSS`
`_DATA` | `SEGMENT WORD PUBLIC 'DATA'` ; initialisierte Daten
`_DATA` | `ENDS`
`_BSS` | `SEGMENT WORD PUBLIC 'BSS'` ; nicht-initialisierte Daten
`_BSS` | `ENDS`
| nur notwendig, wenn tatsächlich entsprechende Daten im ASM-Modul vorhanden sind, bzw. zu entsprechenden Daten des C-Programms zugegriffen wird
`_TEXT` | `SEGMENT BYTE PUBLIC 'CODE'` `ASSUME CS:_TEXT, DS:DGROUP` ; Programm-Code
`_TEXT` | `ENDS`
| `END`

- Speichermodelle Medium und Large
wie bei Speichermodellen Tiny, Small und Compact, nur der Name des Codesegments muß `<DN>_TEXT`*) statt `_TEXT` lauten.

*)`<DN>` - Dateihauptname der ASM-Quell-Datei

- Speichermodell Huge

`<DN>_DATA` | `SEGMENT WORD PUBLIC 'DATA'` ; initialisierte Daten ; nicht-initialisierte Daten
`<DN>_DATA` | `ENDS`
| nur notwendig, wenn tatsächlich entsprechende Daten im ASM-Modul vorhanden sind, bzw. zu entsprechenden Daten des C-Programms zugegriffen wird
`<DN>_TEXT` | `SEGMENT BYTE PUBLIC 'CODE'` `ASSUME CS:<DN>_TEXT, DS:<DN>_DATA` ; Programm-Code
`<DN>_TEXT` | `ENDS`
| `END`

- Verwendung der vereinfachten Segmentanweisungen
alle Speichermodelle

`.MODEL` speichermodell

`.DATA`
;initialisierte Daten

`.DATA?`
;nicht-initialisierte Daten

`.CODE`
; Programm-Code

`END`

- Im ASM-Modul definierte Funktionen und Variable, die im Turbo-C-Programm verwendet werden, müssen

- im ASM-Modul als `PUBLIC`
- und im C-Programm als `extern`

deklariert werden.
- Im C-Programm definierte Funktionen und - globale - Variable, auf die im ASM-Modul zugegriffen werden soll, müssen

- im ASM-Modul als `EXTRN`

deklariert werden :

- für Variable :
`EXTRN <variablenname> : <typ>`

`<typ>` kann sein :

`BYTE` | (ein Byte - char)
`WORD` | (zwei Bytes - int, near pointer)
`DWORD` | (4 Bytes - long, float, far pointer)
`QWORD` | (8 Bytes - double)
`TBYTE` | (10 Bytes - long double)

Bei Array-Variablen richtet sich `<typ>` nach dem Komponententyp

Ort der `EXTRN`-Deklaration für Variable :

- bei Speichermodell Huge | : | außerhalb jedes Segments
- bei allen anderen Speichermodellen | : | innerhalb des Datensegments

- für Funktionen :
`EXTRN <funktionsname> : <call_typ>`

`<call-typ>` kann sein :

`NEAR`
`FAR`

Ort der `EXTRN`-Deklaration für Funktionen :

- für `NEAR`-Funktionen | : | innerhalb des Codesegments
- für `FAR`-Funktionen | : | außerhalb jedes Segments

- Da der C-Compiler allen Bezeichnern einen Underscore ('_') voranstellt, der Assembler aber nicht, müssen die entsprechenden Bezeichner im ASM- Modul mit einem '_' beginnen.
- Da der C-Compiler und der C-Linker zwischen Groß- und Kleinbuchstaben unterscheiden (case sensitive),

- muß auch im ASM-Modul auf diese Unterscheidung geachtet werden
- und muß das ASM-Modul mit der Option /mx (case sensitive für Extern-Referenzen) übersetzt werden:

TASM /mx aname bzw. MASM /mx aname;

- Beispiel für Extern-Referenzen :

im C-Programm :

    extern int max (int, int);
    extern int MAXINT, last;

    int vergleich(int v1, int v2)
    {
      ...
    }

    int zaehler, ifeld[10];
    char zeichen;

im ASM-Modul :

`DGROUP` | `GROUP _DATA`
`_DATA` | `SEGMENT WORD PUBLIC 'DATA'` `EXTRN _zaehler:WORD, _ifeld:WORD, _zeichen:BYTE` `PUBLIC _MAXINT, _last`
`_MAXINT` | `DW 32767`
`_last` | `DW 0`
`_DATA` | `ENDS`
`_TEXT` | `SEGMENT BYTE PUBLIC 'CODE'` `ASSUME CS:_TEXT, DS:DGROUP` `EXTRN _vergleich:NEAR` `PUBLIC _max`
`_max` | `PROC NEAR` ; Programmcode `CALL _vergleich` ; weiterer Programmcode
`_max` | `ENDP`
`_TEXT` | `ENDS`
| `END`

- Jede aus einem ASM-Modul erzeugte und einzubindende OBJ-Datei ist - zusammen mit der C-Quell-Datei - in einer Project-Datei anzugeben.
Der Name der Projekt-Datei bestimmt den Namen der durch den Linker erzeugten EXE-Datei.

Beispiel:

Name der Project-Datei | : | `pname.prj`
Inhalt der Project-Datei | : | `cname` `aname.obj`
Name der erzeugten EXE-Datei | : | `pname.exe`

### Parameterübergabe in Turbo-C-Funktionen

- Die Parameterübergabe erfolgt standardmäßig nach der C-Konvention :
Parameter werden an Funktionen über den Stack übergeben.
Ablage der Parameter in entgegengesetzter Reihenfolge der Auflistung in der Parameterliste (d.h. von rechts nach links).

Entfernung der Parameter durch die aufrufende Funktion

- Durch die Parameter belegter Platz :

`char` | 1 Wort | (2 Bytes)
`int, short` | 1 Wort | (2 Bytes)
`long` | 2 Worte | (4 Bytes)
`float` | 2 Worte | (4 Bytes)
`double` | 4 Worte | (8 Bytes)
`long double` | 5 Worte | (10 Bytes)
`near pointer` | 1 Wort | (2 Bytes)
`far pointer` | 2 Worte | (4 Bytes)

- Der Zugriff zu den Parametern innerhalb der Funktion sollte mittels indizierter Adressierung über den Base Pointer (BP) erfolgen.

Code zu Beginn der Funktion :

`push bp` | ; Sicherung alter BP-Wert
`mov bp,sp` | ; Kopieren SP --> BP

Code am Ende der Funktion :

`pop bp` | ; Wiederherstellen BP
`ret` | ; Rücksprung

- Beispiel : `extern int max(int v1, int v2);`

Stack :

ASM-Code :

`_max` | `PROC NEAR` |
| `push bp` | ;Sicherung alter BP-Wert
| `mov bp,sp` | ;Kopieren SP --> BP
| `push ...` | ;Sicherung weiterer Register *)
| `...` |
| `mov ax,[bp+4]` | ;Zugriff zu Parameter v1
| `...` |
| `pop ...` | ;Wiederherstellen gerett. Reg.
| `pop bp` | ;Wiederherstellen BP
| `ret` | ;keine Entfernung von Parametern
`_max` | `ENDP` |  |

*) | neben BP müssen - nur bei Verwendung - lediglich SI und DI sowie die Segmentregister gerettet werden.

### Funktionswertübergabe in TURBO-C

Funktionswertübergabe : | i.a. über Prozessorregister (Ausnahmen s. unten)

### Beispiel für externes ASM-Modul in einem Turbo-C-Programm

- C-Quell-Datei `bspcasm.c`

    /*-----------------------------------------------------------------*/
    /* Programm BSPCASM                                                */
    /*-----------------------------------------------------------------*/
    /* Beispiel für externe ASM-Module in Turbo-C-Programmen           */
    /* Ermittlung des Maximums zweier Integerzahlen                    */
    /*-----------------------------------------------------------------*/

    #include <stdio.h>

    extern int max(int v1, int v2);

    void main(void)
    { int z1, z2;
      printf("\n2 Integerzahlen ? ");
      scanf("%d%d",&z1,&z2);
      printf("Maximum         : %d",max(z1,z2));
    }
    /*-----------------------------------------------------------------*/

- ASM-Quell-Datei `max.asm`

    ;--------------------------------------------------------------------
    ; Externes ASM-Modul zum Turbo-C-Programm BSPCASM
    : Übersetzen mit :          TASM /mx max
    ;--------------------------------------------------------------------
    ; Function Prototype : int max(int v1,int v2);
    ;
    ; Funktionswert: Maximum von v1 und v2
    ;--------------------------------------------------------------------

    _TEXT SEGMENT BYTE PUBLIC 'CODE'
          ASSUME CS:_TEXT
          PUBLIC _max

    _max  PROC NEAR
          push bp
          mov bp,sp
          mov ax,[bp+4]        ; v1 --> AX
          cmp ax,[bp+6]        ; Vergleich (AX) mit v2
          jg ende              ; v1 > v2
          mov ax,[bp+6]        ; v2 > v1
    ende: pop bp
          ret
    _max  ENDP

    _TEXT ENDS
          END

    ;--------------------------------------------------------------------

- Project-File `bspcasm.prj`

    bspcasm
    max.obj

Zum Inhaltsverzeichnis | Zum nächsten Abschnitt

###### Copyright © FH München, FB 04, Prof. Dr. Rainer Thomas
V - PC - 711 - 01 - TH - 02
V - PC - 711 - 02 - TH - 05
V - PC - 712 - 01 - TH - 05
V - PC - 712 - 02 - TH - 04
V - PC - 713 - 01 - TH - 02
V - PC - 713 - 02 - TH - 02
V - PC - 721 - 00 - TH - 03
V - PC - 722 - 00 - TH - 08
V - PC - 731 - 01 - TH - 03
V - PC - 731 - 02 - TH - 05
V - PC - 732 - 01 - TH - 03
V - PC - 732 - 02 - TH - 04
V - PC - 733 - 01 - TH - 03
V - PC - 733 - 02 - TH - 04
V - PC - 733 - 03 - TH - 02
V - PC - 734 - 01 - TH - 02
V - PC - 734 - 02 - TH - 03
V - PC - 740 - 00 - TH - 02
V - PC - 741 - 00 - TH - 03
V - PC - 751 - 01 - TH - 02
V - PC - 751 - 02 - TH - 01
V - PC - 752 - 00 - TH - 05
V - PC - 761 - 01 - TH - 02
V - PC - 761 - 02 - TH - 02
V - PC - 761 - 03 - TH - 01
V - PC - 762 - 01 - TH - 01
V - PC - 762 - 02 - TH - 02
V - PC - 763 - 01 - TH - 01
V - PC - 763 - 02 - TH - 01
V - PC - 764 - 02 - TH - 02
V - PC - 764 - 03 - TH - 02
V - PC - 764 - 06 - TH - 02
V - PC - 764 - 07 - TH - 01
V - PC - 764 - 09 - TH - 02
V - PC - 764 - 0A - TH - 01
V - PC - 765 - 01 - TH - 01
V - PC - 765 - 02 - TH - 01
V - PC - 765 - 03 - TH - 01
V - PC - 765 - 09 - TH - 01
V - PC - 766 - 01 - TH - 02
V - PC - 766 - 02 - TH - 02
V - PC - 766 - 03 - TH - 02
V - PC - 767 - 01 - TH - 01
V - PC - 767 - 02 - TH - 01
V - PC - 767 - 03 - TH - 01
V - PC - 768 - 01 - TH - 01
V - PC - 768 - 02 - TH - 01
V - PC - 768 - 03 - TH - 01
V - PC - 768 - 04 - TH - 01
V - PC - 768 - 05 - TH - 01
V - PC - 768 - 06 - TH - 01
V - PC - 769 - 01 - TH - 01
V - PC - 769 - 02 - TH - 01
V - PC - 769 - 03 - TH - 01
V - PC - 770 - 00 - TH - 01
V - PC - 771 - 00 - TH - 02
V - PC - 772 - 01 - TH - 04
V - PC - 772 - 02 - TH - 03
V - PC - 772 - 03 - TH - 04
V - PC - 773 - 00 - TH - 03
V - PC - 774 - 00 - TH - 02
V - PC - 775 - 00 - TH - 04