vishia-CRuntime_Javalike - Behandlung von Feldern


In C(++) sind Felder von Strukturen bekannt und üblich, wie im Beispiel:

typedef struct Data_t
{ int a;
  float f;
}Data;

Data* dataArray1 = new Data[12];  //(C++)
Data* dataArray2 = (Data*)(malloc(12*sizeof(Data));  //C-Variante
Data dataArray3[12];              //direkt angelegt mit fester Größe

In Java sind dagegen alle Feldelemente, die keine einfache skalare Variable darstellen (int, float, ...), grundsätzlich über Referenzen geführt.

final class Data
{ int a;
  float f;
}


Data[] dataArray = new Data[12];

In dem gezeigtem Beispiel wird nicht etwa wie in C(++) ein Feld mit 12 Elementen vom Typ Data angelegt, sondern es wird nur ein Feld mit 12 nichtinitialisierten Referenzen auf den Typ Data angelegt. Würde Data nicht final sein, so könnten dort auch Instanzen referenziert werden, die von Data abgeleitetet sind. Um die Feldelemente tatsächlich anzulegen, ist nochmal Arbeit erforderlich:

for(int i = 0; i < dataArray.length(); i++){ dataArray[i] = new Data(); }

Erst damit bekommt man das in C erwartete Ergebnis. Java ist hier etwas komplizierter, aber dafür universell und sicher. Die Verfahrensweise entspricht dem allgemein in Java verwendeten Referenzkonzept. Java kennt keine "eingebetteten Daten", alle Daten werden grundsätzlich referenziert. Damit ist es immer möglich, keine Daten zu haben (null-Zeiger) oder Daten in Instanzen aus abgeleiteten Klassen, ohne dass das irgendwo besonders zu beachten ist:

class Example
{ int val;
  Data data;
}

entspricht in C(++)

struct Example
{ int val;
  Data* data;   //referenzierte Daten
}

und nicht

struct Example
{ int val;
  Data data;    //eingebettete Daten, in Java nicht möglich.
}

Eingebettete Daten

Es ist die Frage zu stellen: Sind eingebettete Daten in Feldelementen in C(++) nötig, oder kann man per Festlegung des Programmierstils mit der Variante leben, die Java entspricht. Sie kann eindeutig zugunsten der Notwendigkeit von eigebetteten Daten beantwortet werden. Aus folgenden Gründen:

Unter folgenden Umständen kann eine Java-Struktur in C(++) quellcodekompatibel mit eingebetteten Daten abgebildet werden:

Anzahl der Feldelemente und Indextest

In Java kann man von einem Array die Länge abfragen. Außerdem führt Java eine Exception aus, wenn mit falschem Index zugegriffen wird. Diese beiden Punkte sind Schwachpunkte in C. In C++ hat man mit den Templates und der Template-Library versucht, eine Abhilfe zu schaffen. Jedoch zeigt die Tatsache, dass oft auch in C++ Arrays wie in C angelegt werden, dass diese Template-Library nicht die Ideallösung ist.

Hier kann eine Herangehensweise aus Java nach C übertragen eine gute Abhilfe schaffen. Danach wird ein Feld in C(++) wie folgt definiert:

typedef struct DataArray_t
{ ARRAYHEAD_Jc
  Data data[100];
}DataArray;

ARRAYHEAD_Jc ist ein Makro und stellt eine Basisklasse Object_Jc, einen Eintragsplatz für die Anzahl der Feldelemente, einen Eintragsplatz für die Größe jedes Feldelementes und einige Mode-Bits bereit. Insgesamt werden für diesen Kopf des Feldes 16 Bytes benötigt (8 Bytes für Object_Jc).

Auf den Kopf folgen die Daten, und zwar unmittelbar. Die feste Angabe einer Elementanzahl (hier [100]) ist für die C-Syntax notwendig, es ist aber zur Laufzeit nicht bindend, dass es genau 100 Elemente sein müssen. Zur Laufzeit kann bei einer nichtstatischen Anlage der Daten auch für mehr oder weniger Elemente Speicher bereitgestellt werden. Die tatsächliche Elementeanzahl ist in den Kopfdaten gespeichert. Syntaktisch richtig wäre es auch, nur 1 Element zu reservieren. Die Angabe einer größeren Anzahl stellt eine Debughilfe dar. Für den Debugger, zum Beispiel von Visual Studio, ist eine Elementeanzahl angegeben, die unmittelbar betrachtet werden kann.

Für das Feld muss noch ein Konstruktor und eine new-Methode definiert werden. Der Konstruktor ist als Funktionsprototyp zu deklarieren und in einem C(pp)-File auszuprogrammieren. Im Konstruktor sind die Kopfdaten zu füllen. Sinnvollerweise können im Konstruktor auch die Datenelemente geeignet belegt werden.

Die new-Methode kann dagegen als define ausgeführt werden:

/** Constructs the array */
METHOD_C DataArray* constructor_DataArray(DataArray* ythis, int size);

#define new_DataArray(SIZE) (constructor_DataArray( (DataArray*)malloc(sizeof_ARRAY_Jc(Data, SIZE )), SIZE))

Die new-Konstruktion kann ähnlich dem new in C++ oder Java verwendet werden und legt ein Feld im Heap komplett an. Die Byteanzahl wird mit einem Makro ermittelt, das in Object_Jc.h definiert ist. Für dieses new-Konstrukt gibt der Konstruktor als Returnwert den ythis-Zeiger zurück, sonst wäre die Makro-Schreibweise nicht möglich (es würde eine Zwischenvariable benötigt werden). Das ist aber laufzeittechnisch problemlos.

Der Konstruktor kann in etwa wie folgt aussehen:

METHOD_C DataArray* constructor_DataArray(DataArray* ythis, int size)
{ constructor_ARRAY_Jc(ythis, size, sizeof(Data), &reflection__DataArray.reflection);
  memset(ythis->data, 0, size * ythis->sizeElement);
  return ythis;
}

Dabei wird der constructor_ARRAY_Jc() gerufen, um die Kopfdaten einschließlich der Object_Jc-Daten zu initialisieren (Reflection-Infos).

Die Anwendung ist dann recht einfach. Im folgenden Beispiel werden mehrere Zugriffe gezeigt. Die Varianten mit Angabe des Index in eckigen Klammern arbeiten ohne Indextest. Darunter stehen jeweils adäquate Befehle mit Indextest. Diese bauen auf ein Makro element_ARRAY_Jc auf.

  DataArray* array = new_DataArray(25);


  array->data[11].a = 22;
  element_ARRAY_Jc(array, 12)->a = 23;

  { int ii;
    for(ii=0; ii < arrayB->length; ii++)
    { arrayB->data[11].b = ii+5;
    }
  }
  { int a = array->data[11].a;
        a = element_ARRAY_Jc(array, 12)->a;
  }

Die adäquate Java-Schreibweise wäre:

  DataArray[] array = new DataArray[25];


  array[11].a = 22;
  array[12].a = 23;
  { int ii;
    for(ii=0; ii < arrayB.length; ii++)
    { arrayB[11].b = ii+5;
    }
  }

  { int a = array[11].a;
        a = array[12].a;
  }

Traditionell in C wäre zu programmieren:

  int lenghtArray;
  lengthArray = 25;
  Data* array = (Data*)malloc(25 * sizeof(Data));


  array[11].a = 22;
  if(12 < lengthArray) array[12].a = 23;

  { int ii;
    for(ii=0; ii < lengthArray; ii++)
    { arrayB[11].b = ii+5;
    }
  }
  { int a = array[11].a;
        a = (12 < lengthArray ? array[12].a : 0);
  }

Hier ist die Länge des Feldes manuell zu verwalten. Es gibt keine Reflections. Ist ein Indextest notwendig, dann muss der Anwender überlegen, war er dann tut. Im obigen Beispiel wird anstelle des Feldzugriffes eine 0 gelesen. Damit wird aber der Fehler verschleiert. Richtig ist es, eine Ausnahmebehandlung auszuführen, wie im Makro element_ARRAY_Jc() realisiert.