Подтвердить что ты не робот

Почему int * [] распадается на int **, но не int [] []?

Я пытаюсь понять природу распада типа. Например, все мы знаем, что в определенном контексте распад массивов в указатели. Моя попытка понять, как int[] приравнивается к int*, но как двумерные массивы не соответствуют ожидаемому типу указателя. Вот тестовый пример:

std::is_same<int*, std::decay<int[]>::type>::value; // true

Возвращает true, как ожидалось, но это не так:

std::is_same<int**, std::decay<int[][1]>::type>::value; // false

Почему это не так? Я, наконец, нашел способ заставить его возвращать true, и это было сделано при указании первого измерения указателем:

std::is_same<int**, std::decay<int*[]>::type>::value; // true

И утверждение верно для любого типа с указателями, но с последним, являющимся массивом. Например (int***[] == int****; // true).

Могу ли я объяснить, почему это происходит? Почему типы массивов не соответствуют типам указателей, как и ожидалось?

4b9b3361

Ответ 1

Почему int*[] распадается на int**, но не int[][]?

Потому что с ним было бы невозможно выполнить арифметику указателя.

Например, int p[5][4] означает массив (длина-4 массив int). Нет указателей, это просто смежный блок памяти размером 5*4*sizeof(int). Когда вы запрашиваете конкретный элемент, например. int a = p[i][j], компилятор действительно делает это:

char *tmp = (char *)p           // Work in units of bytes (char)
          + i * sizeof(int[4])  // Offset for outer dimension (int[4] is a type)
          + j * sizeof(int);    // Offset for inner dimension
int a = *(int *)tmp;            // Back to the contained type, and dereference

Очевидно, что это может быть сделано только потому, что он знает размер "внутреннего" измерения (измерений). Передача в int (*)[4] сохраняет эту информацию; это указатель на (длина-4 массив int). Однако int ** не работает; это просто указатель на (указатель на int).

Для получения дополнительной информации см. следующие разделы часто задаваемых вопросов C:

(Все это для C, но это поведение практически не изменяется в С++.)

Ответ 2

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

На современном языке C существует довольно значительная система типов, а переменные имеют четко определенные типы, но вещи не всегда были такими. Объявление char arr[8]; будет выделять 8 байтов в данной области и сделать arr указывать на первую из них. Компилятор не знал бы, что arr представляет массив - он будет представлять указатель char, как и любой другой char*. Из того, что я понимаю, если бы кто-то объявил char arr1[8], arr2[8];, утверждение arr1 = arr2; было бы совершенно законным, будучи несколько эквивалентным концептуально char *st1 = "foo, *st2 = "bar"; st1 = st2;, но почти всегда представляло бы ошибку.

Правило, которое массивы разлагаются на указатели, было связано с тем, что массивы и указатели действительно были одним и тем же. С тех пор массивы стали признаваться как особый тип, но язык должен был оставаться практически совместимым с теми днями, когда они не были. Когда правила формулируются, вопрос о том, как обрабатывать двумерные массивы, не является проблемой, потому что такого не было. Можно было бы сделать что-то вроде char foo[20]; char *bar[4]; int i; for (i=0; i<4; i++) bar[i] = foo + (i*5);, а затем использовать bar[x][y] так же, как теперь использовать двумерный массив, но компилятор не будет рассматривать вещи таким образом - он просто увидел bar как указатель к указателю. Если бы кто-то хотел сделать foo [1] точкой где-то совершенно отличным от foo [2], можно было бы законно сделать это.

Когда два двумерных массива были добавлены в C, нет необходимости поддерживать совместимость с более ранним кодом, который объявлял двумерные массивы, потому что их не было. Хотя было бы возможно указать, что char bar[4][5]; будет генерировать код, эквивалентный тому, что было показано с помощью foo[20], и в этом случае a char[][] можно было бы использовать как char**, считалось, что так же, как присвоение переменные массива были бы ошибкой в ​​99% случаев, поэтому тоже было бы перераспределение строк массива, если бы это было законно. Таким образом, массивы в C распознаются как разные типы, с их собственными правилами, которые немного нечетны, но являются тем, чем они являются.

Ответ 3

Потому что int[M][N] и int** являются несовместимыми типами.

Однако int[M][N] может распасться на int (*)[N]. Итак, следующее:

std::is_same<int(*)[1], std::decay<int[1][1]>::type>::value;

должен предоставить вам true.

Ответ 4

Двумерные массивы не сохраняются как указатель на указатели, а как непрерывный блок памяти.

Объект, объявленный как тип int[y][x], представляет собой блок размера sizeof(int) * x * y, тогда как объект типа int ** является указателем на int*