it-roy-ru.com

Что такое распадающийся массив?

Что такое распадающийся массив? Есть ли какое-либо отношение к указателям на массивы?

318
Vamsi

Говорят, что массивы "распадаются" на указатели. Массив C++, объявленный как 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 должна быть известна во время компиляции.

234
phoebus

Массивы в основном такие же, как указатели в C/C++, но не совсем. Как только вы конвертируете массив:

const int a[] = { 2, 3, 5, 7, 11 };

в указатель (который работает без приведения и, следовательно, в некоторых случаях может произойти неожиданно):

const int* p = a;

вы теряете способность оператора sizeof подсчитывать элементы в массиве:

assert( sizeof(p) != sizeof(a) );  // sizes are not equal

Эта потерянная способность называется «распадом».

Для более подробной информации, ознакомьтесь с этой статьей о распаде массива .

85
system PAUSE

Вот что говорит стандарт (C99 6.3.2.1/3 - Другие операнды - L-значения, массивы и обозначения функций):

За исключением случаев, когда это операнд оператора sizeof или унарный оператор &, или строковый литерал, используемый для инициализации массива; выражение, имеющее тип «массив типа», - это преобразуется в выражение с указателем типа ‘‘ указатель на тип ’, которое указывает на начальный элемент объект массива и не является lvalue.

Это означает, что почти всегда, когда имя массива используется в выражении, оно автоматически преобразуется в указатель на первый элемент массива.

Обратите внимание, что имена функций действуют аналогичным образом, но указатели на функции используются гораздо реже и гораздо более специализированным образом, что не вызывает такой большой путаницы, как автоматическое преобразование имен массивов в указатели.

Стандарт C++ (4.2 преобразование массива в указатель) ослабляет требование преобразования к (выделение мое):

Lvalue или rvalue типа «массив из N T» или «массив неизвестных границ T» можно преобразовать в rvalue типа «указатель на T.»

Таким образом, преобразование не должно иметь, как в большинстве случаев в C (это позволяет перегрузить функции или сопоставить шаблоны по типу массива).

Вот почему в C вы должны избегать использования параметров массива в прототипах/определениях функций (на мой взгляд - я не уверен, есть ли общее согласие). Они вызывают путаницу и в любом случае являются вымыслом - используйте параметры указателя, и путаница может не исчезнуть полностью, но, по крайней мере, объявление параметра не лжёт.

43
Michael Burr

«Распад» относится к неявному преобразованию выражения из типа массива в тип указателя. В большинстве случаев, когда компилятор видит выражение массива, он преобразует тип выражения из «массива N-элемента T» в «указатель на T» и устанавливает значение выражения в адрес первого элемента массива. , Исключением из этого правила является случай, когда массив является операндом операторов sizeof или &, или массив является строковым литералом, используемым в качестве инициализатора в объявлении. 

Предположим, следующий код:

char a[80];
strcpy(a, "This is a test");

Выражение a имеет тип «80-элементный массив символов char», а выражение «Это тест» имеет тип «16-элементный массив символов char» (в C; в C++ строковые литералы являются массивами const char). Однако в вызове strcpy() ни одно из выражений не является операндом sizeof или &, поэтому их типы неявно преобразуются в «указатель на символ», а их значения устанавливаются по адресу первого элемента в каждом. 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 - это простой указатель на символ. 

25
John Bode

Массивы в Си не имеют значения.

Везде, где ожидается значение объекта, но объект является массивом, вместо него используется адрес его первого элемента с типом pointer to (type of array elements).

В функции все параметры передаются по значению (массивы не являются исключением). Когда вы передаете массив в функцию, он «распадается на указатель» (sic); когда вы сравниваете массив с чем-то другим, он снова «превращается в указатель» (sic); ...

void foo(int arr[]);

Функция foo ожидает значение массива. Но в Си массивы не имеют значения! Таким образом, 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 или оператора & (оператор 'address of'), или как строковый литерал, используемый для инициализации массива символов.

12
pmg

Это когда массив гниет и на него указывают ;-)

На самом деле, просто если вы хотите передать массив куда-то, но вместо этого передается указатель (потому что, черт возьми, он пропустит весь массив за вас), люди говорят, что плохой массив распался на указатель.

6
Michael Krelin - hacker

Затухание массива означает, что когда массив передается в качестве параметра функции, он обрабатывается идентично ("распадается на") указателю.

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 использует это для версий Secure CRT на C++, таких как strcpy_s , и вы можете использовать аналогичный прием для надежного получения количества элементов в массиве .

2
Josh Kelley

tl; dr: когда вы используете определенный вами массив, вы фактически будете использовать указатель на его первый элемент.

Таким образом:

  • Когда вы пишете arr[idx], вы на самом деле просто говорите *(arr + idx).
  • функции никогда не принимают массивы в качестве параметров, только указатели, даже когда вы указываете параметр массива.

Сортировка исключений из этого правила:

  • Вы можете передавать массивы фиксированной длины в функции внутри struct.
  • sizeof() дает размер, занятый массивом, а не размер указателя.
0
einpoklum