Страницы

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

среда, 10 апреля 2019 г.

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

Так не компилируется
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[][] одно и то же. В чём разница?


Ответ

Массив в выражениях преобразуется к указателю на свой первый элемент.
Если у вас есть, например, объявление массива
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];
То есть рассматривает элементы исходного массива как объекты, хранящие действительные значения указателей, а это в общем случае не так.

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

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