Array declaration
Массив — это тип, состоящий из непрерывно выделенной непустой последовательности объектов с определённым типом элементов . Количество этих объектов (размер массива) никогда не меняется в течение времени жизни массива.
Содержание |
Синтаксис
В грамматике объявлений объявления массива, type-specifier последовательность обозначает element type (который должен быть полным типом объекта), а declarator имеет форму:
[
static
(необязательно)
квалификаторы
(необязательно)
выражение
(необязательно)
]
attr-spec-seq
(необязательно)
|
(1) | ||||||||
[
квалификаторы
(необязательно)
static
(необязательно)
выражение
(необязательно)
]
attr-spec-seq
(необязательно)
|
(2) | ||||||||
[
квалификаторы
(необязательно)
*
]
attr-spec-seq
(необязательно)
|
(3) | ||||||||
| 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'
|
В списках параметров функций допускаются дополнительные синтаксические элементы внутри деклараторов массивов: ключевое слово
В каждом
вызове функции
для функции, где параметр массива использует ключевое слово
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) }
Это обычно используется с квалификатором типа
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 }
Если размер равен
Массивы переменной длины и типы, производные от них (указатели на них и т.д.), обычно известны как "вариативно-модифицированные типы" (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) |
Квалификаторы
|
Если тип массива объявлен с квалификатором
|
(until C23) |
|
Тип массива и его тип элементов всегда считаются идентично квалифицированными, за исключением того, что тип массива никогда не считается
|
(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
|
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-выражение массива, используемое в любом контексте, кроме
- в качестве операнда оператора взятия адреса
-
в качестве операнда
sizeof -
в качестве операнда
typeofиtypeof_unqual(начиная с C23) - в качестве строкового литерала для инициализации массива
| (since C11) |
подвергается неявному преобразованию в указатель на свой первый элемент. Результат не является lvalue.
Если массив был объявлен
register
, поведение программы, которая пытается выполнить такое преобразование, не определено.
Когда тип массива используется в списке параметров функции, он преобразуется в соответствующий тип указателя: 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++
для
Объявление массива
|