Что такое распадающийся массив? - PullRequest
337 голосов
/ 22 сентября 2009

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

Ответы [ 8 ]

244 голосов
/ 22 сентября 2009

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

88 голосов
/ 22 сентября 2009

Массивы в основном такие же, как указатели в C / C ++, но не совсем. После преобразования массива:

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

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

const int* p = a;

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

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

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

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

43 голосов
/ 22 сентября 2009

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

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

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

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

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

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

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

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

25 голосов
/ 23 сентября 2009

«Распад» относится к неявному преобразованию выражения из типа массива в тип указателя. В большинстве случаев, когда компилятор видит выражение массива, он преобразует тип выражения из «массива 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 - простой указатель на символ.

12 голосов
/ 22 сентября 2009

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

Везде, где ожидается значение объекта, но объект является массивом, вместо него используется адрес его первого элемента с типом 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'), или как строковый литерал, используемый для инициализации массива символов .

6 голосов
/ 22 сентября 2009

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

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

2 голосов
/ 22 сентября 2009

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

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

0 голосов
/ 13 марта 2018

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

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

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

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

  • Вы можете передавать массивы фиксированной длины в функции в struct.
  • sizeof() дает размер, занятый массивом, а не размер указателя.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...