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

Каков тип указателя на 2D-массив?

Я знаю, что следующее неверно:

int arr[2][3] = {}; //some array initialization here
int** ptr;
ptr = arr;

Но я очень удивлен, что на самом деле работают следующие строки

int arr[2][3] = {}; //some array initialization here
auto ptr = arr;
int another_arr[2][3] = {}; //some array initialization here
ptr = another_arr;

Может ли кто-нибудь объяснить, что такое тип, назначенный ptr во втором блоке кода, и что произошло под ним?

4b9b3361

Ответ 1

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

Но это только "размер внешнего массива", который распадается на указатель. Поскольку массивы являются строковыми, вы получаете int (*)[3] как тип указателя, который является указателем на одномерный массив, а не двумерный массив. Он указывает на первую "строку".

Если вы хотите, чтобы вывод ptr был указателем на массив, используйте адрес-оператора:

auto ptr = &arr;

Теперь ptr есть int(*)[2][3].

Ответ 2

В

auto ptr = arr;

arr распадается на указатель на свой первый элемент обычным способом; это эквивалентно

auto ptr = &arr[0];

Так как arr[0] - это массив из трех int s, что делает ptr a int (*)[3] - указатель на int[3].

another_arr распадается точно так же, поэтому в

ptr = another_arr;

обе стороны назначения имеют тип int (*)[3], и вы можете назначить T* a T* для любого типа T.

Указатель на arr сам имеет тип int(*)[2][3].

Если вам нужен указатель на массив, а не указатель на первый элемент массива, вам нужно использовать &:

auto ptr = &arr; 

Ответ 3

Во-первых, давайте посмотрим, почему вы не можете назначить int arr[2][3] int **. Чтобы упростить визуализацию, мы инициализируем ваш массив последовательностью и рассмотрим, как это выглядит в памяти:

int arr[2][3] = {{1,2,3},{4,5,6}};

В памяти данные массива хранятся как один блок, как обычный, 1D-массив:

arr: [ 1, 2, 3, 4, 5, 6 ]

Переменная arr содержит адрес начала этого блока и его тип (int[2][3]), который компилятор знает, чтобы интерпретировать индекс как arr[1][0] как значение "принять значение, которое находится в позиции (1 * 2 + 0) в массиве".

Однако для указателя на указатель (int**) ожидается, что указатель на указатель содержит либо один адрес памяти, либо массив адресов памяти, и этот/эти адреса указывают на (an) другое одиночное значение int или массив int. Скажем, мы скопировали массив arr в int **ptrptr. В памяти это будет выглядеть так:

ptrptr:     [0x203F0B20, 0x203F17D4]
0x203F0B20: [ 1, 2, 3 ]
0x203F17D4: [ 4, 5, 6 ]

Поэтому в дополнение к фактическим данным int дополнительный указатель должен храниться для каждой строки массива. Вместо того, чтобы преобразовывать два индекса в один поиск массива, доступ должен выполняться путем создания первого поиска массива ( "взять второе значение в ptrptr, чтобы получить int *" ), затем поиск более простого массива ( "принять первое значение в массив по адресу, хранящемуся ранее полученным int *" ).

Вот программа, которая иллюстрирует это:

#include <iostream>

int main()
{
    int arr[2][3] = {{1,2,3},{4,5,6}};

    std::cout << "Memory addresses for int arr[2][3]:" << std::endl;
    for (int i=0; i<2; i++)
    {
        for (int j=0; j<3; j++)
        {
            std::cout << reinterpret_cast<void*>(&arr[i][j]) << ": " << arr[i][j] << std::endl;
        }
    }

    std::cout << std::endl << "Memory addresses for int **ptrptr:" << std::endl;
    int **ptrptr = new int*[2];
    for (int i=0; i<2; i++)
    {
        ptrptr[i] = new int[3];
        for (int j=0; j<3; j++)
        {
            ptrptr[i][j] = arr[i][j];
            std::cout << reinterpret_cast<void*>(&ptrptr[i][j]) << ": " << ptrptr[i][j] << std::endl;
        }
    }

    // Cleanup
    for (int i=0; i<2; i++)
    {
        delete[] ptrptr[i];
        ptrptr[i] = nullptr;
    }
    delete[] ptrptr;
    ptrptr = nullptr;

    return 0;
}

Вывод:

Memory addresses for int arr[2][3]:
0x7ecd3ccc0260: 1
0x7ecd3ccc0264: 2
0x7ecd3ccc0268: 3
0x7ecd3ccc026c: 4
0x7ecd3ccc0270: 5
0x7ecd3ccc0274: 6

Memory addresses for int **ptrptr:
0x38a1a70: 1
0x38a1a74: 2
0x38a1a78: 3
0x38a1a90: 4
0x38a1a94: 5
0x38a1a98: 6

Обратите внимание, что адреса памяти всегда увеличиваются на 4 байта для arr, но для ptrptr есть скачок в 24 байта между значениями 3 и 4.

Простое присваивание не может создать структуру указателя на указатель, необходимую для типа int **, поэтому петли были необходимы в вышеуказанной программе. Лучшее, что он может сделать, это разложить тип int[2][3] на указатель на строку этого массива, т.е. int (*)[3]. Это то, что ваш auto ptr = arr; заканчивается.

Ответ 4

Каков тип [...]

Вы уже пытались попросить компилятор рассказать вам тип выражения?

int main()
{
    int arr[2][3] = {{0,1,2}, {3,4,5}};  // <-- direct complete initialized here

    auto ptr = arr;                     // <-- address assignment only

    cout << "arr: " << typeid(arr).name() << endl;
    cout << "ptr: " << typeid(ptr).name() << endl;
    return 0;
}

Я должен признать, что вывод

arr: A2_A3_i
ptr: PA3_i

кажется, не очень читается с первого взгляда (по сравнению с некоторыми другими языками), но в сомнении это может помочь. Он очень компактный, но к нему скоро можно привыкнуть. Кодирование зависит от компилятора, если вы используете gcc, вы можете прочитать Глава 29. Повреждение, чтобы понять, как это сделать.

Изменить:

некоторые эксперименты с некоторой функцией simple_cpp_name, подобной этой рудиментарной руке

#include <typeinfo>
#include <cxxabi.h>
#include <stdlib.h>
#include <string>

std::string simple_cpp_name(const std::type_info& ti)
{
    /// simplified code extracted from "Chapter 29. Demangling"
    /// https://gcc.gnu.org/onlinedocs/libstdc++/manual/ext_demangling.html
    char* realname = abi::__cxa_demangle(ti.name(), 0, 0, 0);
    std::string name = realname;
    free(realname);
    return name;
}

покажет вам, что auto &rfa = arr; делает rfa тем же типом, что и arr, который равен int [2][3].