Felder in C

Visualisierung eines Integer-Feldes

Der Artikel Felder in C beschreibt die Verwendung von Feldern in der Programmiersprache C. In Feldern kann ein C-Programmierer im Gegensatz zu Variablen und Konstanten nicht nur einen, sondern mehrere Werte desselben Datentyps speichern.

Definition

Varianten

Ein Feld kann auf verschiedene Arten definiert werden. Die erste Variante besteht aus drei Teilen. Erstens aus dem Feldtyp, der mit dem Datentyp der enthaltenen Werte übereinstimmen muss. Zweitens aus dem gewünschten Namen des Feldes und drittens aus der Anzahl der gespeicherten Werte in eckigen Klammern. Das allgemeine Schema der ersten Variante lautet FELDTYP FELDNAME[INDEX].

int ganzzahlen[3];               /* Definition eines Feldes, das drei Werte als Integer enthält */

In obiger Variante werden die einzelnen Feldwerte vom Programmierer nicht initialisiert. Auch der Compiler initialisiert die Feldelemente nur dann jeweils mit 0, wenn es sich um globale oder statische Felder handelt; in allen anderen Fällen werden die Elemente nicht automatisch mit 0 initialisiert. Alternativ zur ersten Variante können die Feldwerte gleich bei der Felddefinition initialisiert werden. In diesem Fall ermittelt der Compiler den Index automatisch als die Anzahl der übergebenen Werte.

int ganzzahlen[] = { 1, 2, 3 };  /* Definition eines Feldes, das die Werte 1, 2 und 3 als Integer enthält */

In einer weiteren Variante kann der Index zusätzlich zu den bei der Definition angegebenen Werten angegeben werden. In diesem Fall werden alle nicht angegebenen Feldelemente mit 0 initialisiert.

int ganzzahlen[5] = { 1, 2, 3 }; /* Definition eines Feldes, das die Werte 1, 2, 3, 0 und 0 als Integer enthält */

Eine letzte Variante, ein Feld zu definieren ist nur für Felder vom Feldtyp char möglich. Sie wird verwendet, um Felder mit einer Zeichenkette zu initialisieren. Der Compiler fügt der angegebenen Zeichenkette dabei automatisch den Wert 0 am Ende hinzu.

char zeichen[] = "test"; /* Definition eines Feldes, das die Werte t, e, s, t und 0 als Character enthält */
                         /* gleichbedeutend, aber umständlicher sind folgende Ausdrücke:                  */
                         /*       char zeichen[5] = { 't', 'e', 's', 't' };                               */
                         /*       char zeichen[] = { 't', 'e', 's', 't', '\0' };                          */

Bestandteile

Als Feldtyp können alle vordefinierte Datentypen außer void sowie selbstdefinierte Datentypen verwendet werden. Für die Wahl eines Feldnamens gelten dieselben Regeln wie für die Wahl eines Variablen- und Konstantennamens. Die im Feld enthaltenen Werte werden auch als Objekte oder Elemente des Feldes bezeichnet.

Die Anzahl der im Feld gespeicherten Werte wird als Index des Feldes bezeichnet. Der Index eines Feldes kann nach der Definition nicht mehr geändert werden, ein Feld ist also während der gesamten Laufzeit des Programms gleich groß. Ein Index startet immer bei Null. Das bedeutet, dass das erste Feldelement am Index 0 liegt, das zweite am Index 1 usw. Da ein Index immer mit Null beginnt, ist es nicht möglich, beispielsweise ein Feld mit den Indizes 100 bis 200 zu erstellen. Da die Feldlänge an verschiedenen Stellen im Programm benötigt werden kann, ist es üblich, sie nur einmal am Anfang des Programms als Präprozessordirektive festzulegen.

#define FELDLAENGE 3
int ganzzahlen[FELDLAENGE];

Die Feldlänge lässt sich mithilfe des unären Operators sizeof ermitteln.

int feld[3];
size_t feldgroesse_in_byte = sizeof(feld);
size_t feldelementgroesse_in_byte = sizeof(feld[0]);
size_t feldindex = feldgroesse_in_byte / feldelementgroesse_in_byte;

Speicherplatz

Bei der Definition eines Feldes fordert der Compiler den benötigten Speicherplatz automatisch an. Die Speicheradresse, an der das Feld liegt, kann vom Programmierer nicht verändert werden. Die Größe des benötigten Speichers hängt vom Datentyp der Feldelemente ab. Ein Feld, das beispielsweise drei Integer enthält, verbraucht drei Mal den Speicherplatz, den ein Integer benötigt.

Die Feldelemente liegen im Speicher direkt hintereinander. Die Adresse des Feldes ist genau dieselbe wie die des ersten Feldelements. Die Adresse des zweiten Feldelements ist die Adresse des ersten Elements plus Eins usw.

Wertzuweisung

Nachdem ein Feld definiert worden ist, können ihm Werte des bei der Definition angegebenen Datentyps zugewiesen werden. Dazu muss auch der Index angegeben werden, an dem der Wert stehen soll.

ganzzahlen[0] = 23;            /* Zuweisung des Wertes 23 an der Indexstelle 0 */
ganzzahlen[1] = 1;
ganzzahlen[2] = 1;
ganzzahlen[0] = 22;

Oft werden Feldwerte mithilfe der For-Schleife zugewiesen.

int ganzzahlen[3];
for (int i = 0; i < 3; i++) {  /* Nach der Schleife steht am Index 0 der Wert 1, am Index 1 der */
    ganzzahlen[i] = i + 1;     /*  Wert 2 und am Index 2 der Wert 3 */
}

Wird außerhalb des Index gelesen oder geschrieben, versucht das Programm trotzdem, auf die jeweilige Speicheradresse zuzugreifen. Wenn das Betriebssystem erkennt, dass das Programm auf eine Speicheradresse zugreifen will, die ihm nicht zugeordnet ist, kann es sein, dass das Programm abstürzt. Wenn die Speicheradresse zufällig dem Programm zugeordnet ist, könnten die Daten dieser Speicherstelle ohne Fehlermeldung ausgelesen bzw. überschrieben werden.

int ganzzahlen[3];             /* Da der Index 82 nicht existiert, kann es zu einem  */
ganzzahlen[82] = 23;           /* Programmabsturz kommen bzw. können Daten überschrieben werden */

Konstante Felder

Sollen die die Werte eines Feldes unveränderbar sein, definiert man ein konstantes Feld.

const int ganzzahlen[3];

Sinn ergeben konstante Felder nur mit Initialisierungen (bei der Definition):

const int ganzzahlen[3] = { 4711, 4200, 0815 };

Mehrdimensionale Felder

In C ist auch die Verwendung mehrdimensionaler Felder möglich. Dabei ist jedes Feldelement selbst wiederum ein Feld.

int mehrdimensional[3][4];       /* Zweidimensionales Feld */
int mehrdimensional[3][4][15][2] /* Vierdimensionales Feld */

Im Speicher werden die Feldwerte eines mehrdimensionalen Feldes wie beim eindimensionalen Feld direkt hintereinander abgelegt. Auch der Zugriff auf die Werte erfolgt analog.

int ganzzahlen[2][2];
ganzzahlen[0][0] = 23;
ganzzahlen[0][1] = 15;
ganzzahlen[1][0] = 2;
ganzzahlen[1][1] = 0;

Felder als Funktionsparameter

In C kann einer Funktion sowohl der Wert einer Variablen als auch ein Zeiger auf die Variable übergeben werden. Von Feldern können hingegen keine Kopien, sondern ausschließlich Zeiger auf ihren Anfang übergeben werden. Dabei erhält die Funktion eine Kopie der Speicheradresse des Feldanfangs. Da die Funktion einen Zeiger erhält, kann sie die Feldelemente lesen und überschreiben.

feld[3];
funktion(feld);        /* auch ohne explizite Angabe der Funktion ein Zeiger auf das Feld übergeben */

Meistens wird auch der Index des Feldes an die Funktion mitübergeben. Dies liegt daran, dass die Feldgröße innerhalb der Funktion nicht mehr ermittelbar ist, da das Feld innerhalb der Funktion eigentlich kein Feld mehr ist, sondern ein konstanter Zeiger.

feld[3];
funktion(feld, 3);     /* auch ohne explizite Angabe der Funktion ein Zeiger auf das Feld übergeben */

Bei der Funktionsdeklaration wird angegeben, dass die Funktion beispielsweise ein eindimensionales Feld vom Feldtyp Integer als Parameter erwartet. Wird bei der Funktionsdeklaration ein Feldindex angegeben, wird diese Angabe vom Compiler ignoriert. Der Index wird deshalb als eigener Parameter übergeben.

void funktion(int feld[], int index);
void funktion(int  *feld, int index); /* gleichbedeutend */

Bei mehrdimensionalen Feldern müssen in der Funktionsdeklaration die erwarteten Indizes aller Dimensionen außer der ersten angegeben werden.

void funktion(int feld[][10][7], int index);
void funktion(int (*feld)[10][7],int index); /* gleichbedeutend */