Namespaces
Variants

Array declaration

From cppreference.net

Array ist ein Typ, der aus einer zusammenhängend allozierten, nicht-leeren Folge von Objekten mit einem bestimmten element type besteht. Die Anzahl dieser Objekte (die Arraygröße) ändert sich während der Lebensdauer des Arrays nie.

Inhaltsverzeichnis

Syntax

In der Deklarationsgrammatik einer Array-Deklaration bezeichnet die type-specifier -Sequenz den element type (der ein vollständiger Objekttyp sein muss), und der declarator hat die Form:

[ static (optional) Qualifizierer  (optional) Ausdruck  (optional) ] Attribut-Spezifizierungssequenz  (optional) (1)
[ Qualifizierer  (optional) static (optional) Ausdruck  (optional) ] Attribut-Spezifizierungssequenz  (optional) (2)
[ Qualifizierer  (optional) * ] Attribut-Spezifizierungssequenz  (optional) (3)
1,2) Allgemeine Array-Deklarator-Syntax
3) Deklarator für VLA unbestimmter Größe (kann nur im Funktionsprototyp-Bereich erscheinen) wo
expression - jeder Ausdruck außer dem Komma-Operator , bezeichnet die Anzahl der Elemente im Array
qualifiers - jede Kombination von const , restrict oder volatile Qualifizierern, nur in Funktionsparameterlisten erlaubt; dies qualifiziert den Zeigertyp, in den dieser Array-Parameter umgewandelt wird
attr-spec-seq - (C23) optionale Liste von Attributen , angewendet auf das deklarierte Array
float fa[11], *afp[17]; // fa ist ein Array von 11 Floats
                        // afp ist ein Array von 17 Zeigern auf Floats

Erklärung

Es gibt mehrere Varianten von Array-Typen: Arrays mit bekannter konstanter Größe, Arrays variabler Länge und Arrays unbekannter Größe.

Arrays konstanter bekannter Größe

Wenn expression in einem Array-Deklarator ein Integer-Konstantenausdruck mit einem Wert größer als null ist und der Elementtyp ein Typ mit bekannter konstanter Größe ist (d.h. die Elemente sind keine VLA) (seit C99) , dann deklariert der Deklarator ein Array konstanter bekannter Größe:

int n[10]; // Ganzzahlige Konstanten sind konstante Ausdrücke
char o[sizeof(double)]; // sizeof ist ein konstanter Ausdruck
enum { MAX_SZ=100 };
int n[MAX_SZ]; // Enum-Konstanten sind konstante Ausdrücke

Arrays von konstanter bekannter Größe können Array-Initialisierer verwenden, um ihre Anfangswerte bereitzustellen:

int a[5] = {1,2,3}; // deklariert int[5] initialisiert mit 1,2,3,0,0
char str[] = "abc"; // deklariert char[4] initialisiert mit 'a','b','c','\0'

In Funktionsparameterlisten sind zusätzliche Syntaxelemente innerhalb der Arraydeklaratoren erlaubt: das Schlüsselwort static und Qualifizierer , die in beliebiger Reihenfolge vor der Größenangabe erscheinen können (sie können auch auftreten, wenn die Größenangabe weggelassen wird).

In jedem Funktionsaufruf an eine Funktion, bei der ein Array-Parameter das Schlüsselwort static zwischen [ und ] verwendet, muss der Wert des aktuellen Parameters ein gültiger Zeiger auf das erste Element eines Arrays sein, das mindestens so viele Elemente enthält, wie durch expression angegeben:

void fadd(double a[static 10], const double b[static 10])
{
    for (int i = 0; i < 10; i++)
    {
        if (a[i] < 0.0) return;
        a[i] += b[i];
    }
}
// Ein Aufruf von fadd kann eine Überprüfung der Array-Grenzen zur Compile-Zeit durchführen
// und erlaubt auch Optimierungen wie das Vorabrufen von 10 Doubles
int main(void)
{
    double a[10] = {0}, b[20] = {0};
    fadd(a, b); // OK
    double x[5] = {0};
    fadd(x, b); // undefiniertes Verhalten: Array-Argument ist zu klein
}

Wenn qualifiers vorhanden sind, qualifizieren sie den Zeigertyp, in den der Array-Parametertyp transformiert wird:

int f(const int a[20])
{
    // in dieser Funktion hat a den Typ const int* (Zeiger auf const int)
}
int g(const int a[const 20])
{
    // in dieser Funktion hat a den Typ const int* const (konstanter Zeiger auf const int)
}

Dies wird häufig verwendet mit dem restrict Typqualifizierer:

void fadd(double a[static restrict 10],
          const double b[static restrict 10])
{
    for (int i = 0; i < 10; i++) // Schleife kann entrollt und neu geordnet werden
    {
        if (a[i] < 0.0)
            break;
        a[i] += b[i];
    }
}

Arrays variabler Länge

Wenn expression kein integer constant expression ist, ist der Deklarator für ein Array variabler Größe.

Jedes Mal, wenn der Kontrollfluss über die Deklaration läuft, expression wird ausgewertet (und muss immer zu einem Wert größer als Null ausgewerten), und das Array wird allokiert (entsprechend endet die lifetime einer VLA, wenn die Deklaration den Gültigkeitsbereich verlässt). Die Größe jeder VLA-Instanz ändert sich während ihrer Lebensdauer nicht, aber bei einem weiteren Durchlauf durch denselben Code kann sie mit einer anderen Größe allokiert werden.

#include <stdio.h>
int main(void)
{
   int n = 1;
label:;
   int a[n]; // 10-mal neu alloziert, jedes Mal mit einer anderen Größe
   printf("The array has %zu elements\n", sizeof a / sizeof *a);
   if (n++ < 10)
       goto label; // das Verlassen des Gültigkeitsbereichs einer VLA beendet ihre Lebensdauer
}

Wenn die Größe * ist, handelt es sich um eine Deklaration eines VLA mit nicht spezifizierter Größe. Eine solche Deklaration darf nur im Funktionsprototyp-Bereich erscheinen und deklariert ein Array eines vollständigen Typs. Tatsächlich werden alle VLA-Deklaratoren im Funktionsprototyp-Bereich so behandelt, als ob expression durch * ersetzt worden wäre.

void foo(size_t x, int a[*]);
void foo(size_t x, int a[x])
{
    printf("%zu\n", sizeof a); // gleich wie sizeof(int*)
}

Arrays mit variabler Länge und die davon abgeleiteten Typen (Zeiger auf sie, etc.) werden allgemein als "variably-modified types" (VM) bezeichnet. Objekte jedes variably-modified type dürfen nur im Blockbereich oder Funktionsprototypbereich deklariert werden.

extern int n;
int A[n];            // Fehler: VLA im Dateibereich
extern int (*p2)[n]; // Fehler: VM im Dateibereich
int B[100];          // OK: Array mit konstanter bekannter Größe im Dateibereich
void fvla(int m, int C[m][m]); // OK: VLA im Prototypenbereich

VLA muss automatische oder allokierte Speicherdauer haben. Zeiger auf VLA, aber nicht VLA selbst können ebenfalls statische Speicherdauer haben. Kein VM-Typ darf Verknüpfung haben.

void fvla(int m, int C[m][m]) // OK: Block-Bereich/Auto-Dauer-Zeiger auf VLA
{
    typedef int VLA[m][m]; // OK: Block-Bereich-VLA
    int D[m];              // OK: Block-Bereich/Auto-Dauer-VLA
//  static int E[m]; // Fehler: VLA mit statischer Dauer
//  extern int F[m]; // Fehler: VLA mit Verknüpfung
    int (*s)[m];     // OK: Block-Bereich/Auto-Dauer-VM
    s = malloc(m * sizeof(int)); // OK: s zeigt auf VLA im allokierten Speicher
//  extern int (*r)[m]; // Fehler: VM mit Verknüpfung
    static int (*q)[m] = &B; // OK: Block-Bereich/statische Dauer-VM}
}

Variabel modifizierte Typen können keine Mitglieder von Structs oder Unions sein.

struct tag
{
    int z[n]; // Fehler: VLA-Strukturmitglied
    int (*y)[n]; // Fehler: VM-Strukturmitglied
};
(seit C99)

Falls der Compiler die Makrokonstante __STDC_NO_VLA__ auf den ganzzahligen Wert 1 definiert, dann werden VLA- und VM-Typen nicht unterstützt.

(seit C11)
(bis C23)

Falls der Compiler die Makrokonstante __STDC_NO_VLA__ auf den ganzzahligen Wert 1 definiert, dann werden VLA-Objekte mit automatischer Speicherdauer nicht unterstützt.

Die Unterstützung für VM-Typen und VLAs mit allokierter Speicherdauer ist zwingend vorgeschrieben.

(seit C23)

Arrays unbekannter Größe

Wenn expression in einem Array-Deklarator weggelassen wird, deklariert es ein Array unbekannter Größe. Außer in Funktionsparameterlisten (wo solche Arrays in Zeiger umgewandelt werden) und wenn ein initializer verfügbar ist, ist solch ein Typ ein incomplete type (beachte, dass VLA unbestimmter Größe, deklariert mit * als Größe, ein complete type ist) (seit C99) :

extern int x[]; // der Typ von x ist "Array unbekannter Größe von int"
int a[] = {1,2,3}; // der Typ von a ist "Array von 3 int"

Innerhalb einer struct -Definition kann ein Array unbekannter Größe als letztes Mitglied auftreten (sofern mindestens ein anderes benanntes Mitglied vorhanden ist), in welchem Fall es sich um einen Spezialfall handelt, der als flexible array member bekannt ist. Details siehe struct :

struct s { int n; double d[]; }; // s.d is a flexible array member
struct s *s1 = malloc(sizeof (struct s) + (sizeof (double) * 8)); // as if d was double d[8]


(seit C99)

Qualifizierer

Wenn ein Array-Typ mit einem const -, volatile - oder restrict (seit C99) Qualifizierer deklariert wird (was durch die Verwendung von typedef möglich ist), ist der Array-Typ nicht qualifiziert, sondern sein Elementtyp:

(bis C23)

Ein Array-Typ und sein Elementtyp werden stets als identisch qualifiziert betrachtet, mit der Ausnahme, dass ein Array-Typ niemals als _Atomic -qualifiziert angesehen wird.

(seit C23)
typedef int A[2][3];
const A a = {{4, 5, 6}, {7, 8, 9}}; // Array von Array von const int
int* pi = a[0]; // Fehler: a[0] hat Typ const int*
void* unqual_ptr = a; // OK bis C23; Fehler seit C23
// Hinweis: clang wendet die Regel in C++/C23 auch in C89-C17-Modi an

_Atomic darf nicht auf einen Array-Typ angewendet werden, obwohl ein Array von atomarem Typ erlaubt ist.

typedef int A[2];
// _Atomic A a0 = {0};    // Fehler
// _Atomic(A) a1 = {0};   // Fehler
_Atomic int a2[2] = {0};  // OK
_Atomic(int) a3[2] = {0}; // OK
(seit C11)

Zuweisung

Objekte vom Array-Typ sind keine modifizierbaren Lvalues , und obwohl ihre Adresse genommen werden kann, können sie nicht auf der linken Seite eines Zuweisungsoperators stehen. Allerdings sind Structs mit Array-Mitgliedern modifizierbare Lvalues und können zugewiesen werden:

int a[3] = {1,2,3}, b[3] = {4,5,6};
int (*p)[3] = &a; // okay, Adresse von a kann genommen werden
// a = b;            // Fehler, a ist ein Array
struct { int c[3]; } s1, s2 = {3,4,5};
s1 = s2; // okay: kann Strukturen mit Array-Mitgliedern zuweisen

Array-zu-Zeiger-Konvertierung

Jeder Lvalue-Ausdruck vom Array-Typ, wenn er in einem anderen Kontext als

(since C11)

erfährt eine implizite Konvertierung zum Zeiger auf sein erstes Element. Das Ergebnis ist kein L-Wert.

Wenn das Array als register deklariert wurde, ist das Verhalten des Programms, das eine solche Konvertierung versucht, undefiniert.

int a[3] = {1,2,3};
int* p = a;
printf("%zu\n", sizeof a); // gibt Größe des Arrays aus
printf("%zu\n", sizeof p); // gibt Größe eines Zeigers aus

Wenn ein Array-Typ in einer Funktionsparameterliste verwendet wird, wird er in den entsprechenden Zeigertyp umgewandelt: int f ( int a [ 2 ] ) und int f ( int * a ) deklarieren dieselbe Funktion. Da der tatsächliche Parametertyp der Funktion ein Zeigertyp ist, führt ein Funktionsaufruf mit einem Array-Argument eine Array-zu-Zeiger-Konvertierung durch; die Größe des Argument-Arrays ist für die aufgerufene Funktion nicht verfügbar und muss explizit übergeben werden:

#include <stdio.h>
void f(int a[], int sz) // deklariert tatsächlich void f(int* a, int sz)
{
    for (int i = 0; i < sz; ++i)
        printf("%d\n", a[i]);
}
void g(int (*a)[10]) // Zeiger auf Array-Parameter wird nicht transformiert
{
    for (int i = 0; i < 10; ++i)
        printf("%d\n", (*a)[i]);
}
int main(void)
{
    int a[10] = {0};
    f(a, 10); // konvertiert a zu int*, übergibt den Zeiger
    g(&a);    // übergibt einen Zeiger auf das Array (Größe muss nicht übergeben werden)
}

Mehrdimensionale Arrays

Wenn der Elementtyp eines Arrays ein anderes Array ist, wird gesagt, dass das Array mehrdimensional ist:

// Array von 2 Arrays mit jeweils 3 int-Werten
int a[2][3] = {{1,2,3},  // kann als 2x3-Matrix betrachtet werden
               {4,5,6}}; // mit row-major Layout

Beachten Sie, dass bei Anwendung der Array-zu-Zeiger-Konvertierung ein mehrdimensionales Array in einen Zeiger auf sein erstes Element konvertiert wird, z.B. Zeiger auf die erste Zeile:

int a[2][3]; // 2x3 Matrix
int (*p1)[3] = a; // Zeiger auf die erste 3-Elemente-Zeile
int b[3][3][3]; // 3x3x3 Würfel
int (*p2)[3][3] = b; // Zeiger auf die erste 3x3-Ebene

Mehrdimensionale Arrays können in jeder Dimension variabel modifiziert werden wenn VLAs unterstützt werden (seit C11) :

int n = 10;
int a[n][2*n];
(seit C99)

Hinweise

Deklarationen von Arrays mit der Länge Null sind nicht erlaubt, obwohl einige Compiler sie als Erweiterungen anbieten (typischerweise als Vor-C99-Implementierung von flexible array members ).

Wenn der Größen- ausdruck einer VLA Nebeneffekte hat, werden diese garantiert ausgelöst, außer wenn es Teil eines sizeof-Ausdrucks ist, dessen Ergebnis nicht davon abhängt:

int n = 5, m = 5;
size_t sz = sizeof(int (*[n++])[m++]); // n wird inkrementiert, m kann inkrementiert werden oder nicht

Referenzen

  • C23-Standard (ISO/IEC 9899:2024):
  • 6.7.6.2 Array-Deklaratoren (S.: TBD)
  • C17-Standard (ISO/IEC 9899:2018):
  • 6.7.6.2 Arraydeklaratoren (S: 94-96)
  • C11-Standard (ISO/IEC 9899:2011):
  • 6.7.6.2 Arraydeklaratoren (S: 130-132)
  • C99-Standard (ISO/IEC 9899:1999):
  • 6.7.5.2 Array-Deklaratoren (S: 116-118)
  • C89/C90 Standard (ISO/IEC 9899:1990):
  • 3.5.4.2 Array-Deklaratoren

Siehe auch

C++ Dokumentation für Array-Deklaration