ArraysJava4C

ArraysJava4C

Inhalt


Die Möglichkeit, mit Arrays (Feldern) zu arbeiten, ist eine grundlegende Eigenschaft aller Programmiersprachen.

Die Möglichkeit, mit Arrays (Feldern) zu arbeiten, ist eine grundlegende Eigenschaft aller Programmiersprachen.

1 Arrays in C und C++

In C hat man bezüglich Arrays recht einfache Konstrukte: Ein Array ist eine Folge von Daten gleichen Typs im Speicher hintereinander. Bei der statischen Anlage von Arrays, egal ob global, stacklokal oder innerhalb einer struct gibt man in [] einfach die Anzahl der Elemente an:

 ArrayElementType exampleArray[123];   //statisches Array in C

Der Bezeichner exampleArray wird vom Compiler damit automatisch als ein Zeiger auf den Typ eines Elementes des Arrays geführt. Das ist praktisch und nützlich, aber gegebenfalls manchmal etwas verwirrend. Der Bezeichner einer ähnlich zu definierenden Struktur struct {...} name wird vom Compiler nicht als Zeiger sondern als die Instanz selbst interpretiert.

Der Zugriff auf die Array-Elemente ist auf zweierlei Weise möglich. Entweder man folgt der Interpretation des Namens des Arrays als Zeiger und kann also mit dem * davor auf das erste Element zugreifen, mit der üblichen Zeigerarithmetik damit auch auf alle anderen. Oder man schreibt intuitiv besser für den Zugriff den Index in Klammern:

 *(exampleArray+12) == exampleArray[12]

Der als Beispiel gezeigte Vergleich wird auf die Inhalte der Elemente ausgeführt, da beide Ausdrücke links und rechts das Element selbst bezeichnen.

Dynamische Arrays in C kann man sehr einfach mit alloc anlegen. Dabei hilft, dass der Zugang zu einem Array nichts anderes als ein simpler Zeiger auf den Array-Element-Typ ist. Ein wenig casting ist erforderlich, weil das Allokieren selbt typfrei ist. size ist die erforderliche Anzahl der Feldelemente.

 ArrayElementType* exampleArray = 
   (ArrayElementType*)malloc
     (size * sizeof(ArrayElementType));

In C++ hat man zunächst genau die selben Möglichkeiten wie in C, simple aber mit all den Problemen. Zusätzlich gibt es in C++ üblicherweise noch einige Library-Klassen, meist mit Templates, die Arrays in einer objektorientiert genehmen Weise verwalten. Hier büst man aber wiederum die Einfachkeit ein. Wie ein Template ein Array anlegt, ist dann nicht mehr simple. Daher wird man in vielen Fällen es doch wieder simple und easy C-like machen.

1.1 Probleme mit Arrays in C

Bezüglich des Compilerens gibt es außer den Irritationen Zeiger oder nicht keine Probleme. Aber zur Laufzeit können sich erhebliche Probleme ergeben, wenn man nicht sorgfältig genug codiert:

Bezüglich des Compilerens gibt es außer den Irritationen Zeiger oder nicht keine Probleme. Aber zur Laufzeit können sich erhebliche Probleme ergeben, wenn man nicht sorgfältig genug codiert:

Keine Längenüberprüfung: Per se kann man mit den Array-Zeigern beliebig herumoperieren, alles wird compiliert und läuft irgendwie. Sowohl exampleArray[-1] geht als auch ein zu großer Index. Es wird halt im Speicher herumgegriffen. Ein solcher Fehler wird gegebenenfalls nicht bemerkt, wenn halbwegs plausible Werte gelesen werden oder wenn irgendwohin fälschlicherweise geschrieben wird, wo es (zunächst) nicht auffällt. Das Problem sind ja nicht die konstanten Indizes, sondern berechnete Werte oder von außen erhaltene Werte.

Eine gute Programmmierung wird von außen erhaltene Indexwerte grundsätzlich überprüfen ("Wareneingangskontrolle"). Wenn die Werte für den Index jedoch innerhalb gebildet werden, kann auch die Richtigkeit des Algorithmus feststellbar sein, eine zusätzliche Überprüfung bei jedem Zugriff auf Array-Elemente ist dann sicherlich übertrieben. Am einfachsten lässt sich das mit einer Laufanweisung überprüfen:

 int idx; for(idx = 0; idx < size; idx++)
 { exampleArray[idx] = ...
 }

Aber auch hier können Fehler entstehen. Ist size die tatsächliche Länge des Feldes oder nur die erdachte Wunschlänge für den Algorithmus?

Fallen bei der Pointerarithmetik: Der Ausdruck exampleArray+7 bezeichnet die Adresse des 7. Elelentes des Arrays. Der Compiler berücksichtigt automatisch die Byteanzahl des Typs und multipliziert diese mit rein. Das ist ganz nett. Was ist, wenn man beispielsweise *exampleArray+7 schreibt und denkt, den Inhalt des 7. Elements zu bekommen. Das ist ein dummer Fehler, Klammenr vergessen! Aber fällt das auf? Weitere beliebige Verwechselungsmöglichkeiten gibt es, das liegt daran das Pointerarithmetik mit numerischer Artithmetik durcheinandergebracht werden kann, ohne dass es der Compiler merkt. Nur der sorgfältige Leser und Tester des Programmes könnte es bemerken, wenn er Klammern richtig zählt.

Wo steht die Länge des Array: Für Indexüberprüfungen oder Laufanweisungen muss man die Länge kennen. Die steht aber irgendwo. Damit gibt es eine Verwechslungsgefahr. Am einfachsten ist das mit statisch definierten Arrays. Hier hilft der sizeof()-Compilerbefehl. Er ermittelt die Byteanzahl eines Arrays, wenn man den Instanzbezeichner angibt. Das tut er so, obwohl der Instanzbezeichner bei Verwendung für einen Zugriff ein Zeiger ist. Mit einer kleinen Arithmetik:

 int nrofElements = sizeof(exampleArray) / sizeof(exampleArray[0]);

kann man die Anzahl der Elemente ohne weitere Hilfsmittel bestimmen. Die daraus sich ergebende Konstante wird zur Compilerzeit berechnet. - Das funktioniert aber nur bei statischen Arrays.

2 Arrays in Java

In Java hat man bei der Sprachschöpfung die Probleme, die seit 20 Jahren in C bekannt gewesen sind, berücksichtigen können. Ein Array in Java wird von der Syntax/Semantik her ähnlich notiert wie in C, ist aber von vornherein etwas komplexer:

In Java hat man bei der Sprachschöpfung die Probleme, die seit 20 Jahren in C bekannt gewesen sind, berücksichtigen können. Ein Array in Java wird von der Syntax/Semantik her ähnlich notiert wie in C, ist aber von vornherein etwas komplexer:

int exampleArrayInt[] = new int[12];

Das ist ein einfaches int-Array mit 12 Elementen. Auffallend ist die Notwendigkeit des Operators new. Ein Array wird nicht einfach im Stack angelegt, oder innerhalb einer Klasse, oder global, sondern so wie in java üblich immer über den Heap.

Die eckigen klammern können in Java wie in C/C++ hinter dem Instanzenbezeichner notiert werden. Möglich, üblich und besser ist die Notation hinter dem Typ. Der Typ wird als Array qualifiziert:

 int[] exampleArrayInt = new int[12];

Der Zugriff auf Array-Elemente erfolgt wie in C mit eckigen klammern. Eine alternative Zeigerartithmetik gibt es aber nicht:

 int value7 = exampleArrayInt[7]; 

Der Bezeichner exampleArray referenziert das Array. Dieses Array hat eine Kopfstruktur, die von java.lang.Object abgeleitet ist. Außerdem ist dort die Länge des Arrays gespeichert:

 int length = exampleArrayInt.length;

Die Länge ist also am Array immer richtig gespeichert. Bei einem Zugriff wird der Index gegen die Länge gecheckt. Das erfolgt immer, der Anwender kann hierbei nichts vergessen. Allerding wird nicht immer Rechenzeit verbraten. Dazu gibt es schließlich intelligente Vorverarbeitungen wie JIT (Just in Time-Compiler). Dabei werden Optimierungen ausgeführt.

Arrays aus strukturierten Daten, in Java sind das Klassen, sehen erstmal so ähnlich aus wie Arrays aus simplen (skalaren) Daten:

 Typ[] exampleArray = new Typ[12];

Hier werden aber nicht etwa 12 Instanzen von Typ angelegt, sondern nur 12 Referenzen auf mögliche Instanzen, die also mit null initialisiert sind. Die Instanzen selbst muss man dann mühevoll in einer Schleife anlegen:

 for(int idx = 0; idx < exampleArray.length; idx++)
 { exampleArray[idx] = new Typ(...);
 }

Das erscheint zweckfrei, wenn man genau diesen Typ ohne weitere Parameter braucht. Aber selbstverständlich sind auf den 12 Speicherplätzen des Arrays auch Referenzen auf abgeleitete Typen speicherbar, außerdem jeweils mit verschiedenen Initialwerten (Konstruktor-Argumenten). Das sind Grundeigenschaften der Objektorientierung, die immer gehen müssen. Nur der simpelste Fall hat gegenüber der Einfachheit von C diesen gezeigten Grundaufwand:

 for(int idx = 0; idx < exampleArray.length; idx++)
 { Data data = Database.getData()
   if(data.testX) { exampleArray[idx] = new TypX(data.getValueX()); }
   else { exampleArray[idx] = new TypY(data.getValueY()); }
 }

In diesem Beispiel muss also TypX und TypY beide von Typ abgeleitet sein. Angelegt wrid der abgeleitete Typ. Gespeichert wird aber die Referenz auf den Grundtyp. Mit den Möglichkeiten des dynamischen Bindens (dynamischer Methodenaufruf) werden die jeweils richtigen Operationen ausgeführt.

Ein mehrdimensionales Feld aus simplen (skalaren) Typen sieht wie in C aus und wird wie in Java üblich adäquat angelegt:

 int[][] exampleArray2Int = new int[12][15].

Der Zugriff wird genauso wie in C geschrieben:

 int value = exampleArray2int[7][3].

Schreibt man

 int[] vector = exampleArray2int[7];

dann erhält man das eindimensionale Array, das auf der 2. Dimension auf der 7. Position steht. Dessen Länge kann ausgelesen werden, dieses Array kann als Instanz des int[]-Typs weiterverarbeitet werden. In C würde man einen Zeiger auf irgendwie das erste Element der 7. übergeordneten Dimension bekommen, oder, wenn man das Array sich im Speicher flach vorstellt, einen Zeiger auf das 7*15 Element. Wieviel Elemente dazugehören, ... muss man irgendwo selbt programmieren.

Bei Arrays aus strukturierten Typen sieht das grundsätzlich genauso aus.

3 Arrays in C mit Java2C übersetzt

Schreibt man einen Algorithmus in Java und übersetzt ihn mit dem Java2C-Translator nach C, dann bekommt man etwas in C gut anwendbares mit Java-like touch, nämlich Arrays mit einer Kopfstuktur. Für die Implementierung von einfachen strukturierten Daten sind aber die C-Gepflogenheiten berücksichtigt. Das wesentliche dabei ist, dass man in embedded Anwendungen die Daten nicht irgendwo in einem Heap verstreut haben will, sondern gegebenenfalls in bestimmten Speicherbereichen und jedenfalls zusammen. Das kann man (fast) mit Java-Standard-Sprachmitteln ausdrücken:

final int embeddedArrayint = new int[123];
 /** @Java2C=embeddedArrayElement. */
 final Type[] embeddedArray = new Type[12];

Das embeddedArrayint kann und wird deshalb an dieser Stelle nicht referenziert sondern wird innerhalb der Struktur angelegt, weil seine Größe auch in Java feststeht. Da die Elemente simple, skalar sind, wird kein anderer Speicherbereich allokiert, alles liegt beieinander. Bei dem embeddedArray aus strukturierten Daten kann mit den reinen Sprachmitteln an dieser Stelle nicht sicher entschieden werden, ob auch die Array-Elemente embedded sein können. Sie werden in Java hier noch nicht initialisiert. Daher ist eine Hilfe angebracht, die gezeigten @java2C=-Annotation im Kommentar. Wenn dann in der weiteren Programmierung dazu ein Wiederspruch auftreten sollte, darf das der Java2C-Translator als Fehler melden, obwohl es in Java ginge. Beispiel dafür ist die für Java gezeigte Initialisierung mit einer abgeleiteten Klasse. Das geht dann nicht. Man muss sich also entscheiden: Möchte man ein einfaches Speicherlayout, dann kann man auch nur einfache Strukturen anlegen. Im embedded Bereich passt das aber meist miteinander.

In C entsteht für den embedded Fall folgendes:

 struct { ObjectJc object;
           int32 length; 
           int16 sizeElement; 
           int16 mode; 
           int32 data[123]; 
           } embeddedArrayint;  /*Embedded int array. */

Man hat also Kopfdaten mit der Länge, und danach die Feldelemente, zusammengefasst in einer Gesamt-Struktur.

Schreibt man einen Algorithmus in Java und übersetzt ihn mit dem Java2C-Translator nach C, dann bekommt man etwas in C gut anwendbares mit Java-like touch, nämlich Arrays mit einer Kopfstuktur. Für die Implementierung von einfachen strukturierten Daten sind aber die C-Gepflogenheiten berücksichtigt. Das wesentliche dabei ist, dass man in embedded Anwendungen die Daten nicht irgendwo in einem Heap verstreut haben will, sondern gegebenenfalls in bestimmten Speicherbereichen und jedenfalls zusammen. Das kann man (fast) mit Java-Standard-Sprachmitteln ausdrücken:

final int embeddedArrayint = new int[123];
 /** @Java2C=embeddedArrayElement. */
 final Type[] embeddedArray = new Type[12];

Das embeddedArrayint kann und wird deshalb an dieser Stelle nicht referenziert sondern wird innerhalb der Struktur angelegt, weil seine Größe auch in Java feststeht. Da die Elemente simple, skalar sind, wird kein anderer Speicherbereich allokiert, alles liegt beieinander. Bei dem embeddedArray aus strukturierten Daten kann mit den reinen Sprachmitteln an dieser Stelle nicht sicher entschieden werden, ob auch die Array-Elemente embedded sein können. Sie werden in Java hier noch nicht initialisiert. Daher ist eine Hilfe angebracht, die gezeigten @java2C=-Annotation im Kommentar. Wenn dann in der weiteren Programmierung dazu ein Wiederspruch auftreten sollte, darf das der Java2C-Translator als Fehler melden, obwohl es in Java ginge. Beispiel dafür ist die für Java gezeigte Initialisierung mit einer abgeleiteten Klasse. Das geht dann nicht. Man muss sich also entscheiden: Möchte man ein einfaches Speicherlayout, dann kann man auch nur einfache Strukturen anlegen. Im embedded Bereich passt das aber meist miteinander.

In C entsteht für den embedded Fall folgendes:

 struct { ObjectJc object;
           int32 length; 
           int16 sizeElement; 
           int16 mode; 
           int32 data[123]; 
           } embeddedArrayint;  /*Embedded int array. */

Man hat also Kopfdaten mit der Länge, und danach die Feldelemente, zusammengefasst in einer Gesamt-Struktur.