Spec-Zone.ru › C

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

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

Синтаксис

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

[ static(необязательно) модификаторы (необязательно) выражение (необязательно) ] последовательность_атрибутов (необязательно) (1)
[ модификаторы (необязательно) static(необязательно) выражение (необязательно) ] последовательность_атрибутов (необязательно) (2)
[ модификаторы (необязательно) * ] последовательность_атрибутов (необязательно) (3)
1,2) Общий синтаксис объявления массива
3) Декларатор для массива переменной длины неизвестного размера (может появляться только в области прототипа функции), где
выражение - любое выражение, кроме оператора запятой, обозначает количество элементов в массиве
модификаторы - любая комбинация модификаторов 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

Объяснение

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

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

Если выражение в деклараторе массива является целым константным выражением со значением, большим нуля и тип элемента — тип с известным постоянным размером (то есть элементы не являются массивами переменной длины)(с 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];
    }
}

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

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

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

#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
}

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

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

Массив переменной длины должен иметь автоматическое или выделенное время хранения. Указатели на массив переменной длины, но не сами массивы переменной длины, также могут иметь статическое время хранения. Ни один тип 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, то типы VM и массивы переменной длины не поддерживаются.

(с C11)
(до C23)

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

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

(с C23)

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

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

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

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

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

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

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

  • в качестве операнда оператора адресов
  • в качестве операнда sizeof
  • в качестве операнда typeof и typeof_unqual (с C23)
  • в качестве строковой константы, используемой для инициализации массивов
  • в качестве операнда _Alignof
(с 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

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

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

Примечания

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

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

int n = 5;
size_t sz = sizeof(int (*)[n++]); // may or may not increment n

Ссылки

  • Стандарт C23 (ISO/IEC 9899:2023):
    • 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/w/c/language/array

Spec-Zone.ru

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