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

295

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

  • 62
    малоизвестно: унарный оператор плюс может использоваться как «оператор распада»: задано int a[10]; int b(void); , тогда +a - это указатель на int, а +b - это указатель на функцию. Полезно, если вы хотите передать его шаблону, принимающему ссылку.
  • 2
    @litb - parens будет делать то же самое (например, (a) должно быть выражением, которое оценивает указатель), верно?
Показать ещё 7 комментариев
Теги:
arrays
pointers

8 ответов

216
Лучший ответ

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

  • 6
    Как проходит первая передача по значению?
  • 7
    by_value передает указатель на первый элемент массива; в контексте параметров функции T a[] идентично T *a . by_pointer передает то же самое, за исключением того, что значение указателя теперь квалифицируется как const . Если вы хотите передать указатель на массив (в отличие от указателя на первый элемент массива), синтаксис T (*array)[U] .
Показать ещё 10 комментариев
82

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

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

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

const int* p = a;

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

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

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

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

  • 44
    Массивы в основном не совпадают с указателями; они совершенно разные животные. В большинстве случаев массив можно рассматривать как указатель, а указатель можно рассматривать как массив, но это настолько близко, насколько они получают.
  • 15
    @ Джон, прошу прощения за мой неточный язык. Я пытался найти ответ, не увязнув в длинных предысториях, и «в основном ... но не совсем» - такое же хорошее объяснение, как и в колледже. Я уверен, что любой заинтересованный может получить более точную картину из вашего комментария.
Показать ещё 1 комментарий
43

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

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

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

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

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

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

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

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

  • 1
    Что такое пример строки кода, где «выражение с типом« массив типа »» является «строковым литералом, используемым для инициализации массива»?
  • 3
    @Garrett char x[] = "Hello"; , Массив из 6 элементов "Hello" не гниет; вместо этого x получает размер 6 а его элементы инициализируются из элементов "Hello" .
23

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

  • 0
    Разве "This is a test" is of type "16-element array of char" как "15-element array of char" ? (длина 14 + 1 для \ 0)
12

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

  • 5
    «Массивы не имеют значения» - что это должно означать? Конечно, массивы имеют значение ... это объекты, у вас могут быть указатели, а в C ++ ссылки на них и т. Д.
  • 2
    Я считаю, что строгое «Значение» определяется в С как интерпретация битов объекта в соответствии с типом. Мне трудно понять полезный смысл этого с типом массива. Вместо этого вы можете сказать, что вы конвертируете в указатель, но он не интерпретирует содержимое массива, он просто получает его местоположение. То, что вы получите, это значение указателя (и это адрес), а не значение массива (это будет «последовательность значений содержащихся элементов», как используется в определении «строка»). Тем не менее, я думаю, что будет справедливо сказать «значение массива», когда один означает, что указатель получен.
Показать ещё 3 комментария
6

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

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

  • 0
    Красиво сказано. Что было бы хорошим массивом, который не распадается на указатель или тот, который предотвращен от распада? Можете ли вы привести пример в C? Благодарю.
  • 0
    @Unheilig, конечно, можно упаковать массив в struct и передать структуру.
Показать ещё 2 комментария
2

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

  • 1
    Распад происходит во многих других ситуациях, а не просто при передаче массива в функцию.
0

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

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

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

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню