Что такое разложение массива?
Что такое распад массива? Есть ли какое-либо отношение к указателям массива?
8 ответов
говорят, что массивы "распадаются" на указатели. Массив C++, объявленный как int numbers [5]
не может быть повторно указал, т. е. вы не можете сказать numbers = 0x5a5aff23
. Что еще более важно, термин "распад" означает потерю типа и размера;numbers
распад int*
потеряв информацию об измерении (количество 5), а тип не int [5]
больше. Посмотрите здесь случаи, когда распад не происходит.
если вы передаете массив по значению, то на самом деле вы копируете указатель - a указатель на первый элемент массива копируется в параметр (тип которого также должен быть указателем типа элемента массива). Это работает из-за разлагающейся природы массива; однажды разлагаясь,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
info, В то время как первый не будет, так как аргумент массива распался, чтобы быть назначенным параметру.
1 константа U должна быть известна во время компиляции.
массивы в основном такие же, как указатели в C/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 или унарного оператора&, или строковый литерал, используемый для инициализации массива, выражение, имеющее тип " массив типа преобразуется в выражение с типом " указатель на тип’, которое указывает на исходный элемент объект array и не является lvalue.
этот это означает, что практически всегда, когда имя массива используется в выражении, оно автоматически преобразуется в указатель на 1-й элемент массива.
обратите внимание, что имена функций действуют аналогичным образом, но указатели функций используются гораздо реже и гораздо более специализированным образом, что это не вызывает почти такой же путаницы, как автоматическое преобразование имен массивов в указатели.
стандарт C++ (преобразование массива в указатель 4.2) ослабляет требование преобразования к (подчеркнуто мною):
lvalue или rvalue типа " массив N T "или" массив неизвестной границы T"можете преобразуется в значение rvalue типа "указатель на Т".
таким образом, преобразование не есть произойти так, как это в значительной степени всегда происходит в C (это позволяет перегрузить функции или шаблоны совпадают по типу массива).
Это также, почему в C вы должны избегать использования параметров массива в функцию прототипы / определения (на мой взгляд - я не уверен, есть ли какое-либо общее согласие). Они вызывают путаницу и в любом случае являются фикцией - используйте Параметры указателя, и путаница может не исчезнуть полностью, но, по крайней мере, объявление параметра не лжет.
"распад" означает неявное преобразование выражения из типа массива в тип указателя. В большинстве контекстов, когда компилятор видит выражение массива, он преобразует тип выражения из "N-элементного массива T" в "указатель на T" и устанавливает значение выражения в адрес первого элемента массива. Исключения из этого правила, когда массив является операндом либо sizeof
или &
операторы, или массив является строковым литералом, используемым в качестве инициализатора в объявлении.
предположим следующий код:
char a[80];
strcpy(a, "This is a test");
выражение a
имеет тип "80-элементный массив char", а выражение" Это тест "имеет тип" 16-элементный массив char " (в C; в C++ строковые литералы являются массивами const char). Однако в призыве к strcpy()
, ни одно выражение не является операндом sizeof
или &
, поэтому их типы неявно преобразуются в "указатель на char" , а их значения устанавливаются в адрес первый элемент в каждом. Что?!--9--> получает не массивы, а указатели, как видно в его прототипе:
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. Однако они являются разными типами и обрабатываются по-разному, как показано ниже:
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.
массивы в 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 или оператора & (оператор "адрес"), или в качестве строкового литерала, используемого для инициализации массива символов.
Это когда массив гниет и указал ;-)
на самом деле, это просто, если вы хотите передать массив где-то, но указатель передается вместо этого (потому что кто, черт возьми, передал бы весь массив для вас), люди говорят, что бедный массив распался на указатель.
распад массива означает, что, когда массив передается в качестве параметра функции, он обрабатывается идентично ("распадается на") указателю.
void do_something(int *array) {
// We don't know how big array is here, because it's 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++ безопасных функций CRT, таких как предоставляя strcpy_s, и вы можете использовать подобный трюк надежно получить количество элементов в массиве.
tl; dr: когда вы используете массив, который вы определили, вы фактически будете использовать указатель на его первый элемент.
таким образом:
- когда вы пишите
arr[idx]
ты действительно просто говоришь*(arr + idx)
. - функции никогда не принимают массивы в качестве параметров, только указатели, даже если вы указываете параметр массива.
исключения из этого правила:
- вы можете передавать массивы фиксированной длины функциям в пределах
struct
. -
sizeof()
дает размер, занимаемый массивом, а не размер указателя.