Spec-Zone.ru › C

Объявление массива

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

Синтаксис

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

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

Пояснение

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

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

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

int n[10]; // integer constants are constant expressions
char o[sizeof(double)]; // sizeof is a constant expression
enum { MAX_SZ=100 };
int n[MAX_SZ]; // enum constants are constant expressions

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

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

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

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

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];
    }
}
// a call to fadd may perform compile-time bounds checking
// and also permits optimizations such as prefetching 10 doubles
int main(void)
{
    double a[10] = {0}, b[20] = {0};
    fadd(a, b); // OK
    double x[5] = {0};
    fadd(x, b); // undefined behavior: array argument is too small
}

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

int f(const int a[20])
{
    // in this function, a has type const int* (pointer to const int)
}
int g(const int a[const 20])
{
    // in this function, a has type const int* const (const pointer to const int)
}

Часто это используется вместе с квалификатором типа restrict:

void fadd(double a[static restrict 10],
          const double b[static restrict 10])
{
    for (int i = 0; i < 10; i++) // loop can be unrolled and reordered
    {
        if (a[i] < 0.0)
            break;
        a[i] += b[i];
    }
}

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

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

Каждый раз при прохождении потока управления через объявление вычисляется выражение (результат всегда должен быть больше нуля) и выделяется память для массива (соответственно, время существования 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 в области видимости прототипа функции обрабатываются так, как если бы выражение было заменено на *.

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

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

extern int n;
int A[n];            // Error: file scope VLA
extern int (*p2)[n]; // Error: file scope VM
int B[100];          // OK: file-scope array of constant known size
void fvla(int m, int C[m][m]); // OK: prototype-scope VLA

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

void fvla(int m, int C[m][m]) // OK: block scope/auto duration pointer to VLA
{
    typedef int VLA[m][m]; // OK: block scope VLA
    int D[m];              // OK: block scope/auto duration VLA
//  static int E[m]; // Error: static duration VLA
//  extern int F[m]; // Error: VLA with linkage
    int (*s)[m];     // OK: block scope/auto duration VM
    s = malloc(m * sizeof(int)); // OK: s points to VLA in allocated storage
//  extern int (*r)[m]; // Error: VM with linkage
    static int (*q)[m] = &B; // OK: block scope/static duration VM}
}

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

struct tag
{
    int z[n]; // Error: VLA struct member
    int (*y)[n]; // Error: VM struct member
};
(начиная с C99)

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

(начиная с C11)
(до C23)

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

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

(начиная с C23)

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

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

extern int x[]; // the type of x is "array of unknown bound of int"
int a[] = {1,2,3}; // the type of a is "array of 3 int"

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

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(начиная с C99) (что возможно при использовании typedef), тип массива не считается квалифицированным, но его тип элементов считается:

(до C23)

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

(начиная с C23)
typedef int A[2][3];
const A a = {{4, 5, 6}, {7, 8, 9}}; // array of array of const int
int* pi = a[0]; // Error: a[0] has type const int*
void* unqual_ptr = a; // OK until C23; error since C23
// Notes: clang applies the rule in C++/C23 even in C89-C17 modes

Квалификатор _Atomic нельзя применять к типу массива, но массив атомарного типа разрешён.

typedef int A[2];
// _Atomic A a0 = {0};    // Error
// _Atomic(A) a1 = {0};   // Error
_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; // okay, address of a can be taken
// a = b;            // error, a is an array
struct { int c[3]; } s1, s2 = {3,4,5};
s1 = s2; // okay: can assign structs holding array members

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

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

  • операнда оператора взятия адреса
  • операнда sizeof
  • операнда typeof и typeof_unqual (начиная с C23)
  • строкового литерала, используемого для инициализации массива
  • операнда _Alignof(начиная с C11)(до C23)alignof(начиная с C23)
(начиная с C11)

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

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

int a[3] = {1,2,3};
int* p = a;
printf("%zu\n", sizeof a); // prints size of array
printf("%zu\n", sizeof p); // prints size of a pointer

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

#include <stdio.h>

void f(int a[], int sz) // actually declares void f(int* a, int sz)
{
    for (int i = 0; i < sz; ++i)
        printf("%d\n", a[i]);
}

void g(int (*a)[10]) // pointer to array parameter is not transformed
{
    for (int i = 0; i < 10; ++i)
        printf("%d\n", (*a)[i]);
}

int main(void)
{
    int a[10] = {0};
    f(a, 10); // converts a to int*, passes the pointer
    g(&a);    // passes a pointer to the array (no need to pass the size)
}

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

Если тип элементов массива — другой массив, такой массив называется многомерным:

// array of 2 arrays of 3 ints each
int a[2][3] = {{1,2,3},  // can be viewed as a 2x3 matrix
               {4,5,6}}; // with row-major layout

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

int a[2][3]; // 2x3 matrix
int (*p1)[3] = a; // pointer to the first 3-element row
int b[3][3][3]; // 3x3x3 cube
int (*p2)[3][3] = b; // pointer to the first 3x3 plane

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

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

Примечания

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

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

int n = 5, m = 5;
size_t sz = sizeof(int (*[n++])[m++]); // n is incremented, m may or may not be incremented

Ссылки

  • Стандарт 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++ для Объявление массива

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/c/language/array

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API