23. Arrays

Arrays (Felder) sind Sammlungen von Daten gleicher Art, z.B. 20 ganze Zahlen. Auf die einzelnen Elemente wird über den Index, eine ganze Zahl, zugegriffen, analog zu Python-Sequenzen steht der Index in eckigen Klammern.

In C ist der Index des ersten Elements 0. Das bedeutet, dass der letzte Index eines N-elementigen Arrays N- 1 ist. Daher auch das Idiom

for (i=0; i<N; i++)

Auch wenn das alles etwas an Python-Sequenzen, sagen wir Listen, erinnert, sind Arrays doch etwas fundamental anderes. Aus NutzerInnensicht fällt zunächst auf, dass man sich vor der Benutzung entscheiden muss, im Augenblick sogar zur Compilezeit, wie groß das Array ist – C-Arrays können nicht wachsen. Weiter wissen sie nicht notwendig etwas über ihre Länge, es gibt also (in der Regel) keine len-Funktion für Arrays. Schließlich stehen in einem Array immer nur Daten gleicher Art, während wir in Listen und Tupeln recht beliebig Daten kombinieren konnten.

Daneben ist das Verhältnis von Arrays zu Listen wiederum ein wenig so wie das von C- zu Python-Variablen. Arrays stellen direkt den Speicherplatz für die Daten, die in ihnen stehen, zur Verfügung, in Python-Listen stehen immer nur Verweise auf die Werte, die in der Liste stehen.

Definition

Eine Definition eines Array sieht so aus:

arrayDeclaration ::= typeSpecifier variableName
  "[" [integerLiteral ]"]" ";"

Beispiele:

unsigned int nums[14000];
char string[20];
char powers[]={1,2,4,8,16};

Im letzten Beispiel haben wir das Array initialisiert. Wenn wir das tun, können wir die Größe des Arrays weglassen, C macht es automatisch groß genug. Die Initializer sind immer mit Kommata getrennt und stehen in geschweiften Klammern.

Neuere C-Implementationen können auch nur einzelne Elemente initialisieren – der Rest wird dann automatisch auf Null gesetzt:

int flags[N_FLAGS]={[3]=23, [N_FLAGS-2]=4, 3};

setzt flags[3] auf 23 und die letzten beiden Elemente (also N_FLAGS-2 und N_FLAGS-1) auf 4 und 3, alles andere auf 0.

Nicht alle Maschinen vertragen beliebig große Arrays als lokale Variable. Wenn euer Programm abstürzt, bevor es überhaupt losläuft, seht mal nach, ob in main ein riesiges Array definiert wurde. Eine mögliche Abhilfe ist, das Array global zu definieren.

Array-Fallen

In C kann einfach über die Arraygrenzen rausgeschrieben werden, was fast sicher zum Absturz des Programms führt. Viele Cracks basieren auf diesen buffer overflows.

void crash1(void)
{
  int i, arr[20];

  for (i=0; i<=20; i++) {
    arr[i] = 0;
  }
}

Manche C-Implementationen erlauben per Compiler-Option, zur Laufzeit zu überprüfen, ob über die Arraygrenzen rausgeschrieben wurde, meistens unter irgendwas wie range-checking. Sowas ist nützlich zur Programmentwicklung, kostet in den fertigen Programmen aber natürlich massiv Zeit (die man aber häufig hat).

Zum Teil hilft auch die Hardware bei der Suche nach buffer overflows (das geht dann ohne Laufzeitstrafen). Eine Bibliothek, die so etwas tut, heißt electric fence (hilft aber prinzipbedingt leider nur bei dynamisch allozierten arrays, die wir erst später kennenlernen).

Übungen zu diesem Abschnitt

Ihr solltet euch wenigstens an den rötlich unterlegten Aufgaben versuchen

(1)

Wenn ihr mal einen Buffer Overflow in Aktion sehen wollt, probiert folgendes Programm (funktioniert so mit gcc 3.3 auf x86, für andere Compiler und Architekturen muss möglicherweise der Index in der Zuweisung geändert werden:

#include <stdio.h>

int main(void)
{
  int a[5];

  a[11] = (int)main;
  printf("Hallo Welt.\n");
  return 0;
}

Wenn ihr das Programm laufen lasst, sollte ganz oft “Hallo Welt” ausgegeben werden, bis das Programm endlich einen Segmentation Fault verursacht. Wenn das nicht so ist, spielt an der 11 rum, bis es passiert. Könnt ihr euch einen Reim darauf machen?


Markus Demleitner

Copyright Notice