Zeiger

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

# 8. Zeiger

### 8.1 Eigenschaften von Zeigern
8.2 Zeiger als Parameter und Funktionswert
8.3 Dynamische Speicherallokation
8.4 Adressarithmetik
8.5 Verwandschaft zwischen Zeigern und Arrays
8.6 Mehrdimensionale Arrays und Pointer-Arrays
8.7 Kommandozeilenparameter in C
8.8 Zeiger auf Funktionen
8.9 Near- und Far-Zeiger in TURBO/BORLAND-C

## 8.1 Eigenschaften von Zeigern

### Zeiger (Pointer) in C

- Ein Zeiger (Pointer) ist eine Variable (Zeigervariable) oder eine Konstante (Zeigerkonstante) deren Werte Speicheradressen sind.
  Diese Speicheradressen sind
  - entweder (Anfangs-) Adressen von anderen Variablen - oder (Start-) Adressen von Funktionen

  --> Ein Zeiger z e i g t auf andere Variable oder auf Funktionen

- Mittels Zeiger können Variable indirekt über ihre Adresse (--> Referenz) angesprochen werden.
  (--> indirekte Adressierung in Assemblersprachen)

- Zeiger sind an bestimmte Objekt-Typen gebunden. D.h. ein Zeiger auf int-Werte sollte auch nur Adressen von int-Variablen als Werte annehmen.

  Ausnahme (ANSI-C) :

      generic pointer
                        void *

  kann auf beliebige Objekt-Typen zeigen.

- Im Zusammenhang mit Zeigern existieren zwei - zueinander inverse - unäre Operatoren :

  - `&` Adreßoperator
    liefert die Adresse eines Objektes
    z.B. `&x` --> Adresse der Variablen x

    Adressen können Zeigern zugewiesen werden. z.B.

    px sei Zeigervariable (auf int)
    x sei int-Variable
    --> `px = &x;`

    Achtung : nicht anwendbar auf Register-Variable !

  - `*` Objektoperator
    liefert das Objekt, auf das ein Zeiger zeigt
    --> Dereferenzierung
    z.B. `*px` --> Objekt, auf das Zeiger px zeigt

    Zeigerobjekte können wie unter Namen referenzierte Variable verwendet werden
    z.B.

    px sei Zeigervariable (auf int)
    y sei int-Variable

    --> | `y = *px; *px = y+1;`

- Vereinbarung von Zeigervariablen

  mittels Verwendung des Objektoperators.

  z.B.:

      int *px;   /* px wird als Zeiger auf int-Werte vereinbart    */
                 /* Interpretation : *px ist vom Typ int,
                    1         --> px ist ein Zeiger auf int */

    /*--------------------------------------------------------------------*/
    /* Programm POINDEM1                                                  */
    /*--------------------------------------------------------------------*/
    /* Einfaches Demonstrationsprogramm zu Zeigern                        */
    /*--------------------------------------------------------------------*/

    #include <stdio.h>

    void main(void)
    {
      int x=1, y, *px, *py;

      px=&x;                   /* Wertzuweisung an Zeiger mittels
                                  Adressoperator */

      *px=5;                   /* Modifikation des Zeigerobjekts */
      (*px)++;                 /* Klammern notwendig, wenn Zeigerobjekt */
                               /* modifiziert werden soll   */

      y=*px+1;                 /* Zeigerobjekt als Operand */

      printf("\nx = %d\n",x);
      printf("y = %d\n",y);

      py=px;                   /* Zuweisung des Werts eines anderen Zeigers */

      *py+=3;                  /* Modifikation des Zeigerobjekts */

      printf("\nWert des Zeigerobjekts von px : %d\n",*px);
      printf("Wert des Zeigerobjekts von py : %d\n",*py);
    }

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

Bildschirmausgabe :

    E:\RT\C\VORL>poindem1

    x = 6
    y = 7

    Wert des Zeigerobjekts von px : 9
    Wert des Zeigerobjekts von py : 9

    /*-------------------------------------------------------------------*/
    /* Programm POINDEM2                                                 */
    /*-------------------------------------------------------------------*/
    /* Demonstration der direkten Wertzuweisung an Zeiger                */
    /* sowie des Einlesens von Zeigerwerten                              */
    /*-------------------------------------------------------------------*/

    #include <stdio.h>

    void main(void)
    {
      char x='A',y='F',*px;
      int a;

      /* --1--------- Direkte Zuweisung eines Integerwertes ------------ */
      /* - Compiler gibt Warnung aus : Different levels of indirection - */
      px=0xffd8;
      printf("\n1)  Wert des Zeigerobjekts von px : %02x\n",*px);

      /* --2--------- Zuweisung eines mittels Cast-Operator ------------ */
      /* ------------      umgewandelten Integerwertes      ------------ */
      px=(char *)0xffd8;
      printf("\n2)  Wert des Zeigerobjekts von px : %02x\n",*px);

      /* --3----- Direkte Eingabe einer Adresse als Integerwert -------- */
      printf("\n3)  Adresse (Offset)    ? ");
      scanf("%x",&px);
      printf("    Inhalt dieser Zelle : %02x\n",*px);

      /* --4-------- Mittels Cast-Operator typgewandelte als ----------- */
      /* -----------     Integerwert eingelesene Adresse     ----------- */
      printf("\n4)  Adresse (Offset)    ? ");
      scanf("%x",&a);
      px=(char *) a;
      printf("    Inhalt dieser Zelle : %02x\n",*px);

      /* --5------ Direkte Eingabe einer Adresse als Zeigerwert -------- */
      /* ---------                 (ANSI-C !!!)                 -------- */
      printf("\n5)  Adresse (Offset)    ? ");
      scanf("%p",&px);
      printf("    Inhalt dieser Zelle : %02x\n",*px);
    }

Bilschirmausgabe :

    E:\RT\C\VORL>poindem2

    1)  Wert des Zeigerobjekts von px : 41

    2)  Wert des Zeigerobjekts von px : 41

    3)  Adresse (Offset)    ? ffd9
        Inhalt dieser Zelle : 46

    4)  Adresse (Offset)    ? ffd9
        Inhalt dieser Zelle : 46

    5)  Adresse (Offset)    ? ffd8
        Inhalt dieser Zelle : 41

    /*-------------------------------------------------------------------*/
    /* Programm POINDEM3                                                 */
    /*-------------------------------------------------------------------*/
    /* Demonstration der Ausgabe von Zeigerwerten                        */
    /*-------------------------------------------------------------------*/

    #include <stdio.h>

    void main(void)
    {
      char x='A',y='F',*ptr;
      int a;

      /* --1----- Ausgabe einer Adresse direkt als Integerwert --------- */
      /* --------               (sedezimal)                    --------- */
      /* --------------------------------------------------------------- */
      printf("\n1)  Adresse (Offset) von x : %04x\n",&x);
      ptr=&x;
      printf("    Adresse (Offset) von x : %04x\n",ptr);

      /* --2----- Ausgabe einer mittels Cast-Operator in einen --------- */
      /* -------- Integerwert (sedezimal)  gewandelten Adresse --------- */
      /* --------------------------------------------------------------- */
      printf("\n2)  Adresse (Offset) von y : %04x\n",(int)&y);
      ptr=&y;
      printf("    Adresse (Offset) von y : %04x\n",(int)ptr);

      /* --3----- Ausgabe einer Adresse direkt als Zeigerwert ---------- */
      /* --------                (ANSI-C !!!)                 ---------- */
      /* --------------------------------------------------------------- */
      printf("\n3)  Adresse (Offset) von x : %04p\n",&x);
      ptr=&x;
      printf("    Adresse (Offset) von x : %04p\n",ptr);
    }

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

Bildschirmausgabe :

    E:\RT\C\VORL>poindem3

    1)  Adresse (Offset) von x : ffd8
        Adresse (Offset) von x : ffd8

    2)  Adresse (Offset) von y : ffd9
        Adresse (Offset) von y : ffd9

    3)  Adresse (Offset) von x : FFD8
        Adresse (Offset) von x : FFD8

## 8.2 Zeiger als Parameter und Funktionswert

- Zeiger als Funktionsparameter werden dann benötigt, wenn eine Funktion mehr als einen Wert "erzeugen" soll.

  --> | Ausgabeparameter sind erforderlich. Diese lassen sich z.B. in PASCAL durch VAR-Parameter realisieren

  Da C nur Wert-Parameter kennt, müssen in solchen Fällen die Adressen der Variablen, die die erzeugten Werte aufnehmen sollen, als Werte übergeben werden.
  Durch De-Referenzierung dieser Adressen kann die Funktion dann den entsprechenden Variablen Werte zuweisen.

      /* ---------------------------------------------------------------- */
      /* Funktion tausch()                                                */
      /* ---------------------------------------------------------------- */
      /* Beispiel für Zeiger als Parameter in Funktionen                  */
      /* Vertausch zweier int-Werte                                       */
      /* ---------------------------------------------------------------- */
      /* Beispiel fuer Aufruf :     tausch(&x,&y)                         */
      /* ---------------------------------------------------------------- */

      void tausch(int *a, int *b)
      {
        int hilf;

        hilf=*a;
        *a=*b;
        *b=hilf;
      }
      /* ---------------------------------------------------------------- */

- Eine Funktion kann auch einen Zeiger als Funktionswert liefern :

      /* ---------------------------------------------------------------- */
      /* Funktion maxelement()                                            */
      /* ---------------------------------------------------------------- */
      /* Beispiel für Zeiger als Funktionswert                            */
      /* Ausgabe eines Zeigers auf das größte Element eines double-Arrays */
      /* ---------------------------------------------------------------- */

      double *maxelement(double a[], int n)
      {
        int i,m;
        double maxwert;

        for (maxwert=a[0], m=0, i=1; i<=n; i++)
          if (a[i]>maxwert)
          { maxwert=a[i];
            m=i;
          }
        return &a[m];
      }
      /* ---------------------------------------------------------------- */

## 8.3 Dynamische Speicherallokation

### Dynamische Speicherallokation in C

- Datenobjekte (Variable), die in einem Programm definiert werden, sind statische Objekte.
  Der Compiler sorgt für die Bereitstellung von Speicher für diese Objekte. Ihre Anzahl und Größe (Speicherbedarf !) muß daher zum Zeitpunkt der Compilierung festliegen.

- vielen Fällen läßt sich aber die Größe eines Objektes (z.B. Arrays) bzw. die Anzahl der Objekte erst zur Programmlaufzeit angeben, wobei sich deren gesamte Struktur auch noch dynamisch während der Programmabarbeitung ändern kann.
  Eine statische Bereitstellung von Speicherplatz der erwarteten maximalen Größe stellt in solchen Fällen nur eine Notlösung dar. Denn erstens ist damit i.a. eine mehr oder weniger große Speicherplatzverschwendung verbunden und zweitens kann sich die dadurch getroffene Größenbegrenzung in der Zukunft als zu gering herausstellen.

- Zur sinnvollen Lösung des o.a. Problems bietet sich die dynamische Speicherallokation (Dynamic Storage Allocation) an :
  Der für ein Objekt benötigte Speicherplatz wird nicht statisch durch Vorkehrungen des Compilers sondern erst bei Bedarf während der Programmabarbeitung bereitgestellt.

  Derartige - dynamisch allokierte - Objekte können nicht wie statische Objekte definiert werden.
  Sie werden auch nicht über einen Namen, sondern nur über ihre Adresse angesprochen. Diese Adresse kann ihrerseits einer statischen Pointer- Variablen, die wie üblich zu definieren ist, zugewiesen werden.

- Die dynamische Speicherbereitstellung erfolgt mittels spezieller in der Standardbibliothek enthaltener Speicherallokationsfunktionen :

  `malloc()` | Allokation eines Speicherblocks bestimmter Größe
  `calloc()` | Allokation eines Speicherblocks für eine bestimmte Anzahl von Objekten einer bestimmten Größe, Initialisierung aller Bytes des Speicherblocks mit 0
  `realloc()` | Veränderung der Größe eines allokierten Speicherblocks

  Diese Funktionen liefern die Anfangsadresse des allokierten Blocks

  - in ANSI-C als `void`-Pointer (`void *`)
    --> kein Type-Cast bei Zuweisung an Pointer-Variable erforderlich
  - in KuR-C als `char`-Pointer (`char *`)
    --> Type-Cast bei Zuweisung an Pointer-Variable erforderlich

- Der für die dynamische Speicherverwaltung zur Verfügung stehende Speicherbereich wird als Heap bezeichnet.
  Bei TURBO-C (MS-DOS) hängt die Lage und Größe dieses Bereichs vom Speichermodell ab :
  - Teil des Datensegments zwischen statischen Daten und Stack
    (Speichermodelle Tiny, Small, Medium)
  - gesamter restlicher Arbeitsspeicher (Far Heap)
    (Speichermodelle Compact, Large, Huge)

- Die Lebensdauer dynamisch allokierten Speichers ist nicht an die Aus- führungszeit eines Blocks gebunden.

  --> | nicht mehr benötigter dynamisch allokierter Speicher ist explizit freizugeben. Hierfür dient die Bibliotheksfunktion `free()`

### Funktionen der ANSI-C-Std-Bibliothek zur dynamischen Speicherverwaltung

    /* -------------------------------------------------------------------- */
    /*          malloc          -      Allokation eines Speicherblocks      */
    /* -------------------------------------------------------------------- */

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

    void *malloc(size_t size);

    /*  size    Laenge des zu allokierenden Blocks in Bytes  */

Anm:
  Der Typ size_t ist der "natürliche" unsigned Typ
  (u.a. in <stdlib.h> definiert)

`malloc` allokiert einen Speicherblock von wenigstens der Länge `size` Bytes (der tatsächlich allokierte Speicherblock kann infolge evtl. notwendiger Speicherplatzausrichtung größer sein)

Funktionswert :
  - Pointer auf den allokierten Speicherblock, falls genügend Speicherplatz frei ist,
  - bzw. NULL-Pointer, falls Speicherpl. nicht ausreicht

    /* -------------------------------------------------------------------- */
    /*     calloc  -  Allokation und Initialisierung eines Speicherblocks   */
    /* -------------------------------------------------------------------- */

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

    void *calloc(size_t nobj, size_t size);

      /*  nobj    Anz. Objekte, für die Speicherplatz allokiert werden soll */
      /*  size    Laenge eines Objekts in Bytes                             */

`calloc` allokiert einen Speicherblock von wenigstens der Länge `nobj` `size` Bytes (der tatsächlich allokierte Speicherblock kann infolge evtl. notwendiger Speicherplatzausrichtung größer sein) und initialisiert alle Bytes mit 0.

Funktionswert :
  - Pointer auf den allokierten Speicherblock, falls genügend Speicherplatz frei ist,
  - bzw. NULL-Pointer, falls freier Speicherplatz nicht ausreicht

    /* -------------------------------------------------------------------- */
    /*          free            -      Freigabe von Speicherplatz           */
    /* -------------------------------------------------------------------- */

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

    void free(void *ptr);

           /* ptr       Zeiger auf allokierten Speicherblock */

`free` gibt den - zuvor allokierten - Speicherblock, auf den `ptr` zeigt, wieder frei.

Funktionswert :
  keiner

### Beispiel zur dynamischen Speicherallokation in C

    /* ------------------------------------------------------------------- */
    /* Programm MALDEM                                                     */
    /* ------------------------------------------------------------------- */
    /* Demonstrationsprogramm zur Verwendung der Bibliotheksfunktionen zur */
    /* dynamischen Speicherverwaltung                                      */
    /*                                                                     */
    /* Einlesen, Sortieren und Ausgeben einer beliebigen Anzahl von        */
    /* double-Werten                                                       */
    /*                                                                     */
    /* Die Funktionen zum Einlesen, Sortieren und Ausgaben eines Feldes    */
    /* von double-Werten stehen in einem getrennt zu übersetzenden Modul   */
    /* zur Verfügung                                                       */
    /* ------------------------------------------------------------------- */

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

    #define DBL_SIZ   8              /* Länge eines Double-Wertes in Bytes */

    extern void dfeld_ein(double *, unsigned);
    extern void dfeld_sort(double *, unsigned);
    extern void dfeld_aus(double *, unsigned);

    void main(void)
    {
      int anz;
      double *dfeld;

      printf("\nAnzahl der double-Werte ? ");
      scanf("%d", &anz);
      if (anz>0)
        if ((dfeld=malloc(anz*DBL_SIZ)) == NULL)
          printf("\nSpeicherplatz reicht nicht aus !\n");
        else
        { dfeld_ein(dfeld, anz);
          dfeld_sort(dfeld, anz);
          dfeld_aus(dfeld, anz);
          free(dfeld);
        }
      else
        printf("\nAnzahl muß >0 sein !\n");
    }

## 8.4 Adressarithmetik

### Zeigerarithmetik in C

Neben der Zuweisung und der Objektbildung lassen sich auf Zeiger in C die folgenden Operationen anwenden:

- Addition eines ganzzahligen Wertes (einschließlich Increment)

  Addition von n zu einem Zeiger liefert als neuen Zeigerwert die Adresse, die um den Speicherplatz für n Objekte seines Objekttyps größer als sein ursprünglicher Wert ist.

  ---> | Compiler nimmt Skalierung von n entsprechend der Größe des Objekttyps vor (Portabilität !).

  z.B.

      int *px;

      px=px+4;     /* jeweils Erhöhung von px um den Speicherplatz */
      px+=4;       /* von 4 int-Werten                             */

      px++;        /* Erhöhung von px um den Speicherplatz für     */
                   /* einen int-Wert                               */

- Subtraktion eines ganzzahligen Wertes (einschließlich Decrement)

  Wirkung analog zur Addition.

  z.B.

      double *pd;

      pd=pd-2;     /* Erniedrigung von pd um den Speicherplatz für */
                   /* 2 double-Werte                               */

- Subtraktion von zwei Zeigern, die auf Objekte des gleichen Typs zeigen

  Die Operation liefert die Anzahl der Objekte, um die die Werte der beiden Zeiger voneinander entfernt sind.

  Sinnvoll vor allem auf Zeiger anzuwenden, die auf Elemente des gleichen Arrays zeigen.
  ---> Anzahl der zwischen den Zeigerobjekten liegenden Elemente + 1

  z.B. alternative Formulierung der Funktion strlen

      int strlen(char kette[])   /* liefert Länge des Strings kette */
      {
        char *anf = &kette[0];       /*       anf -->   'h'         */
        char *end = anf;             /*                 'a'         */
        while (*end)                 /*                 'l'         */
          end++;                     /*                 'l'         */
        return(end-anf);             /*                 'o'         */
      }                              /*       end -->   '\0'        */

- Vergleich von zwei Zeigern, die auf Objekte des gleichen Typs zeigen

  Sinnvoll nur für Zeiger, die auf Elemente des gleichen Arrays zeigen.

- Vergleich eines Zeigers mit dem Wert 0

### Beispiel zur Zeigerarithmetik und zu statisch-globalen Variablen in C

    /* ------------------------------------------------------------------ */
    /* Modul ALLOCMEM                                                     */
    /* ------------------------------------------------------------------ */
    /* Einfache Funktionen zur dynamischen Speicherverwaltung :           */
    /* Allokation und Freigabe von Speicherplatz                          */
    /*                                                                    */
    /* Der dynamisch verwaltete Speicher steht als "privates" char-Array  */
    /* (Speicherklasse static !) zur Verfügung                            */
    /* Verwaltung erfolgt stack-ähnlich :                                 */
    /* Freigabe der allokierten Speicherblöcke muß in umgekehrter Reihen- */
    /* folge ihrer Allokation erfolgen                                    */
    /* ------------------------------------------------------------------ */

    #define NULLPOINT  0                 /* Null-Pointer */
    #define LAENGE 4096

    static char allocbuf[LAENGE];        /* verfuegbarer Speicherplatz */
    static char *allocp=&allocbuf[0];    /* Zeiger auf jeweils naechste
                                            verfuegbare Speicherzelle  */

    char *alloc(int n)         /* liefert Zeiger auf Bereich fuer n Zeichen */
    {
      if (allocp+n <= &allocbuf[0]+LAENGE)
      { allocp+=n;                       /* Speicher vorhanden */
        return(allocp-n);
      }
      else return(NULLPOINT);            /* Speicher nicht vorhanden  */
    }

    void back(char *p)    /* Rueckgabe des Speicherplatzes ab der Adresse p */
    {
      if (p>=&allocbuf[0] && p<&allocbuf[0]+LAENGE)
        allocp=p;
    }

## 8.5 Verwandschaft zwischen Zeigern und Arrays

### Pointer und Arrays in C

- Zwischen Pointern und Arrays besteht in C eine enge Verwandschaft :

      int a[10];       /* Definition eines int-arrays mit 10 Elementen */

  `a[0]` | --> | 1.Element von a
  `a[1]` | --> | 2.Element von a
  `a[i]` | --> | Element, das i Positionen vom Anfang entfernt ist

      int *pa;         /* Definition eines Pointers auf int-Variable   */
      pa=&a[0];

  `pa` | --> | zeigt auf das 1.Element von a
  `pa+1` | --> | zeigt auf das 2.Element von a
  `pa+i` | --> | zeigt auf das Element, das i Positionen von dem Element, auf das pa zeigt, entfernt ist

  ==> | `*pa` | ist gleich mit `a[0]`
  | `*(pa+1)` | ist gleich mit `a[1]`
  | `*(pa+i)` | ist gleich mit `a[i]`

- Jeder Auftritt eines Array-Namens wird durch den Compiler in einen Pointer auf den Array-Anfang umgewandelt.

  --> | Ein Array-Name ist äquivalent mit einem Pointer auf das 1.Element des Arrays:

  `a` ist äquivalent mit `&a[0]`

  ==> statt `pa=&a[0]` kann man auch `pa=a` schreiben

  --> Für den Zugriff zum i-ten Element von a gilt:

  `a[i]` ist äquivalent mit `*(a+i)`

- Umgekehrt lassen sich Pointer-Variable auch indizieren :

  statt `*(pa+i)` kann man auch `pa[i]` schreiben

  ==> |
  Jeder Array- und Index-Ausdruck kann als Pointer- und Offset-Ausdruck geschrieben werden und umgekehrt.

  Anm. : | Pointer-Arithmetik kann effizienter (schneller) als Array- Indizierung sein.

- Eine Pointer-Variable kann auch mit negativem Index verwendet werden:

  `pa[-i]` (i>0) bezeichnet das Element, das i Positionen (Objekte !) vor dem Element liegt, auf das `pa` zeigt.
  d.h. `pa[-i]` ist äquivalent mit `*(pa-i)`

  Dies ist i.a. nur sinnvoll, wenn pa auf ein Element innerhalb eines Arrays zeigt und das dadurch referierte davorliegende Array-Element auch tatsächlich existiert.

- Zwischen Array-Namen und Pointern besteht jedoch ein gravierender Unterschied :

  - Ein Pointer ist eine Variable.
    Sein jeweiliger Wert kann jederzeit geändert werden:

    z.B.

        int x,*px;
        px=&x;         /* ist jederzeit zulässig */

  - Ein Array-Name ist eine Pointer-Konstante.
    Sein jeweiliger Wert kann nicht geändert werden:

    z.B.

        int z,y[5];
        y=&z;          /* ist  u n z u l ä s s i g  */

  - Damit wird auch verständlich, warum in C keine "Wertzuweisung" von ganzen Arrays möglich ist :

        int a1[10], a2[10];

        ...

    `a2=a1;` | --> | würde bedeuten, daß die Anfangsadresse von a1 der (Adreß-) Konstanten a2 zugewiesen wird. --> u n z u l ä s s i g !!!

  - Zulässig ist dagegen die Zuweisung der Anfangsadresse eines Arrays an eine Pointer-Variable:

        int a1[10], *pa2;

        ...

    `pa2=a1;` | --> | zulässig !!!

    Anwendung derartiger Konstruktionen vor allem bei char-Arrays (Strings), z.B. :

    `char *message;` |  |
    `message="Vorsicht !";` | --> | der Pointervariablen message wird die Anfangsadresse des - im ASp irgendwo als Konstante abgelegten - Strings zugewiesen. Kein Kopieren von Strings !!!

- Als formale Parameter in Funktionen sind Array-Namen und Pointer jedoch vollkommen identisch.

  z.B.

      void strcopy(char str1[], char str2[]);
      void strcopy(char *str1, char *str2);

  In beiden Fällen werden beim Funktionsaufruf den jeweiligen formalen Parametern - sowohl den als String, als auch den als Pointer deklarierten - Adressen als aktuelle Parameter übergeben.

  --> | als formale Funktionsparameter sind `char s[]` und `char *s` vollkommen äquivalent. Arrays und Pointer sind sowohl als aktuelle als auch als formale Parameter vollkommen austauschbar.

### Beispiel zur Verwandschaft zwischen Zeigern und Arrays

    /* -------------------------------------------------------------------- */
    /*           Aequivalente Formulierungen der Funktion strcopy           */
    /* -------------------------------------------------------------------- */

    void strcopy(char str1[], char str2[])             /* s. Umdruck VPC623 */
    {
      int i=0;
      while((str2[i]=str1[i]) != '\0')
        i++;
    }

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

    void strcopy(char *str1, char *str2)
    {
      int i=0;
      while ((str2[i]=str1[i]) != '\0')
        i++;
    }

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

    void strcopy(char *str1, char *str2)
    {
      int i=0;
      while ((*(str2+i)=*(str1+i)) != '\0')
        i++;
    }

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

    void strcopy(char *str1, char *str2)
    {
      while ((*str2=*str1)!='\0')
      { str2++;
        str1++;
      }
    }

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

    void strcopy(char *str1, char *str2)
    {
      while (*str2++=*str1++);
    }

- Vergleich des erzeugten Codes für verschiedene äquivalente Formulierungen der Funktion strcopy (1)

  - Für die folgenden Formulierungen wird der gleiche Code erzeugt:

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

        void strcopy(char str1[], char str2[])
        {
          int i=0;
          while((str2[i]=str1[i]) != '\0')
            i++;
        }

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

        void strcopy(char *str1, char *str2)
        {
          int i=0;
          while ((str2[i]=str1[i]) != '\0')
            i++;
        }

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

        void strcopy(char *str1, char *str2)
        {
          int i=0;
          while ((*(str2+i)=*(str1+i)) != '\0')
            i++;
        }

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

  - Erzeugter Code in ASM-Darstellung :

        _strcopy  proc   near
                  push   bp
                  mov    bp,sp
                  push   si
                  xor    si,si                  ; i=0
                  jmp    short @2
        @4:
                  inc    si                     ; i++
        @2:
                  mov    bx,word ptr [bp+4]     ; str1    --> BX
                  mov    al,byte ptr [bx+si]    ; str1[i] --> AL
                  mov    bx,word ptr [bp+6]     ; str2    --> BX
                  mov    byte ptr [bx+si],al    ; AL      --> str2[i]
                  or     al,al
                  jne    @4                     ; str1[i] == 0 ?
                  pop    si
                  pop    bp
                  ret
        _strcopy  endp

- Vergleich des erzeugten Codes für verschiedene äquivalente Formulierungen der Funktion strcopy (2)

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

      void strcopy(char *str1, char *str2)
      {
        while ((*str2=*str1)!='\0')
        { str2++;
          str1++;
        }
      }
      /* -------------------------------------------------------------- */

      _strcopy  proc   near
                push   bp
                mov    bp,sp
                jmp    short @2
      @4:
                inc    word ptr [bp+6]            ; str2++
                inc    word ptr [bp+4]            ; str1++
      @2:
                mov    bx,word ptr [bp+4]         ; str1  --> BX
                mov    al,byte ptr [bx]           ; *str1 --> AL
                mov    bx,word ptr [bp+6]         ; str2  --> BX
                mov    byte ptr [bx],al           ; AL    --> *str2
                or     al,al
                jne    @4                         ; *str1 == 0 ?
                pop    bp
                ret
      _strcopy  endp

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

      void strcopy(char *str1, char *str2)
      {
        while (*str2++=*str1++);
      }
      /* -------------------------------------------------------------- */

      _strcopy  proc   near
                push   bp
                mov    bp,sp
                jmp    short @2
      @4:
      @2:
                mov    bx,word ptr [bp+4]         ; str1  --> BX
                inc    word ptr [bp+4]            ; str1++
                mov    al,byte ptr [bx]           ; *str1 --> AL
                mov    bx,word ptr [bp+6]         ; str2  --> BX
                inc    word ptr [bp+6]            ; str2++
                mov    byte ptr [bx],al           ; AL    --> *str2
                or     al,al
                jne    @4                         ; *str1 == 0 ?
                pop    bp
                ret
      _strcopy  endp

### Anwendung der Verwandschaft zwischen Zeigern und Arrays in C :
Arrays variabler Länge

- Arrays variabler Länge (dynamische Arrays) sind Arrays, deren Größe zur Compilezeit nicht bekannt ist, sondern erst während der Programmabarbeitung festgelegt wird.
  Statisch lassen sich derartige Arrays in C nicht definieren.
  Folgendes ist nicht zulässig :

      int proc_var_array(int n)
      {
        double var_ar[n];
        ...
      }

- Die Möglichkeit der dynamischen Speicherallokation stellt jedoch im Zusammenhang mit der engen Verwandschaft zwischen Zeigern und Arrays ein elegantes Mittel zur Realisierung dynamischer Arrays bereit :

      #define DBL_SIZ  8
      #include <stdlib.h>

      int proc_var_array(int n)
      {
        double *var_ar;
        if ((var_ar=calloc(n, DBL_SIZ)) != NULL)
        { ...
          var_ar[1] = ...  /* Zugriff zu Array-Komponenten */
          ...
        }
        else
          ...
      }

  Achtung : | Die Definition `double *var_ar;` stellt noch keinen Speicherplatz für das Array bereit, sondern lediglich für eine Pointer-Variable ! Ein Zugriff zu Array-Komponenten darf erst nach Alloka- tion des entsprechenden Speicherplatzes erfolgen.

- Beispiel :
    Funktion zum Aneinanderhängen zweier Strings in einem neu bereitzustellenden Speicherbereich

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

        char *add_string(char *s1, char *s2)
        {
          char *as;
            if ((as=malloc(strlen(s1)+strlen(s2)+1)) != NULL)
            { strcpy(as, s1);
              strcat(as, s2);
            }
          return as;
        }

## 8.6 Mehrdimensionale Arrays und Pointer-Arrays

### Mehrdimensionale Arrays und Pointer

Mehrdimensionale Arrays koennen als "Pointer auf Arrays" aufgefasst werden.

Beispiel :

      char cf[3][4];   /* Definition und damit Speicherplatzreser-
                          vierung eines Arrays von 3 * 4 char-
                          Werten */
                       /* cf ist ein Pointer auf ein Array von
                          3 char-Arrays je der Laenge 4 */

`cf+i` | ist ein Pointer auf das (i+1)-te Zeilen-Array (der immer auf das 1.Element dieses Arrays zeigt)
`*(cf+i) = cf[i]` | ist das (i+1)-te Zeilen-Array, d.h. genauer ein Pointer auf das 1.Element des (i+1)-ten Zeilen-Arrays
---> `(cf+i)` und `*(cf+i)` | liefern immer die gleiche Adresse
`*(cf+i)+j = cf[i]+j` | ist ein Pointer auf das (j+1)-te Element des (i+1)-ten Zeilen-Arrays
`*(*(cf+i)+j) = *(cf[i]+j) = cf[i][j] = (*(cf+i))[j]` | ist das (j+1)-te Element des (i+1)-ten Zeilen-Arrays

Parameterdeklaration mehrdimensionaler Felder:

`char (*cmat)[4]` und `char cmat[][4]` sind äquivalent

### Pointer - Arrays

Pointer-Arrays (Arrays von Pointern), die wiederum auf Arrays zeigen, sind von mehrdimensionalen Arrays zu unterscheiden, obwohl sie gleichartig verwendet werden koennen.

Beispiel :

      char *pa[3];      /* Definition und damit Speicherplatzreser-
                           vierung eines Arrays von 3 Pointern, die
                           jeweils auf char-Werte zeigen */
                        /* pa ist ein Zeiger auf ein Array von
                           3 char-Pointern */

      char ca1[4],      /* Definition und damit Speicherplatzreser-
           ca2[4],      /* vierung von  3 char-Arrays je der
           ca3[4];      /* Laenge 4 */

      pa[0] = ca1;      /* Wertzuweisung an die char-Pointer */
      pa[1] = ca2;
      pa[2] = ca3;

`pa+i` | ist ein Pointer auf die (i+1)-te Komponente des Pointer-Arrays `pa`
`*(pa+i) = pa[i]` | ist das Array, auf das die (i+1)-te Komponente des Pointer-Arrays `pa` zeigt, d.h. genauer ein Pointer auf das 1.Element dieses Arrays
`*(pa+i)+j = pa[i]+j` | ist ein Pointer auf das (j+1)-te Element des Arrays, auf das die (i+1)-te Komponente des Pointer-Arrays `pa` zeigt
`*(*(pa+i)+j) = *(pa[i]+j) = pa[i][j] = (*(pa+i))[j]` | ist das (j+1)-te Element des Arrays, auf das die (i+1)-te Kompon. d. Point.-Arrays `pa` zeigt

### Mehrdimensionale Arrays und Pointer-Arrays - Gegenüberstellung

- Mehrdimensionale Zusammenfassungen von Objekten können sowohl mittels mehrdimensionaler Arrays als auch mittels Pointer-Arrays beschrieben und referiert werden.
  Beispiel : Eine Gruppe von Strings --> 2-dimensionale Ansammlung von Zeichen
  Annahme : 10 Strings der maximalen Länge (20-1)

  - Speicherung und Referierung als 2-dimensionales Zeichen-Array :
    `char string_feld[10][20];`

  - Speicherung der Arrays als Einzel-Arrays und Referierung über ein Pointer-Array :

        char str0[20];
             ...
        char str9[20];

        char *strp_feld[10];
             ...
        strp_feld[0]=str0;
             ...

  - In beiden Fällen wird mit `string_feld[i][j]` bzw. `strp_feld[i][j]` (oder auch mit `*(*(str_feld+i)+j)` bzw. `*(*(strp_feld+i)+j)` ) ein einzelnes Zeichen referiert.

- Unterschiede :

  - Mit der Definition `char str_feld[10][20]` wird Speicherplatz für 10 Zeilen (Strings) zu 20 Spalten (zu je 20 Zeichen) also insgesamt 200 Zeichen (char) reserviert (--> 200 Bytes).

    Mit der Definition `char *strp_feld[10]` wird Speicherplatz lediglich für 10 Pointer auf char reserviert.
    Der Speicherplatz für die Objekte der einzelnen Komponenten des Arrays - den eigentlichen Strings - wird hierdurch nicht reserviert.
    Er muß durch gesonderte Definitionen bzw Allokationen beschafft werden. (für 10 Strings zu je 20 Zeichen --> 200 Bytes).
    Der Speicherplatz für die Pointer wird zusätzlich zum Speicherplatz für die Zeichen benötigt.

  - Die Zeilen eines 2-dimensionalen Arrays haben alle die gleiche Länge.

    --> | Die Zeilenlänge muß sich nach der längsten Zeile (String) richten. --> bei kürzeren Zeilen (Strings) Platzverschwendung.

    Die Objekte, auf die die einzelnen Pointer des Pointer-Arrays zeigen müssen nicht gleich lang sein.

    --> | Zur Speicherung unterschiedlich langer Objekte (Strings) wird nur soviel Platz belegt, wie tatsächlich benötigt wird.

  - Der Zugriff zu den einzelen Elementen der Pointer-Objekte kann schneller sein (Objekt-Operator --> indirekte Adressierung) als zu den einzelnen Elementen des mehrdimensionalen Arrays (Berechnung des Element-Offsets erfordert Multiplikation und Addition).

  - Verwendung als Funktionsparameter :

    2-dimensionales Array | : | z.B. oder | `int fkt(char sf[][MSP]); int fkt(char (*sf)[MSP]);`
    Pointer-Array | : | z.B. bzw. | `int fkt(char *sfp[]); int fkt(char **sfp);`

### Programmbeispiel zu mehrdimensionalen Arrays und Pointer-Arrays

    /* ------------------------------------------------------------------- */
    /* Programm POINDEM4                                                   */
    /* ------------------------------------------------------------------- */
    /* Demonstration von Ähnlichkeit und Unterschied mehrdimensionaler     */
    /* Arrays und Pointer-Arrays                                           */
    /* ------------------------------------------------------------------- */

    #include <stdio.h>

    char aacWochTagFeld[][11] = {"ungueltig",   "Montag",
                                 "Dienstag",    "Mittwoch",
                                 "Donnerstag",  "Freitag",
                                 "Samstag",     "Sonntag" };

    char *apcWochTagZeig[]    = {"ungueltig",   "Montag",
                                 "Dienstag",   "Mittwoch",
                                 "Donnerstag", "Freitag",
                                 "Samstag",    "Sonntag" };
    void main(void)
    { int i,j;
      char cZeichF,cZeichZ;
      int indein(int *, int *);
      while (indein(&i,&j)>=0)
      { cZeichF=aacWochTagFeld[i][j];
        cZeichZ=apcWochTagZeig[i][j];
        printf("aacWochTagFeld[%2d][%2d] = %02x\n",i,j,cZeichF);
        printf("apcWochTagZeig[%2d][%2d] = %02x\n",i,j,cZeichZ);
      }
    }

    int indein(int *iA, int *iB)
    { printf("\nZeile  ? ");  scanf("%d",iA);
      if (*iA<0) return (-1);
      else
      { printf("Spalte ? ");  scanf("%d",iB);
        if (*iB<0) return (-1);
        else return 1 ;
      }
    }

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

Bildschirmausgabe :

      F:\RT\C\VORL>poindem4

      Zeile  ? 0
      Spalte ? 8
      aacWochTagFeld[ 0][ 8] = 67
      apcWochTagZeig[ 0][ 8] = 67

      Zeile  ? 1
      Spalte ? 7
      aacWochTagFeld[ 1][ 7] = 00
      apcWochTagZeig[ 1][ 7] = 44

      Zeile  ? -1

### Programmbeispiel zur Verwendung von Pointer-Arrays

    /* -------------------------------------------------------------------- */
    /* Programm POINDEM5                                                    */
    /* -------------------------------------------------------------------- */
    /* Einlesen einer unbekannten Anzahl von Zeilen (max. Anzahl 100)       */
    /* Nach Abschluss der Eingabe (mit CTRL_Z) Ausgabe der Zeilen in umge-  */
    /* kehrter Reihenfolge (letzte Zeile zuerst)                            */
    /* -------------------------------------------------------------------- */

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

    #define MAXANZ  100
    #define MAXLEN  136
    #define WRITELN putchar('\n')

    void strcopy(char *pcStr1, char *pcStr2)
    { while (*pcStr2++ = *pcStr1++);
    }

    int ZeilenEin(char *apcZeilFeld[], int iMax)
    { extern int GetLine(char *, int);         /* s. Umdruck V-PC-722 */
      char *pcNext, acZeile[MAXLEN];
      int iLaenge, iZeilZahl=0;
      while (((iLaenge=GetLine(acZeile,MAXLEN)) >= 0)
             && (iZeilZahl < iMax) && (pcNext=malloc(iLaenge+1)) != NULL)
      { strcopy(acZeile,pcNext);
        apcZeilFeld[iZeilZahl++]=pcNext;
      }
      return iZeilZahl;
    }

    void ZeilenAus(char *apcZeilFeld[], int iAnz)
    { int i;
      WRITELN;
      for (i=iAnz;i>0; )
      { printf("%s\n",apcZeilFeld[--i]);
        free(apcZeilFeld[i]);
      }
    }

    void main(void)
    { char *apcZeilen[MAXANZ];
      int iAnz;
      WRITELN;
      iAnz=ZeilenEin(apcZeilen,MAXANZ);
      ZeilenAus(apcZeilen,iAnz);
    }
    /* -------------------------------------------------------------------- */

Bildschirmausgabe :

    F:\RT\C\VORL>poindem5

    hallo
    guten
    morgen
    ^Z

    morgen
    guten
    hallo

## 8.7 Kommandozeilenparameter in C

- C bietet die Möglichkeit, beim Programmstart (Aufruf des gelinkten Object-Files) Kommandozeilenparameter an das Programm zu übergeben.
  Die einzelnen Parameter sind in der Kommandozeile nach dem Programmnamen jeweils durch Blanks getrennt anzugeben.

- Hierfür wird die "Haupt"-Funktion main mit 2 Parametern aufgerufen.
  Die korrespondierenden formalen Parameter in main werden üblicherweise mit
  `argc` (= argument count) und
  `argv` (= argument vector)
  bezeichnet:

  `void main(int argc, char *argv[])`

- Im ersten Parameter `argc` wird die Anzahl der Kommandozeilenparameter übergeben.
  Dabei zählt der Programmname selbst immer als erster Parameter.
  ---> `argc` ist immer mindestens = 1

- Der zweite Parameter `argv` ist ein Pointer auf ein Pointer-Array, dessen Elemente auf die einzelnen Kommandozeilenparameter-Strings verweisen.
  Abgeschlossen wird das Pointer-Array mit dem NULL-Pointer.

  `argv[0]` | ist ein Pointer auf den Programmnamen selbst
  `argv[1]` | ist ein Pointer auf den ersten echten Kommandozeilen-Parameter
  `argv[argc-1]` | ist ein Pointer auf den letzten Kommandozeilen- Parameter
  `argv[argc]` | ist der NULL-Pointer

  z.B.

    Programm: | `echopar`
    Programmaufruf: | `echopar Pascal ist wunderbar`

### Beispiel zu Kommandozeilen-Parametern

    /* ------------------------------------------------------------------ */
    /* Programm ECHOPAR                                                   */
    /* ------------------------------------------------------------------ */
    /* Ausgabe der Kommandozeilen-Parameter untereinander nach stdout     */
    /* (Bildschirm)                                                       */
    /* ------------------------------------------------------------------ */

    #include <stdio.h>

    void main(int argc, char *argv[])

                  /*  argc     Anzahl der Kommandozeilen-Parameter        */
                  /*  argv     Pointer-Array auf Kommandozeilen-Parameter */

    { int i;
      putchar('\n');
      for (i=1; i<argc; i++)
        printf("%s\n",argv[i]);
    }
    /* ------------------------------------------------------------------ */

Bildschirmausgabe :

    E:\RT\C\VORL>echopar pascal ist wunderbar

    pascal
    ist
    wunderbar

    /* ------------------------------------------------------------------ */
    /* Programm ECHOPAR2                                                  */
    /* ------------------------------------------------------------------ */
    /* Ausgabe der Kommandozeilen-Parameter untereinander nach stdout     */
    /* (Bildschirm)                                                       */
    /* Verwendung des Namens argv selbst wieder als Pointer               */
    /* ------------------------------------------------------------------ */

    #include <stdio.h>

    void main(int argc, char **argv)

                  /*  argc     Anzahl der Kommandozeilen-Parameter        */
                  /*  argv     Pointer auf Pointer-array                  */

    {
      putchar('\n');
      while (--argc>0)
        printf("%s\n",*++argv);
    }
    /* ------------------------------------------------------------------ */

    E:\RT\C\VORL>echopar2 c ist noch wunderbarer

    c
    ist
    noch
    wunderbarer

## 8.8 Zeiger auf Funktionen

- In C kann ein Zeiger auch die Anfangsadresse einer Funktion enthalten.
  --> es existieren auch Zeiger auf Funktionen.

  Es ist möglich
  - Zeiger-Variable, die auf Funktionen zeigen, zu vereinbaren
  - Funktions-Zeiger-Variablen Werte zuzuweisen,
  - Arrays von Funktionszeigern aufzubauen,
  - Zeiger auf Funktionen als Parameter zu übergeben
  - Zeiger auf Funktionen als Funktionswert zurückzugeben

  Ein Funktionszeiger kann immer nur auf Funktionen zeigen, deren Funktionswert von einem ganz bestimmten Typ ist.

- Vereinbarung einer Funktions-Zeiger-Variablen Es ist anzugeben :
  - Typ des Funktionswerts der Objekt-Funktion
  - Anzahl und Typ der Parameter der Objekt-Funktion
    (optional, ohne Angabe kann aber keine Parameterüberprüfung erfolgen)

  Beispiel :

      double (*pf)(int);    /* pf ist ein Zeiger auf eine Funk-
                               tion, die einen double-Wert lie-
                               fert und einen int-Parameter er-
                               wartet */

  Achtung : die runden Klammern um `(*pf)` sind zwingend notwendig

- Wertzuweisung an eine Funktions-Zeiger-Variable

  Man erhält in C die Adresse einer Funktion durch Angabe des Funktionsnamens allein - ohne eine Parameterliste.
  (Keine Anwendung des &-Operators !, Funktionsname ist Adreßkonstante !)

  Beispiel :

      extern double f1(int);   /* Funktionsdeklaration */
      double (*pf)(int);       /* Definition einer Funktions-
                                  Zeiger-Variablen pf */
      pf = f1;                 /* Zuweisung der Adresse der
                                  Funktion f1 an pf */

- Aufruf einer Funktion über einen Funktionszeiger :
  Durch Dereferenzierung des Funktionszeigers und Nachstellen einer aktuellen Parameterliste.

  Beispiel :

      a=(*pf)(2*i);

- Funktionszeiger als Funktionswert

  Beispiel :

      int (*best(char *))(int);

  --> | best ist eine Funktion mit einem char-Pointer-Parameter, die einen Pointer auf eine int-Funktion mit einem int-Parameter liefert.

- Hauptanwendung : Realisierung allgemeiner anwendbarer Funktionen
  - durch Übergabe von Funktionszeigern als Parameter
  - durch Auswahl einer bestimmten anzuwendenden Routine über die Indizierung eines Funktionszeiger-Arrays

### Programmbeispiele zu Zeiger auf Funktionen

    /* ----------------------------------------------------------------- */
    /* Programm SIMPINT                                                  */
    /* ----------------------------------------------------------------- */
    /* Definition der Funktion "simpint",                                */
    /* Integration einer beliebigen Funktion mit einem double-Parameter, */
    /* die einen double-Wert liefert, nach dem Simpson-Verfahren         */
    /* Beispiel zur Anwendung der Funktion                               */
    /* ----------------------------------------------------------------- */

    #include <stdio.h>

    double simpint(double a, double b, unsigned n,
                   double (*fkt)(double))
           /* a,b       Untere u. obere Integrationsgrenze    */
           /* n         Anz. Intervalle                       */
           /* fkt       Pointer auf zu integrierende Funktion */
    {
      double x,usum,gsum,h,zh;
      int i;
      h=(b-a)/n;
      zh=2*h;
      for (usum=0,i=1,x=a+h; i<n; x+=zh,i+=2)
        usum=usum+(*fkt)(x);
      for (gsum=0,i=2,x=a+zh; i<n-1; x+=zh,i+=2)
        gsum=gsum+(*fkt)(x);
      return ((*fkt)(a)+(*fkt)(b)+4*usum+2*gsum)/3*h;
    }

    double rechteck(double x)        /* period. Rechteckfunktion    */
    {                                /* Grundperiode : 0 ... 1.0    */
      double r;                      /* x = 0   )                   */
      r = x-(int)x;                  /* x = 0.5  >  rechteck = 0.5  */
      r = r <0 ? r+1.0 : r;          /* x = 1   )                   */
      if( r==0 || r==0.5)            /* x < 0.5     rechteck = 1.0  */
      return 0.5;                    /* x > 0.5     rechteck = 0    */
      else
        if (r<0.5) return 1.0;
        else return 0;
    }

    void main(void)
    {
      double x1,x2;
      unsigned m;
      printf("\nUntere Grenze ? ");  scanf("%lf",&x1);
      printf("Obere  Grenze ? ");    scanf("%lf",&x2);
      printf("Intervallanzahl ? ");  scanf("%u",&m);
      printf("\nIntegral : %1.4f\n",simpint(x1,x2,m,rechteck));
    }

Bildschirmausgabe :

    C>simpint

    Untere Grenze ? 0
    Obere  Grenze ? 1
    Intervallanzahl ? 10000

    Integral : 0.5000

    /* ------------------------------------------------------------------- */
    /* Programm FKZDEMO                                                    */
    /* ------------------------------------------------------------------- */
    /* Demonstrationsprogramm zu Funktionszeigern                          */
    /* Auswahl einer Funktion über Indizierung eines Arrays von Funktions- */
    /* zeigern                                                             */
    /* ------------------------------------------------------------------- */

    #include <stdio.h>
    #define FKTZ 4

    int add(int a, int b)
    { printf("Summe     : ");
      return a+b;
    }

    int sub(int a, int b)
    { printf("Differenz : ");
      return a-b;
    }

    int mul(int a, int b)
    { printf("Produkt    : ");
      return a*b;
    }

    int mod(int a, int b)
    { printf("Modulus    : ");
      return a%b;
    }

    void main(void)
    { int i;
      int x,y;
      int (*op[FKTZ])(int, int);      /* Array von Funktionszeigern */
      op[0]=add;
      op[1]=sub;
      op[2]=mul;
      op[3]=mod;
      while((printf("\nZwei int-Zahlen ? "), scanf("%d%d", &x, &y))!=EOF)
      { printf("Operation  (0 - ADD,  1 - SUB,  2 - MUL,  3 - MOD) ? ");
        scanf("%d",&i);
        printf("%d\n",(*op[i])(x,y));
      }
    }

Bildschirmausgabe :

    E:\RT\C\VORL>fkzdemo

    Zwei int-Zahlen ? 125 68
    Operation  (0 - ADD,  1 - SUB,  2 - MUL,  3 - MOD) ? 1
    Differenz : 57

    Zwei int-Zahlen ? 45 17
    Operation  (0 - ADD,  1 - SUB,  2 - MUL,  3 - MOD) ? 3
    Modulus    : 11

    Zwei int-Zahlen ? ^Z

## 8.9 Near und Far-Zeiger in TURBO/BORLAND-C

### Near- und Far-Zeiger in TURBO-C

- n TURBO-C ist durch das verwendete Speichermodell festgelegt, ob defaultmäßig Near-Zeiger (16 Bit, nur Offset) oder Far-Zeiger (32 Bit, Segment und Offset) verwendet werden.

- Es ist jedoch auch möglich gezielt Near- und Far-Zeiger unabhängig vom verwendeten Speichermodell zu verwenden.
  Hierfür dienen die Turbo-C-spezifischen Modifier

  `near` | (--> Near-Zeiger) und
  `far` | (--> Far-Zeiger)

  Diese sind bei Zeigervariablen- und Funktions-Vereinbarungen unmittelbar nach dem Datentyp des Zeigerobjekts bzw unmittelbar vor dem Funktionsnamen anzugeben.

  Beispiele :

  `int far *p1;` | p1 ist Far-Zeiger auf int-Werte
  `int far *f1();` | f1 ist eine Funktion (Near oder Far je nach Speichermodell), die einen Far-Zeiger auf int-Werte liefert
  `int far f2();` | f2 ist eine Far-Funktion, die einen int-Wert liefert
  `int *far f3();` | f3 ist eine Far-Funktion, die einen Zeiger (Near oder Far je nach Speichermodell) auf int-Werte liefert
  `int (far *f4)();` | f4 ist ein Far-Zeiger auf eine Funktion, die einen int-Wert liefert.

- Zur Bildung eines Far-Zeigers aus zwei unsigned-Werten für Segment und Offset ist in der Header-Datei dos.h die "Funktion" MK_FP (make far pointer) als parameterisierter Makro definiert:
  `void far *MK_FP(unsigned seg, unsigned offs);`

  Beispiel :

      #include <dos.h>
      int far *screen;
      screen=MK_FP(0xB800,0);

- Zur Ermittlung des Segments und des Offsets eines Far-Zeigers als unsigned-Werte sind in der Header-Datei `dos.h` die "Funktionen" `FP_SEG` und `FP_OFF` als parameteriserte Makros definiert:

      unsigned FP_SEG(void far *fptr);    /* --> Segment */
      unsigned FP_OFF(void far *fptr);    /* --> Offset  */

  Beispiel :

      #include <dos.h>
      unsigned seg,offs;
      seg=FP_SEG(screen);
      offs=FP_OFF(screen);

- Mittels expliziter Typumwandlung (type cast) kann aus einem Near-Zeiger ein Far-Zeiger (und umgekehrt) gemacht werden.

  Beispiel :

      int i;              /* Speicher-Modell Small ! */
      int far *iptr;
      iptr=(int far *)&i; /* liefert Adr. von i als Far-Zeiger */

### Ein- und Ausgabe von Zeigerwerten in TURBO-C

- In ANSI-C ist mit dem Konvertierungszeichen `p` im Controlstring für `scanf()` und `printf()` die Möglichkeit zur direkten Ein- und Ausgabe von Zeigerwerten eingeführt worden.
  Die jeweilige Darstellung eines Zeigerwertes ist dabei implementierungs- spezifisch.

  Beispiel :

      scanf("%p", &px);      /* px sei als Pointer definiert */
      printf("%p", &x);      /* x sei als int definiert      */

- In TURBO-C bewirkt die Angabe des Konvertierungszeichen `p` eine Interpretation und Darstellung des Zeigerwertes als Near- bzw Far-Zeiger entsprechend dem jeweiligen Speichermodell.

- Zur Ein- und Ausgabe von Near- und Far-Zeigern unabhängig vom Speichermodell sind in TURBO-C zusätzliche Length Modifier eingeführt worden, die vor dem Konvertierungszeichen anzugeben sind :
  `N` für einen Near-Zeiger
  `F` für einen Far-Zeiger

  Leider ist offensichtlich ein Fehler in der Funktion scanf() enthalten.
  Sie arbeitet mit dem Length Modifier F nicht wie beschrieben.

  Statt `F` ist im Controlstring für `scanf()` der Length Modifier

  `l` oder
  L für einen Far-Zeiger
  anzugeben.

  Beispiel :

      char c;
      char near *nptr;
      char far  *fptr;
                                ...
      scanf("%Np", &nptr);
      scanf("%lp", &fptr);
      printf("Offset : %Np", (char near *)&c);
      printf("Segment:Offset : %Fp", (char far *)&c);

  Die Eingabe von Far-Zeigern erfolgt in der Form :

  `SSSS:OOOO` | `SSSS` | Segment | sedezimal
  | `OOOO` | Offset | sedezimal

### Beispiel zur Ein- und Ausgabe von Zeigerwerten in TURBO-C

    /*---------------------------------------------------------------------*/
    /* Programm POINDEM6                                                   */
    /*---------------------------------------------------------------------*/
    /* Demonstration der Ein- und Ausgabe von Near- und Far-Zeigern in     */
    /* TURBO-C                                                             */
    /*---------------------------------------------------------------------*/

    #include <stdio.h>

    void main(void)
    { char cZx='A', cZy='B';
      char *pcZ1, near *npcZ2;
      char far *fpcZeig;

      /* -------------- Ausgabe eines Default-Zeigers -------------------- */
      printf("\nAdresse von cZx (Default-Zeiger) : %p", &cZx);

      /* ---------------- Ausgabe eines Near-Zeigers --------------------- */
      printf("\nAdresse von cZx (Near-Zeiger)    : %Np", (char near *)&cZx);

      /* ---------------- Ausgabe eines Far-Zeigers ---------------------- */
      printf("\nAdresse von cZx (Far-Zeiger)     : %Fp\n",(char far *)&cZx);

      /* -------------- Eingabe eines Default-Zeigers -------------------- */
      printf("\nAdresse (Offset)         ? ");
      scanf("%p", &pcZ1);

      /* ---------------- Eingabe eines Near-Zeigers --------------------- */
      printf("Adresse (Offset)         ? ");
      scanf("%Np", &npcZ2);

      /* ---------------- Eingabe eines Far-Zeigers ---------------------- */
      printf("Adresse (Segment:Offset) ? ");
      scanf("%Lp", &fpcZeig);

      /* -------- Ausgabe des Objekts der eingegebenen Zeiger ------------ */
      printf("\nObjekt von pcZ1    : %c", *pcZ1);
      printf("\nObjekt von npcZ2   : %c", *npcZ2);
      printf("\nObjekt von fpcZeig : %c\n", *fpcZeig);
    }

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

Bildschirmausgabe :

    F:\RT\C\VORL>poindem6

    Adresse von cZx (Default-Zeiger) : FFCA
    Adresse von cZx (Near-Zeiger)    : FFCA
    Adresse von cZx (Far-Zeiger)     : 0987:FFCA

    Adresse (Offset)         ? ffcb
    Adresse (Offset)         ? ffcb
    Adresse (Segment:Offset) ? 0987:ffcb

    Objekt von pcZ1    : B
    Objekt von npcZ2   : B
    Objekt von fpcZeig : B

### Beispiel zum direkten Speicherzugriff über Far-Zeiger in TURBO-C

    /*------------------------------------------------------------------*/
    /* Programm GIV2                                                    */
    /*------------------------------------------------------------------*/
    /* Ermittlung des Interrupt-Vektors, der zu einer INT-Nr. gehört    */
    /* Auslesen des Interrupt-Vektors direkt aus der INT-Vektor-Tabelle */
    /*------------------------------------------------------------------*/

    #include <stdio.h>
    #include <dos.h>

    #define INT_SEG  0

    void main(void)
    {
      int int_nr;
      void far *int_vec;
      int int_nr_anfordern(void);
      void far *get_vect(unsigned);

      while ((int_nr=int_nr_anfordern()) != EOF)
      { int_vec=get_vect(int_nr);
        printf("Interrupt-Vektor         : %Fp\n",int_vec);
      }
      putchar('\n');
    }

    int int_nr_anfordern(void)
    {
      unsigned zahl;
      printf("\nINT-Nr. (Abbruch CTRL-Z) ? ");
      if (scanf("%i", &zahl) != EOF)
        return zahl;
      else
        return EOF;
    }

    void far *get_vect(unsigned int_nr)
    {
      void far *ivec;
      ivec=*(void far * far *)MK_FP(INT_SEG,4*int_nr);
      return ivec;
    }

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

Bildschirmausgabe :

    E:\RT\C\VORL>giv2

    INT-Nr. (Abbruch CTRL-Z) ? 0x21
    Interrupt-Vektor         : 20B2:19C8

    INT-Nr. (Abbruch CTRL-Z) ? 0x13
    Interrupt-Vektor         : 08C2:18C5

    INT-Nr. (Abbruch CTRL-Z) ? 13
    Interrupt-Vektor         : F000:EAA6

    INT-Nr. (Abbruch CTRL-Z) ? ^Z

### Turbo-C-Funktionen zum direkten Zugriff auf Speicher-Adressen

    /* -------------------------------------------------------------------- */
    /*       peekb     -     Lesen eines Bytes von einer Speicher-Adresse   */
    /* -------------------------------------------------------------------- */

    #include <dos.h>

    char peekb(unsigned seg, unsigned offs);

`peekb` liest ein einzelnes Byte von der durch `seg:offs` angegebenen Speicher-Adresse.

Funktionswert :
  gelesenes Byte

    /* -------------------------------------------------------------------- */
    /*  peek -  Lesen eines Int-Wertes (2 Bytes) von einer Speicher-Adresse */
    /* -------------------------------------------------------------------- */

    #include <dos.h>

    int peek(unsigned seg, unsigned offs);

`peek` liest einene Integerwert (2 Bytes) von der durch `seg:offs` angegebenen Speicher-Adresse (und der nächsten Adresse).

Funktionswert :
  gelesener Integerwert

    /* -------------------------------------------------------------------- */
    /*      pokeb    -     Schreiben eines Bytes in eine Speicher-Adresse   */
    /* -------------------------------------------------------------------- */

    #include <dos.h>

    void pokeb(unsigned seg, unsigned offs, char wert);

`pokeb` schreibt das Byte mit dem Wert `wert` in die durch `seg:offs` angegebene Speicher-Adresse.

Funktionswert :
  keiner

    /* -------------------------------------------------------------------- */
    /* poke - Schreiben eines Int-Wertes (2 Bytes) in eine Speicher-Adresse */
    /* -------------------------------------------------------------------- */

    #include <dos.h>

    void poke(unsigned seg, unsigned offs, int wert);

`poke` schreibt den Integer-Wert `wert` in die durch `seg:offs` angegebene Speicher-Adresse (und in die nächste Adresse).

Funktionswert :
  keiner

### Turbo-C-Funktionen zum direkten Zugriff auf E/A-Adressen

    /* -------------------------------------------------------------------- */
    /*       inportb        -     Lesen eines Bytes von einer E/A-Adresse   */
    /* -------------------------------------------------------------------- */

    #include <dos.h>

    unsigned char inportb(int eaadr);

`inportb` liest ein einzelnes Byte von der durch `eaadr` angegebenen E/A-Adresse.

Funktionswert :
  gelesenes Byte

    /* -------------------------------------------------------------------- */
    /*   inport  -  Lesen eines Int-Wertes (2 Bytes) von einer E/A-Adresse  */
    /* -------------------------------------------------------------------- */

    #include <dos.h>

    int inport(int eaadr);

`inport` liest einene Integerwert (2 Bytes) von der durch `eaadr` angegebenen E/A-Adresse (und der nächsten Adresse).

Funktionswert :
  gelesener Integerwert

    /* -------------------------------------------------------------------- */
    /*      outportb      -     Schreiben eines Bytes in eine E/A-Adresse   */
    /* -------------------------------------------------------------------- */

    #include <dos.h>

    void outportb(int eaadr, unsigned char wert);

`outportb` schreibt das Byte mit dem Wert `wert` in die durch `eaadr` angegebene E/A-Adresse.

Funktionswert :
  keiner

    /* -------------------------------------------------------------------- */
    /*  outport - Schreiben eines Int-Wertes (2 Bytes) in eine E/A-Adresse  */
    /* -------------------------------------------------------------------- */

    #include <dos.h>

    void outport(int eaadr, int wert);

`outport` schreibt den Integer-Wert `wert` in die durch `eaadr` angegebene E/A-Adresse (und in die nächste Adresse).

Funktionswert :
  keiner

### Beispiel für den direkten Zugriff auf E/A-Adressen in TURBO-C

    /*---------------------------------------------------------------------*/
    /* Programm CMRAM                                                      */
    /*---------------------------------------------------------------------*/
    /* Lesen aus dem CMOS-RAM eines IBM/AT                                 */
    /* Beispiel für den direkten Zugriff auf E/A-Adressen in TURBO-C       */
    /*---------------------------------------------------------------------*/

    #include <dos.h>
    #include <stdio.h>

    #define CM_AR     0x70               /* CMOS-RAM - Adress-Register */
    #define CM_DR     0x71               /* CMOS-RAM - Daten-Register  */
    #define WRITELN   putchar('\n')
    #define HEADER    "\n*** Lesen des CMOS-RAM ***\n"

    void main(void)
    {
      unsigned cm_adr;
      unsigned char wert;

      printf(HEADER);
      while ((printf("\nAdresse ? "),scanf("%i",&cm_adr))!=EOF)
      { outportb(CM_AR, cm_adr);
        wert=inportb(CM_DR);
        printf("Wert    : %02X\n",wert&0x0ff);
      }
      WRITELN;
    }

Bildschirmausgabe :

    E:\RT\C>cmram

    *** Lesen des CMOS-RAM ***

    Adresse ? 0x07
    Wert    : 29

    Adresse ? 0x08
    Wert    : 04

    Adresse ? 0x09
    Wert    : 91

    Adresse ? 0x10
    Wert    : 40

    Adresse ? 0x12
    Wert    : F0

    Adresse ? 0x15
    Wert    : 80

    Adresse ? 0x16
    Wert    : 02

    Adresse ? ^Z

Zum Inhaltsverzeichnis | Zum nächsten Abschnitt

###### Copyright © FH München, FB 04, Prof. Dr. Rainer Thomas
V - PC - 811 - 00 - TH - 05
V - PC - 812 - 00 - TH - 04
V - PC - 813 - 00 - TH - 04
V - PC - 814 - 00 - TH - 03
V - PC - 821 - 00 - TH - 06
V - PC - 825 - 00 - TH - 01
V - PC - 826 - 00 - TH - 07
V - PC - 827 - 00 - TH - 02
V - PC - 831 - 00 - TH - 05
V - PC - 832 - 00 - TH - 05
V - PC - 841 - 01 - TH - 05
V - PC - 841 - 02 - TH - 05
V - PC - 842 - 01 - TH - 04
V - PC - 842 - 02 - TH - 02
V - PC - 842 - 03 - TH - 02
V - PC - 843 - 00 - TH - 01
V - PC - 844 - 00 - TH - 02
V - PC - 845 - 00 - TH - 02
V - PC - 846 - 00 - TH - 03
V - PC - 847 - 00 - TH - 05
V - PC - 848 - 00 - TH - 06
V - PC - 851 - 00 - TH - 05
V - PC - 852 - 00 - TH - 05
V - PC - 861 - 00 - TH - 04
V - PC - 863 - 00 - TH - 04
V - PC - 864 - 00 - TH - 02
V - PC - 871 - 00 - TH - 03
V - PC - 872 - 00 - TH - 01
V - PC - 873 - 00 - TH - 04
V - PC - 875 - 00 - TH - 03
V - PC - 876 - 00 - TH - 02
V - PC - 878 - 00 - TH - 04
V - PC - 879 - 00 - TH - 01