Что такое разложение массива? Есть ли какое-либо отношение к указателям на массив?
Он сказал, что массивы "распадаются" на указатели. Массив С++, объявленный как int numbers [5]
, не может быть перенаправлен, т.е. Вы не можете сказать numbers = 0x5a5aff23
. Что еще более важно, термин "распад" означает потерю типа и размерности; numbers
распадайтесь на int*
, потеряв информацию о размере (число 5), и тип уже не int [5]
. Посмотрите здесь случаи, когда распад не происходит.
Если вы передаете массив по значению, то, что вы действительно делаете, это копирование указателя - указатель на первый элемент массива копируется в параметр (тип которого также должен быть указателем типа элемента массива). Это работает из-за разрушения массива; раз затухает, sizeof
больше не дает полный размер массива, поскольку он по существу становится указателем. Именно поэтому он предпочел (среди прочего) пройти по ссылке или указателю.
Три способа передачи в массив 1:
void by_value(const T* array) // const T array[] means the same
void by_pointer(const T (*array)[U])
void by_reference(const T (&array)[U])
Последние два будут содержать соответствующую информацию sizeof
, а первая не будет, так как аргумент массива распался для назначения этому параметру.
1 Константа U должна быть известна во время компиляции.
T a[]
идентично T *a
. by_pointer передает то же самое, за исключением того, что значение указателя теперь квалифицируется как const
. Если вы хотите передать указатель на массив (в отличие от указателя на первый элемент массива), синтаксис T (*array)[U]
.
Массивы в основном такие же, как указатели на C/С++, но не совсем. После преобразования массива:
const int a[] = { 2, 3, 5, 7, 11 };
в указатель (который работает без кастинга и, следовательно, может случиться неожиданно в некоторых случаях):
const int* p = a;
вы теряете способность оператора sizeof
подсчитывать элементы в массиве:
assert( sizeof(p) != sizeof(a) ); // sizes are not equal
Эта потерянная способность называется "распад".
Для получения дополнительной информации ознакомьтесь с этой статьей о распаде массива.
Вот что говорит стандарт (C99 6.3.2.1/3 - Другие операнды - Lvalues, массивы и обозначения функций):
За исключением случаев, когда это операнд оператора sizeof или унарный и оператор, или строковый литерал, используемый для инициализации массива, выражение, которое имеет тип '' массив типа, является преобразуется в выражение с типом '' указателем на тип, указывающим на начальный элемент объект массива и не является lvalue.
Это означает, что в любом случае имя массива используется в выражении, оно автоматически преобразуется в указатель на 1-й элемент массива.
Обратите внимание, что имена функций действуют аналогичным образом, но указатели на функции используются гораздо меньше и гораздо более специализированным образом, что это не вызывает почти столько же путаницы, как автоматическое преобразование имен массивов в указатели.
Стандарт С++ (4.2 Преобразование массива в указатель) ослабляет требование преобразования к (выделение мое):
Значение lvalue или rvalue типа "массив из N T" или "массив неизвестной границы T" может быть преобразован в rvalue типа "указатель на T".
Таким образом, преобразование не должно происходить, как это почти всегда происходит в C (это позволяет перегрузкам функций или шаблонам соответствовать типу массива).
Вот почему в C вам следует избегать использования параметров массива в прототипах/определениях функций (на мой взгляд - я не уверен, есть ли какое-либо общее соглашение). Они вызывают путаницу и в любом случае являются фикцией - используют параметры указателя, и путаница может не исчезнуть целиком, но, по крайней мере, объявление параметра не лежит.
char x[] = "Hello";
, Массив из 6 элементов "Hello"
не гниет; вместо этого x
получает размер 6
а его элементы инициализируются из элементов "Hello"
.
"Распад" означает неявное преобразование выражения из типа массива в тип указателя. В большинстве случаев, когда компилятор видит выражение массива, он преобразует тип выражения из "N-element array of T" в "указатель на T" и устанавливает значение выражения в адрес первого элемента массива, Исключения из этого правила заключаются в том, что массив является операндом операторов sizeof
или &
, или массив является строковым литералом, который используется в качестве инициализатора в объявлении.
Предположим, что следующий код:
char a[80];
strcpy(a, "This is a test");
Выражение a
имеет тип "80-элементный массив из char", а выражение "This is the test" имеет тип "16-элементный массив char" (в C; в строке С++ литералы являются массивами const char). Однако в вызове strcpy()
ни одно выражение не является операндом sizeof
или &
, поэтому их типы неявно преобразуются в "указатель на char", а их значения устанавливаются на адрес первого элемент в каждом. Что strcpy()
получает не массивы, а указатели, как показано в его прототипе:
char *strcpy(char *dest, const char *src);
Это не то же самое, что указатель на массив. Например:
char a[80];
char *ptr_to_first_element = a;
char (*ptr_to_array)[80] = &a;
Оба ptr_to_first_element
и ptr_to_array
имеют одинаковое значение; базовый адрес. Тем не менее, они являются разными типами и обрабатываются по-разному, как показано ниже:
a[i] == ptr_to_first_element[i] == (*ptr_to_array)[i] != *ptr_to_array[i] != ptr_to_array[i]
Помните, что выражение a[i]
интерпретируется как *(a+i)
(оно работает только в том случае, если тип массива преобразуется в тип указателя), поэтому оба a[i]
и ptr_to_first_element[i]
работают одинаково. Выражение (*ptr_to_array)[i]
интерпретируется как *(*a+i)
. Выражения *ptr_to_array[i]
и ptr_to_array[i]
могут приводить к предупреждениям или ошибкам компилятора в зависимости от контекста; они определенно сделают неправильную вещь, если вы ожидаете, что они оценят значение a[i]
.
sizeof a == sizeof *ptr_to_array == 80
Опять же, когда массив является операндом sizeof
, он не преобразуется в тип указателя.
sizeof *ptr_to_first_element == sizeof (char) == 1
sizeof ptr_to_first_element == sizeof (char *) == whatever the pointer size
is on your platform
ptr_to_first_element
- простой указатель на char.
"This is a test" is of type "16-element array of char"
как "15-element array of char"
? (длина 14 + 1 для \ 0)
Массивы в C не имеют значения.
Где бы ни ожидалось значение объекта, но объектом является массив, вместо него используется адрес его первого элемента с типом pointer to (type of array elements)
.
В функции все параметры передаются по значению (массивы не являются исключением). Когда вы передаете массив в функции, он "распадается на указатель" (sic); когда вы сравниваете массив с чем-то другим, снова он "распадается на указатель" (sic);...
void foo(int arr[]);
Функция foo ожидает значение массива. Но в C массивы не имеют ценности! Таким образом, foo
получает вместо этого адрес первого элемента массива.
int arr[5];
int *ip = &(arr[1]);
if (arr == ip) { /* something; */ }
В приведенном выше сравнении arr
не имеет значения, поэтому он становится указателем. Он становится указателем на int. Этот указатель можно сравнить с переменной ip
.
В синтаксисе индексирования массива, который вы используете для просмотра, опять же arr "разлагается на указатель"
arr[42];
/* same as *(arr + 42); */
/* same as *(&(arr[0]) + 42); */
Единственный раз, когда массив не распадается на указатель, это когда он является операндом оператора sizeof или оператором and (оператор "operator" ) или как строковый литерал, используемый для инициализации массива символов.
Это когда массив гниет и указывает на; -)
На самом деле, это просто, если вы хотите передать массив где-нибудь, но вместо этого указатель передается (потому что кто, черт возьми, передаст вам весь массив), люди говорят, что плохой массив распался на указатель.
Затухание массива означает, что, когда массив передается как параметр функции, он обрабатывает тождественно ("decays to") указатель.
void do_something(int *array) {
// We don't know how big array is here, because it decayed to a pointer.
printf("%i\n", sizeof(array)); // always prints 4 on a 32-bit machine
}
int main (int argc, char **argv) {
int a[10];
int b[20];
int *c;
printf("%zu\n", sizeof(a)); //prints 40 on a 32-bit machine
printf("%zu\n", sizeof(b)); //prints 80 on a 32-bit machine
printf("%zu\n", sizeof(c)); //prints 4 on a 32-bit machine
do_something(a);
do_something(b);
do_something(c);
}
Существует два осложнения или исключения из вышеизложенного.
Во-первых, при работе с многомерными массивами в C и C++ теряется только первое измерение. Это связано с тем, что массивы сложены в памяти, поэтому компилятор должен знать все, кроме первого измерения, чтобы вычислить смещения в этом блоке памяти.
void do_something(int array[][10])
{
// We don't know how big the first dimension is.
}
int main(int argc, char *argv[]) {
int a[5][10];
int b[20][10];
do_something(a);
do_something(b);
return 0;
}
Во-вторых, в C++ вы можете использовать шаблоны для вывода размера массивов. Microsoft использует это для C++ версий функций Secure CRT, таких как strcpy_s, и вы можете использовать подобный трюк, чтобы надежно получить количество элементов в массиве.
Таким образом:
arr[idx]
вы действительно говорите *(arr + idx)
.Сортировка исключений из этого правила:
struct
.sizeof()
дает размер, занимаемый массивом, а не размер указателя.
int a[10]; int b(void);
, тогда+a
- это указатель на int, а+b
- это указатель на функцию. Полезно, если вы хотите передать его шаблону, принимающему ссылку.