Объявление массива
Массив — это тип, состоящий из непустой непрерывно размещённой последовательности объектов с определённым типом элементов. Количество этих объектов (размер массива) не меняется в течение времени существования массива.
Синтаксис
В грамматике объявлений объявления массива последовательность спецификаторов типа задаёт тип элементов (который должен быть полным типом объекта), а декларатор имеет следующий вид:
[ static(необязательно) квалификаторы (необязательно) выражение (необязательно) ] последовательность спецификаторов атрибутов (необязательно) | (1) | |
[ квалификаторы (необязательно) static(необязательно) выражение (необязательно) ] последовательность спецификаторов атрибутов (необязательно) | (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
Пояснение
Существует несколько разновидностей типов массивов: массивы известного постоянного размера, массивы переменной длины и массивы неизвестного размера.
Массивы известного постоянного размера
Если выражение в деклараторе массива является целочисленным константным выражением со значением больше нуля и тип элементов — это тип с известным постоянным размером (то есть элементы не являются 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'
| В списках параметров функций в деклараторы массивов разрешено включать дополнительные синтаксические элементы: ключевое слово При каждом вызове функции, в котором параметр-массив функции использует ключевое слово 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)
}
Часто это используется вместе с квалификатором типа 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
}
Если размер равен 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) |
| Если компилятор определяет макроконстанту |
(начиная с C11) (до C23) |
| Если компилятор определяет макроконстанту Поддержка типов 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) |
Квалификаторы
| Если тип массива объявлен с квалификатором | (до C23) |
| Тип массива и тип его элементов всегда считаются имеющими одинаковые квалификаторы, за исключением того, что тип массива никогда не считается квалифицированным | (начиная с 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
| Квалификатор 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) - строкового литерала, используемого для инициализации массива
| (начиная с 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