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