Namespaces
Variants

Array declaration

From cppreference.net

Массив — это тип, состоящий из непрерывно выделенной непустой последовательности объектов с определённым типом элементов . Количество этих объектов (размер массива) никогда не меняется в течение времени жизни массива.

Содержание

Синтаксис

В грамматике объявлений объявления массива, type-specifier последовательность обозначает element type (который должен быть полным типом объекта), а declarator имеет форму:

[ static (необязательно) квалификаторы  (необязательно) выражение  (необязательно) ] attr-spec-seq  (необязательно) (1)
[ квалификаторы  (необязательно) static (необязательно) выражение  (необязательно) ] attr-spec-seq  (необязательно) (2)
[ квалификаторы  (необязательно) * ] attr-spec-seq  (необязательно) (3)
1,2) Общий синтаксис объявления массива
3) Декларатор для VLA неопределенного размера (может появляться только в области прототипа функции) где
expression - любое выражение, кроме оператора запятой , определяет количество элементов в массиве
qualifiers - любая комбинация квалификаторов const , restrict или volatile , разрешённая только в списках параметров функций; квалифицирует тип указателя, в который преобразуется данный массивный параметр
attr-spec-seq - (C23) опциональный список атрибутов , применяемых к объявленному массиву
float fa[11], *afp[17]; // fa - это массив из 11 элементов типа float
                        // afp - это массив из 17 указателей на float

Объяснение

Существует несколько разновидностей массивов: массивы известного постоянного размера, массивы переменной длины и массивы неизвестного размера.

Массивы постоянного известного размера

Если expression в деклараторе массива является целочисленным константным выражением со значением больше нуля и тип элемента является типом с известным постоянным размером (то есть элементы не являются VLA) (начиная с C99) , тогда декларатор объявляет массив постоянного известного размера:

int n[10]; // целочисленные константы являются константными выражениями
char o[sizeof(double)]; // sizeof является константным выражением
enum { MAX_SZ=100 };
int n[MAX_SZ]; // константы перечисления являются константными выражениями

Массивы постоянного известного размера могут использовать инициализаторы массивов для предоставления их начальных значений:

int a[5] = {1,2,3}; // объявляет int[5] инициализированный значениями 1,2,3,0,0
char str[] = "abc"; // объявляет char[4] инициализированный значениями 'a','b','c','\0'

В списках параметров функций допускаются дополнительные синтаксические элементы внутри деклараторов массивов: ключевое слово static и квалификаторы , которые могут появляться в любом порядке перед выражением размера (они также могут присутствовать даже когда выражение размера опущено).

В каждом вызове функции для функции, где параметр массива использует ключевое слово static между [ и ] , значение фактического параметра должно быть корректным указателем на первый элемент массива, содержащего как минимум столько элементов, сколько указано в expression :

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];
    }
}
// вызов fadd может выполнять проверку границ во время компиляции
// а также позволяет оптимизации, такие как предварительная выборка 10 чисел double
int main(void)
{
    double a[10] = {0}, b[20] = {0};
    fadd(a, b); // OK
    double x[5] = {0};
    fadd(x, b); // неопределенное поведение: аргумент-массив слишком мал
}

Если qualifiers присутствуют, они квалифицируют тип указателя, в который преобразуется тип параметра массива:

int f(const int a[20])
{
    // в этой функции a имеет тип const int* (указатель на const int)
}
int g(const int a[const 20])
{
    // в этой функции a имеет тип const int* const (константный указатель на const int)
}

Это обычно используется с квалификатором типа restrict :

void fadd(double a[static restrict 10],
          const double b[static restrict 10])
{
    for (int i = 0; i < 10; i++) // цикл может быть развернут и переупорядочен
    {
        if (a[i] < 0.0)
            break;
        a[i] += b[i];
    }
}

Массивы переменной длины

Если expression не является целочисленным константным выражением , декларатор объявляет массив переменного размера.

Каждый раз, когда поток управления проходит через объявление, expression вычисляется (и он всегда должен давать значение больше нуля), и массив выделяется (соответственно, lifetime VLA заканчивается, когда объявление выходит из области видимости). Размер каждого экземпляра VLA не меняется в течение его времени жизни, но при следующем проходе по тому же коду он может быть выделен с другим размером.

#include <stdio.h>
int main(void)
{
   int n = 1;
label:;
   int a[n]; // re-allocated 10 times, each with a different size
   printf("The array has %zu elements\n", sizeof a / sizeof *a);
   if (n++ < 10)
       goto label; // leaving the scope of a VLA ends its lifetime
}

Если размер равен * , объявление предназначено для VLA неопределенного размера. Такое объявление может появляться только в области прототипа функции и объявляет массив полного типа. Фактически, все деклараторы VLA в области прототипа функции трактуются так, как если бы expression было заменено на * .

void foo(size_t x, int a[*]);
void foo(size_t x, int a[x])
{
    printf("%zu\n", sizeof a); // то же самое, что sizeof(int*)
}

Массивы переменной длины и типы, производные от них (указатели на них и т.д.), обычно известны как "вариативно-модифицированные типы" (VM). Объекты любого вариативно-модифицированного типа могут быть объявлены только в области видимости блока или в области прототипа функции.

extern int n;
int A[n];            // Ошибка: VLA в области видимости файла
extern int (*p2)[n]; // Ошибка: VM в области видимости файла
int B[100];          // OK: массив в области видимости файла с известным постоянным размером
void fvla(int m, int C[m][m]); // OK: VLA в области видимости прототипа

VLA должен иметь автоматическую или выделенную продолжительность хранения. Указатели на VLA, но не сами VLA, также могут иметь статическую продолжительность хранения. Ни один VM-тип не может иметь линковки.

void fvla(int m, int C[m][m]) // OK: указатель с областью видимости блока/автоматической длительностью на VLA
{
    typedef int VLA[m][m]; // OK: VLA с областью видимости блока
    int D[m];              // OK: VLA с областью видимости блока/автоматической длительностью
//  static int E[m]; // Ошибка: VLA со статической длительностью
//  extern int F[m]; // Ошибка: VLA с линковкой
    int (*s)[m];     // OK: VM с областью видимости блока/автоматической длительностью
    s = malloc(m * sizeof(int)); // OK: s указывает на VLA в выделенной памяти
//  extern int (*r)[m]; // Ошибка: VM с линковкой
    static int (*q)[m] = &B; // OK: VM с областью видимости блока/статической длительностью
}

Типы с переменной модификацией не могут быть членами структур или объединений.

struct tag
{
    int z[n]; // Ошибка: член структуры VLA
    int (*y)[n]; // Ошибка: член структуры VM
};
(начиная с C99)

Если компилятор определяет макроконстанту __STDC_NO_VLA__ как целочисленную константу 1 , тогда VLA и VM типы не поддерживаются.

(since C11)
(until C23)

Если компилятор определяет макроконстанту __STDC_NO_VLA__ как целочисленную константу 1 , то VLA-объекты с автоматической длительностью хранения не поддерживаются.

Поддержка VM-типов и VLA с выделенной длительностью хранения является обязательной.

(since C23)

Массивы неизвестного размера

Если выражение в деклараторе массива опущено, он объявляет массив неизвестного размера. За исключением списков параметров функций (где такие массивы преобразуются в указатели) и случаев, когда доступен инициализатор , такой тип является неполным типом (обратите внимание, что VLA неопределенного размера, объявленный с * в качестве размера, является полным типом) (начиная с C99) :

extern int x[]; // тип x - "массив int неизвестного размера"
int a[] = {1,2,3}; // тип a - "массив из 3 int"

В определении структуры массив неизвестного размера может появляться в качестве последнего члена (при условии, что есть хотя бы один другой именованный член), в этом случае это особый случай, известный как гибкий массив-член . Подробности смотрите в разделе 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]


(начиная с C99)

Квалификаторы

Если тип массива объявлен с квалификатором const , volatile , или restrict (since C99) (что возможно через использование typedef ), то тип массива не квалифицирован, но квалифицирован его тип элементов:

(until C23)

Тип массива и его тип элементов всегда считаются идентично квалифицированными, за исключением того, что тип массива никогда не считается _Atomic -квалифицированным.

(since C23)
typedef int A[2][3];
const A a = {{4, 5, 6}, {7, 8, 9}}; // массив массивов константных int
int* pi = a[0]; // Ошибка: a[0] имеет тип const int*
void* unqual_ptr = a; // OK до C23; ошибка начиная с C23
// Примечание: clang применяет правило из C++/C23 даже в режимах C89-C17

_Atomic не разрешается применять к типу массива, хотя массив атомарного типа допустим.

typedef int A[2];
// _Atomic A a0 = {0};    // Ошибка
// _Atomic(A) a1 = {0};   // Ошибка
_Atomic int a2[2] = {0};  // OK
_Atomic(int) a3[2] = {0}; // OK
(начиная с C11)

Присваивание

Объекты типа массива не являются изменяемыми lvalue , и хотя их адрес может быть получен, они не могут находиться в левой части оператора присваивания. Однако структуры с элементами-массивами являются изменяемыми lvalue и могут быть присвоены:

int a[3] = {1,2,3}, b[3] = {4,5,6};
int (*p)[3] = &a; // корректно, можно взять адрес a
// a = b;            // ошибка, a является массивом
struct { int c[3]; } s1, s2 = {3,4,5};
s1 = s2; // корректно: можно присваивать структуры, содержащие члены-массивы

Преобразование массива в указатель

Любое lvalue-выражение массива, используемое в любом контексте, кроме

(since C11)

подвергается неявному преобразованию в указатель на свой первый элемент. Результат не является lvalue.

Если массив был объявлен register , поведение программы, которая пытается выполнить такое преобразование, не определено.

int a[3] = {1,2,3};
int* p = a;
printf("%zu\n", sizeof a); // выводит размер массива
printf("%zu\n", sizeof p); // выводит размер указателя

Когда тип массива используется в списке параметров функции, он преобразуется в соответствующий тип указателя: int f ( int a [ 2 ] ) и int f ( int * a ) объявляют одну и ту же функцию. Поскольку фактический тип параметра функции является типом указателя, вызов функции с аргументом массива выполняет преобразование массива в указатель; размер массива-аргумента недоступен вызываемой функции и должен быть передан явно:

#include <stdio.h>
void f(int a[], int sz) // фактически объявляет void f(int* a, int sz)
{
    for (int i = 0; i < sz; ++i)
        printf("%d\n", a[i]);
}
void g(int (*a)[10]) // параметр указателя на массив не преобразуется
{
    for (int i = 0; i < 10; ++i)
        printf("%d\n", (*a)[i]);
}
int main(void)
{
    int a[10] = {0};
    f(a, 10); // преобразует a в int*, передает указатель
    g(&a);    // передает указатель на массив (не нужно передавать размер)
}

Многомерные массивы

Когда тип элемента массива сам является массивом, говорят, что массив многомерный:

// массив из 2 массивов по 3 int каждый
int a[2][3] = {{1,2,3},  // может рассматриваться как матрица 2x3
               {4,5,6}}; // с построчным расположением элементов

Обратите внимание, что при применении преобразования массива в указатель многомерный массив преобразуется в указатель на свой первый элемент, например, указатель на первую строку:

int a[2][3]; // матрица 2x3
int (*p1)[3] = a; // указатель на первую строку из 3 элементов
int b[3][3][3]; // куб 3x3x3
int (*p2)[3][3] = b; // указатель на первую плоскость 3x3

Многомерные массивы могут быть модифицируемыми по всем измерениям если поддерживаются VLA (начиная с C11) :

int n = 10;
int a[n][2*n];
(начиная с C99)

Примечания

Объявления массивов нулевой длины не разрешены, даже несмотря на то, что некоторые компиляторы предоставляют их в качестве расширений (обычно в качестве реализации гибких элементов массива до стандарта C99).

Если выражение размера expression VLA имеет побочные эффекты, они гарантированно выполняются, за исключением случаев, когда оно является частью выражения sizeof, результат которого от него не зависит:

int n = 5, m = 5;
size_t sz = sizeof(int (*[n++])[m++]); // n инкрементируется, m может быть инкрементировано или нет

Ссылки

  • Стандарт C23 (ISO/IEC 9899:2024):
  • 6.7.6.2 Объявители массивов (стр.: TBD)
  • Стандарт C17 (ISO/IEC 9899:2018):
  • 6.7.6.2 Объявители массивов (стр: 94-96)
  • Стандарт C11 (ISO/IEC 9899:2011):
  • 6.7.6.2 Объявители массивов (стр. 130-132)
  • Стандарт C99 (ISO/IEC 9899:1999):
  • 6.7.5.2 Деклараторы массивов (стр: 116-118)
  • Стандарт C89/C90 (ISO/IEC 9899:1990):
  • 3.5.4.2 Деклараторы массивов

Смотрите также

Документация C++ для Объявление массива