Страницы

Поиск по вопросам

среда, 5 февраля 2020 г.

С++ Как работает передача/возврат массивов, в чём разница между int[][] и int**?

#cpp #массивы #указатели #преобразование


Так не компилируется

class A {
private: int arr[10][10];
public: int** getArr() {return arr;}
}


Так собирается, но получаем ошибку во время исполнения(код 11 - попытка доступа к
заблокированной памяти) https://ideone.com/Pq9gLn

class A {
private: int arr[10][10];
public: int** getArr() {return (int**)arr;}
}
...
A a;
int** arr = a.getArr();
cout << arr[0][0];


Почему так? Ведь по идее int** и int[][] одно и то же. В чём разница?
    


Ответы

Ответ 1



Массив в выражениях преобразуется к указателю на свой первый элемент. Если у вас есть, например, объявление массива T a[N]; где T это некоторый тип, а N - число элементов в массиве, то использование имени a в выражениях преобразуется к типу T *. Это можно представить как T *tmp = a; Двумерный массив - это массив массивов. То есть если у вас есть массив вида int a[10][10]; то a - это массив из 10 элементов, которые в свою очередь массивы с типом int[10]. Вы можете ввести объявление typedef для этих элементов. Например, typedef int T[10]; И тогда объявление массива будет выглядеть как T a[10]; Как сказано выше, в выражениях массив преобразуется в указатель на свой первый элемент. Следовательно это преобразование можно представить как T *tmp = a; где T - это алиас для типа int[10] Следовательно, если убрать объявление typedef, то вы получите int ( *tmp )[10] = a; Типы int ( * )[10] и int ** - два разных типа. Например, выведите на консоль размер объектов, для которых определены эти указатели и сравните их #include int main() { int **p; int ( *q )[10]; std::cout << sizeof( *p ) << std::endl; std::cout << sizeof( *q ) << std::endl; return 0; } Вывод программы может выглядеть следующим образом 4 40 То есть в первом случае выводится размер скалярного объекта, а во втором случае размер массива. Поэтому правильное определение метода в вашем классе будет выглядеть так class A { private: int arr[10][10]; public: int ( * getArr() )[10] { return arr; } }; или class A { private: int arr[10][10]; public: typedef int ( *T )[10]; T getArr() { return arr; } }; Что касается вашего примера class A { private: int arr[10][10]; public: int** getArr() {return (int**)arr;} }; ... A a; int** arr = a.getArr(); cout << arr[0][0]; то переменная arr получит адрес экстента, занимаемого исходным двумерным массивом. При использовании выражения arr[0] происходит обращение к памяти массива, где хранится его первый элемент. При этом предполагается, что arr[0] , эквивалентное выражению *arr, в свою очередь вернет указатель. Но исходный массив не хранит указатели. Он хранит в общем случае произвольные значения. Поэтому происходит ошибка обращения к памяти. Для наглядности рассмотрите следующий пример. Допустим, что sizeof( int ) и sizeof( int * ) равны между собой. Чтобы у вас работала конструкция arr[0][0], где arr имеет тип int **, исъодный массив должен быть определен, как показано в следующей демонстрационной программе. #include int main() { int a[][2] = { { reinterpret_cast( &a[1][0] ), 20 }, { 30, 40 }, }; int **arr = reinterpret_cast( a ); std::cout << arr[0][0] << std::endl; return 0; } В этом случае arr[0] возвратит указатель на элемент массива a[1][0], то есть &a[1][0] . Применяя к полученному выражению снова оператор индексирования, вы получите целое число 30. Однако если первый элемент массива содержит произвольное целое число, как, например, 10, то arr[0] вернет это значение, которое в выражении arr[0][0] будет интерпретироваться как адрес памяти, и произойдет ошибка обращения к памяти. Таким образом указатель int **arr; интерпретирует массив int a[N][N]; как массив, имеющий тип int * tmp[N]; То есть рассматривает элементы исходного массива как объекты, хранящие действительные значения указателей, а это в общем случае не так.

Комментариев нет:

Отправить комментарий