Что такое разложение массива?

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

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