A..1 Vereinbarung von freien Feldgrenzen

In C ist es leider nicht möglich, direkt einen beliebigen Wert für die untere Grenze eines Feldes anzugeben; durch die enge Verknüpfung von Feldern einerseits und dem Zeiger auf das erste (bzw. nullte) Feldelement andererseits beginnen alle Felder mit dem Index 0. Dies ist für alte FORTRAN- oder Pascal-Programmierer lästig, da in deren Sprachen die Feldgrenzen beliebig angegeben werden dürfen.

In C beginnen nun alle Felder mit dem Index 0. Bei jedem Feldzugriff muß man den gewünschten Index um den (gedachten) untersten Index erniedrigen. Dadurch hat man zwei Wertebereiche für die Feldindices: zum einen den gewünschten Bereich aus der Problemstellung (logische Feldgrenzen), und zum anderen den physikalischen Wertebereich des vereinbarten Feldes (von 0 bis (Anzahl der Elemente - 1), also die realen Feldgrenzen).

Beispiel: Ein Programm soll von den Zahlen 10 bis 20 die Quadrate berechnen, in einem Feld speichern und danach alle ausgeben.

Der logische Indexbereich geht also von 10 bis 20; der reale hat die gleiche Anzahl Elemente (11), aber beginnt bei 0; er reicht also in C von 0 bis 10 (einschließlich).

In Pascal ist das Problem ebenso leicht:

PROGRAM Quadrate( OUTPUT );

VAR     i:        integer;
        feld:     ARRAY[10..20] OF integer;

BEGIN
    { Die Quadrate der Zahlen von 10 bis 20 berechnen:       }
    FOR i:=10 TO 20 DO
        feld[i]:=i*i; 
    
    { Die berechneten Quadrate ausgeben:                     }
    FOR i:=10 TO 20 DO 
        writeln( 'Das Quadrat von ', i, ' ist ', feld[i] ); 
END.

wie in FORTRAN zu lösen:

      PROGRAM QUADRA
      INTEGER     I, FELD(10:20)

*     DIE QUADRATE BERECHNEN:
      DO 100, I = 10, 20 
        FELD(I) = I*I
 100  CONTINUE

*     UND AUSGEBEN:
      DO 200, I = 10, 20 
        PRINT*, 'DAS QUADRAT VON ', I, ' IST ', FELD(I)
 200  CONTINUE
 
      END

Das Umrechnen des logischen Datenbereichs auf reale Adressen im Speicher übernimmt hier der Compiler.

In C dagegen muß man ein Feld mit dem untersten Index 0 vereinbaren und bei jedem Feldzugriff den gewünschten logischen Index (hier 10 bis 20) um die untere Feldgrenze (10) erniedrigen, um auf die realen Indices ab 0 zu kommen:

/* arrq0.c 30. 7.95 kw
 */

#include <stdio.h>
#include <math.h>

int main()
{
  long      feld[11];    /* Feld von 10 bis 20                */
  int       i;           /* Schleifenzaehler                  */

  /* Die Quadrate der Zahlen 10 bis 20 berechnen:
   */
  for( i=10; i<=20; i++ )
  {
    /* i laeuft von 10 bis 20, aber abgespeichert werden
     * muss in feld[0] bis feld[10]:
     */
    feld[i-10] = (long)i*(long)i;
  }

  /* Analog jetzt die zuvor berechneten Quadrate ausgeben:
   */
  for( i= 10; i<=20; i++ )
  {   printf( "Das Quadrat von %i ist %ld\n",
              i,
              feld[i-10]
              );
  }
}

Greift man an vielen Stellen im Programm auf das Feld zu, dann wird das Umrechnen der logischen Feldindices auf die realen (ab 0) unübersichtlich und fehlerträchtig. Durch Einführung eines zusätzlichen Zeigers kann man das Problem aber lindern. Dieser Zeiger soll den Typ ,,Zeiger auf ein Feldelement`` haben. In C darf man einen Zeiger ebenso wie einen Feldnamen mit einem Index versehen, um auf ein Element unter- oder oberhalb der entsprechenden Adresse zugreifen zu können. Der Zeiger wird nun so initialisiert, daß er nicht auf das niedrigste vereinbarte Feldelement zeigt, sondern um die untere Feldgrenze ,,daneben``. Ist die untere Feldgrenze positiv oder größer als die Feldlänge, dann weist der Zeiger neben das Feld. Das macht aber nichts, solange man als Index einen Wert von der unteren bis zur oberen Feldgrenze verwendet:

/* arrq1.c 30. 7.95 kw
 */

#include <stdio.h>
#include <math.h>

int main()
{
  long      feld[11];      /* Feld von 10 bis 20              */
  long
    *feldbasis =         /* Zeiger auf das (gedachte)       */
    &feld[-10];          /* nullte Element                  */
  int       i;             /* Schleifenzaehler                */

  /* Die Quadrate der Zahlen 10 bis 20 berechnen:
   */
  for( i=10; i<=20; i++ )
  {
    /* i laeuft von 10 bis 20, aber abgespeichert werden
     * muss in feld[0] bis feld[10].
     * Durch die Initialisierung sind aber feld[0] und
     * feldbasis[10] identisch; ebenso die jeweils
     * folgenden Elemente bis feld[10] und feldbasis[20]:
     */
    feldbasis[i] = (long)i*(long)i;
  }

  /* Analog jetzt die zuvor berechneten Quadrate ausgeben:    */
  for( i= 10; i<=20; i++ )
  {
    printf( "Das Quadrat von %i ist %ld\n",
            i,
            feldbasis[i]
            );
  }
}

Über den Zeiger feldbasis kann man endlich die gewünschten logischen Indices verwenden, die nicht bei 0 beginnen. Um ein versehentliches Manipulieren des Zeigers feldbasis zu vermeiden, ist er als const vereinbart.

Diese Vorgehensweise ist aber immer noch fehlerträchtig, da man bei jeder Feldvereinbarung von neuem den Vorgang genau durchblicken muß.

Durch den Praeprozessor von C kann man die Vereinbarung weitgehend automatisieren. Man definiert sich einmalig ein Makro namens ARRAY (am besten in einer einzigen #include-Datei) und vereinbart dann mit dessen Hilfe das eigentliche Feld und den Hilfszeiger. Den Namen des gewünschten Feldes verwendet das Makro für den Hilfszeiger; der Name des eigentlichen Feldes wird durch Anhängen von _ vorweg und _ARRAY_BASE hinterher gebildet39. Die Bildung des Feldnamens erfolgt mit dem Praeprozessoroperator ##.

Das Makro ARRAY erhält vier Parameter: Den Typ eines jeden Elementes, gewünschten Feldnamen, sowie die beiden Feldgrenzen.

/* arrq2.c 30. 7.95 kw
 */

#include <stdio.h>
#include <math.h>

#define ARRAY(typ,name,min,max)                                      \
                    typ  _ ## name ## _ARRAY_BASE[max-min+1];        \
                    typ *name = &_ ## name ## _ARRAY_BASE[-min]

int main()
{
  ARRAY(long,feld,10,20);         /* Feld von 10 bis 20       */
  int       i;                    /* Schleifenzaehler         */

  /* Die Quadrate der Zahlen 10 bis 20 berechnen:
   */
  for( i=10; i<=20; i++ )
  {
    /* Mit feld[10] bis feld[20] kann man jetzt die in
     * _feld_ARRAY_BASE[0] bis _feld_ARRAY_BASE[10]
     * vereinbarten 11 Feldelemente ansprechen:
     */
    feld[i] = (long)i*(long)i;
  }

  /* Analog jetzt die zuvor berechneten Quadrate ausgeben:
   */
  for( i= 10; i<=20; i++ )
  {
    printf( "Das Quadrat von %i ist %ld\n",
            i,
            feld[i]
            );
  }
}

Die Definition des Makros ARRAY kann man in einer Headerdatei hinterlegen und bei Bedarf einfach mit #include einfügen.

AnyWare@Wachtler.de