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

Массив указателей на массив фиксированного размера

Я попытался назначить два массива с фиксированным размером массива указателей на них, но компилятор предупреждает меня, и я не понимаю, почему.

int A[5][5];
int B[5][5];
int*** C = {&A, &B};

Этот код компилируется со следующим предупреждением:

предупреждение: инициализация из несовместимого типа указателя [включена по умолчанию]

Если я запустил код, он поднимет segmentation fault. Однако, если я динамически выделяю A и B, он работает отлично. Почему это?

4b9b3361

Ответ 1

Если вы хотите, чтобы объявление C соответствовало существующим объявлениям A и B, вам нужно сделать это следующим образом:

int A[5][5];
int B[5][5];
int (*C[])[5][5] = {&A, &B};

Тип C читается как "C - это массив указателей на int [5][5] массивы". Поскольку вы не можете назначить весь массив, вам нужно назначить указатель на массив.

В этом объявлении (*C[0])[1][2] обращается к той же ячейке памяти, что и A[1][2].

Если вам нужен более чистый синтаксис, например C[0][1][2], вам нужно будет сделать то, что заявили другие, и распределить память динамически:

int **A;
int **B;
// allocate memory for A and each A[i]
// allocate memory for B and each B[i]
int **C[] = {A, B};

Вы также можете сделать это, используя синтаксис, предложенный Владом из Москвы:

int A[5][5];
int B[5][5];
int (*C[])[5] = {A, B};

Это объявление C читается как "C - это массив указателей на int [5] массивы". В этом случае каждый элемент массива C имеет тип int (*)[5], а массив типа int [5][5] может распадаться на этот тип.

Теперь вы можете использовать C[0][1][2] для доступа к той же ячейке памяти, что и A[1][2].

Эта логика также может быть расширена до более высоких измерений:

int A[5][5][3];
int B[5][5][3];
int (*C[])[5][3] = {A, B};

Ответ 2

К сожалению, там много дерьмовых книг/учебников/учителей, которые научат вас неправильным вещам....

Забудьте о указателях-указателях, они не имеют ничего общего с массивами. Период.

Также как правило: когда вы обнаруживаете, что используете более двух уровней косвенности, это, скорее всего, означает, что ваш дизайн программы в корне ошибочен и нуждается в переделке с нуля.


Чтобы сделать это правильно, вам нужно будет сделать следующее:

Указатель на массив int [5][5] называется указателем на массив и объявляется как int(*)[5][5]. Пример:

int A[5][5];
int (*ptr)[5][5] = &A;

Если вам нужен массив указателей массива, это будет тип int(*[])[5][5]. Пример:

int A[5][5];
int B[5][5];
int (*arr[2])[5][5] = {&A, &B};

Как вы можете сказать, этот код выглядит бесполезно сложным - и это так. Будет больно обращаться к отдельным элементам, так как вам нужно будет ввести (*arr[x])[y][z]. Значение: "в массиве указателей массива берут число указателя на массив x, берут содержимое, на которое он указывает, - это 2D-массив, затем берут элемент индекса [y] [z] в этом массиве".

Изобретать такие конструкции - просто безумие, и я бы не рекомендовал ничего. Я полагаю, что код можно упростить, работая с указателем простого массива:

int A[5][5];
int B[5][5];
int (*arr[2])[5][5] = {&A, &B};
int (*ptr)[5][5] = arr[0];
...
ptr[x][y][z] = 0;

Однако это еще несколько сложный код. Рассмотрим совершенно другой дизайн! Примеры:

  • Сделайте 3D-массив.
  • Создайте структуру, содержащую 2D-массив, затем создайте массив таких структур.

Ответ 3

В строке

много ошибок.
int*** C = {&A, &B};

Вы объявляете один указатель C, но вы говорите ему, указывая на несколько объектов; это не сработает. Что вам нужно сделать, это объявить C как массив указателей на эти массивы.

Типы &A и &B: int (*)[5][5], или "указатель на 5-элементный массив из 5-элементного массива int"; таким образом, тип C должен быть "массивом указателя на 5-элементный массив из 5-элементного массива int" или

int (*C[2])[5][5] = { &A, &B };

который читается как

      C           -- C is a
      C[2]        -- 2-element array of
     *C[2]        -- pointers to
    (*C[2])[5]    -- 5-element arrays of
    (*C[2])[5][5] -- 5-element arrays of
int (*C[2])[5][5] -- int

Тьфу. Это чертовски уродливо. Это становится еще более уродливым, если вы хотите получить доступ к элементу из A или B через C:

int x = (*C[0])[i][j]; // x = A[i][j]
int y = (*C[1])[i][j]; // y = B[i][j]

Нам нужно явно разыменовать C[i], прежде чем мы можем индексировать его в массив, на который он указывает, а так как индексный оператор [] имеет более высокий приоритет, чем унарный оператор *, нам нужно сгруппировать *C[0] в parens,

Мы можем немного почистить это. За исключением случаев, когда он является операндом операторов sizeof или унарных & (или является строковым литералом, используемым для инициализации другого массива в объявлении), выражение типа "N -element array of T" будет преобразован ( "распад" ) в выражение типа "указатель на T", а значение выражения будет адресом первого элемента массива.

Выражения A и B имеют тип int [5][5] или "5-элементный массив из 5-элементного массива int". В соответствии с вышеприведенным правилом оба выражения "распадаются" на выражения типа "указатель на 5-элементный массив int" или int (*)[5]. Если мы инициализируем массив с A и B вместо &A и &B, нам нужен массив указателей на 5-элементные массивы int или

int (*C[2])[5] = { A, B };

Ладно, это все еще довольно гладко, но это так же чисто, как это получится без typedefs.

Итак, как мы получаем доступ к элементам A и B через C?

Помните, что операция индексирования массива a[i] определяется как *(a + i); то есть заданный базовый адрес A, смещение i элементов (не байтов) 1 из этого адреса и разыгрывать результат. Это означает, что

*a == *(a + 0) == a[0]

Таким образом,

*C[i] == *(C[i] + 0) == C[i][0]

Объединяя все это:

C[0] == A                      // int [5][5], decays to int (*)[5]
C[1] == B                      // int [5][5], decays to int (*)[5]

*C[0] == C[0][0] == A[0]       // int [5], decays to int *
*C[1] == C[1][0] == B[0]       // int [5], decays to int *

C[0][i] == A[i]                // int [5], decays to int *
C[1][i] == B[i]                // int [5], decays to int *

C[0][i][j] == A[i][j]          // int
C[1][i][j] == B[i][j]          // int

Мы можем индексировать C, как если бы это был трехмерный массив int, который немного чище, чем (*C[i)[j][k].

Эта таблица также может быть полезна:

Expression        Type                "Decays" to       Value
----------        ----                -----------       -----
         A        int [5][5]           int (*)[5]       Address of A[0]
        &A        int (*)[5][5]                         Address of A
        *A        int [5]              int *            Value of A[0] (address of A[0][0])
      A[i]        int [5]              int *            Value of A[i] (address of A[i][0])
     &A[i]        int (*)[5]                            Address of A[i]
     *A[i]        int                                   Value of A[i][0]   
   A[i][j]        int                                   Value of A[i][j]   

Обратите внимание, что A, &A, A[0], &A[0] и &A[0][0] все дают одно и то же значение (адрес массива и адрес первого элемента массива всегда одинаковы), но типы различны, как показано в таблице выше.


  • Арифметика указателя учитывает размер заостренного типа; если p содержит адрес объекта int, тогда p+1 выводит адрес следующего объекта int, который может быть от 2 до 4 байтов.

Ответ 4

Общим заблуждением среди новичков C является то, что они просто предполагают, что указатели и массивы эквивалентны. Это совершенно неправильно.

Путаница приходит к новичкам, когда они видят код типа

int a1[] = {1,2,3,4,5};
int *p1 = a1;            // Beginners intuition: If 'p1' is a pointer and 'a1' can be assigned
                         // to it then arrays are pointers and pointers are arrays.

p1[1] = 0;               // Oh! I was right
a1[3] = 0;               // Bruce Wayne is the Batman! Yeah.

Теперь новички проверяют, что массивы - указатели и указатели - массивы, поэтому они проводят такие эксперименты:

int a2[][5] = {{0}};
int **p2 = a2;

И затем появляется предупреждение о несовместимости указателей, и они думают: "Боже мой! Почему этот массив стал Харви Дент?".

Некоторые даже идут на шаг вперед

int a3[][5][10] = {{{0}}};
int ***p3 = a3;             // "?"

а затем Riddler приходит к их кошмару эквивалентности указателя массива.

введите описание изображения здесь

Всегда помните, что массивы не являются указателями и наоборот. Массив - это тип данных, а указателем является другой тип данных (который не является типом массива). Это было рассмотрено несколько лет назад в C-FAQ:

Говорить, что массивы и указатели являются "эквивалентными", означает, что они не идентичны и даже не взаимозаменяемы. Это означает, что арифметика массива и указателя определяется так, что указатель может быть удобно использован для доступа к массиву или для имитации массива. Другими словами, как выразился Уэйн Трооп, арифметика указателей it "и индексирование массива [, которые] эквивалентны в C, указатели и массивы различны.

Теперь всегда помните несколько важных правил для массива, чтобы избежать такого путаницы:

  • Массивы не являются указателями. Указатели не являются массивами.
  • Массивы преобразуются в указатель на свой первый элемент при использовании в выражении, за исключением случаев, когда операнд оператора sizeof и &.
  • Это арифметика указателей и индексирование массива, которые являются такими же.
  • Указатели и массивы отличаются.
  • Я сказал, что "указатели не являются массивами и наоборот".

Теперь у вас есть правила, вы можете заключить, что в

int a1[] = {1,2,3,4,5};
int *p1 = a1;

a1 - это массив, а в объявлении int *p1 = a1; он преобразуется в указатель на его первый элемент. Его элементы имеют тип int, тогда указатель на его первый элемент будет иметь тип int *, который совместим с p1.

В

int a2[][5] = {{0}};
int **p2 = a2;

a2 - это массив, а в int **p2 = a2; он распадается на указатель на свой первый элемент. Его элементы имеют тип int[5] (2D-массив представляет собой массив из 1D массивов), поэтому указатель на его первый элемент будет иметь тип int(*)[5] (указатель на массив), который несовместим с типом int **. Это должно быть

int (*p2)[5] = a2;

Аналогично для

int a3[][5][10] = {{{0}}};
int ***p3 = a3;

Элементы a3 имеют тип int [5][10], а указатель на его первый элемент будет иметь тип int (*)[5][10], но p3 имеет тип int ***, поэтому для обеспечения их совместимости он должен быть

int (*p3)[5][10] = a3;

Теперь перейдем к вашему фрагменту

int A[5][5];
int B[5][5];
int*** C = {&A, &B};

&A и &B имеют тип int(*)[5][5]. C имеет тип int***, это не массив. Поскольку вы хотите сделать C для хранения адреса массивов A и B, вам нужно объявить C как массив из двух элементов типа int(*)[5][5]. Это должно быть сделано как

int (*C[2])[5][5] = {&A, &B};

Однако, если я динамически выделяю A и B, он работает нормально. Почему это?

В этом случае вы должны были объявить A и B как int **. В этом случае оба являются указателями, а не массивами. C имеет тип int ***, поэтому он может содержать адрес данных типа int**. Обратите внимание, что в этом случае декларация int*** C = {&A, &B}; должна быть

  int*** C = &A;

В случае int*** C = {&A, &B}; поведение программы будет либо undefined, либо определена реализация.

C11: 5.1.1.3 (P1):

Соответствующая реализация должна выдавать, по меньшей мере, одно диагностическое сообщение (идентифицированное определенным образом), если блок трансляции или блок перевода содержит нарушение любого синтаксического правила или ограничения, даже если поведение также явно указано как undefined или определенный реализацией

Прочитайте этот пост для дальнейшего объяснения.

Ответ 5

Массивы - это не то же самое, что многомерные указатели в C. Имя массива интерпретируется как адрес буфера, который содержит его в большинстве случаев, независимо от того, как вы его индексируете. Если A объявлен как int A[5][5], тогда A обычно означает адрес первого элемента, то есть он эффективно интерпретируется как int * (фактически int *[5]), а не int ** вообще, Для вычисления адреса просто требуется два элемента: A[x][y] = A + x + 5 * y. Это удобство для выполнения A[x + 5 * y], оно не продвигает A в многомерный буфер.

Если вам нужны многомерные указатели на C, вы тоже можете это сделать. Синтаксис был бы очень похож, но для этого требуется немного больше настроек. Есть несколько общих способов сделать это.

С одним буфером:

int **A = malloc(5 * sizeof(int *));
A[0] = malloc(5 * 5 * sizeof(int));
int i;
for(i = 1; i < 5; i++) {
    A[i] = A[0] + 5 * i;
}

С отдельным буфером для каждой строки:

int **A = malloc(5 * sizeof(int *));
int i;
for(i = 0; i < 5; i++) {
    A[i] = malloc(5 * sizeof(int));
}

Ответ 6

Вас путают эквивалентность массивов и указателей.

Когда вы объявляете массив как A[5][5], потому что вы объявили оба измерения, C будет выделять память на 25 объектов смежно. То есть, память будет выделяться следующим образом:

A00, A01, ... A04, A10, A11, ..., A14, A20, ..., A24, ...

Результирующий объект A является указателем на начало этого блока памяти. Он имеет тип int *, а не int **.

Если вам нужен вектор указателей на массивы, вы должны объявить свои переменные следующим образом:

int   *A[5], *B[5];

Это даст вам:

A0, A1, A2, A3, A4

весь тип int*, который вы должны заполнить, используя malloc() или что-то еще.

В качестве альтернативы вы можете объявить C как int **C.

Ответ 7

Несмотря на то, что массивы и указатели тесно связаны друг с другом, это совсем не то же самое. Люди иногда путаются по этому поводу, потому что в большинстве контекстов значения массива распадаются на указатели и потому, что нотация массива может использоваться в прототипах функций для объявления параметров, которые на самом деле являются указателями. Кроме того, многие люди считают, что нотация индексирования массива действительно выполняет комбинацию арифметики указателей и разыменования, так что она одинаково хорошо работает для значений указателя и значений массива (потому что значения массива распадаются на указатели).

Учитывая объявление

int A[5][5];

Переменная A обозначает массив из пяти массивов из пяти int. Это распадается, где он распадается, на указатель типа int (*)[5] - то есть указатель на массив из 5 int. С другой стороны, указатель на весь многомерный массив имеет тип int (*)[5][5] (указатель на массив из 5 массивов 5 int), который полностью отличается от int *** (указатель на указатель на указатель на int). Если вы хотите объявить указатель на многомерный массив, такой как эти, вы можете сделать это следующим образом:

int A[5][5];
int B[5][5];
int (*C)[5][5] = &A;

Если вы хотите объявить массив таких указателей, вы можете сделать это:

int (*D[2])[5][5] = { &A, &B };

Добавлено:

Эти различия вступают в игру различными способами, причем наиболее важными являются контексты, в которых значения массивов не разлагаются на указатели и контексты, связанные с ними. Одним из наиболее значительных из них является значение операнда оператора sizeof. Учитывая вышеприведенные объявления, все следующие реляционные выражения оцениваются в 1 (true):

sizeof(A)       == 5 * 5 * sizeof(int)
sizeof(A[0])    == 5 * sizeof(int)
sizeof(A[0][4]) == sizeof(int)
sizeof(D[1])    == sizeof(C)
sizeof(*C)      == sizeof(A)

Кроме того, вероятно, но не гарантировано, что эти реляционные выражения оцениваются в 1:

sizeof(C)       == sizeof(void *)
sizeof(D)       == 2 * sizeof(void *)

Это имеет фундаментальное значение для работы индексации массивов и важно понимать, когда вы выделяете память.

Ответ 8

Либо вы должны объявить третий массив, как

int A[5][5];
int B[5][5];
int ( *C[] )[N][N] = { &A, &B };

который представляет собой массив указателей на двумерные массивы.

Например

#include <stdio.h>

#define N   5

void output( int ( *a )[N][N] )
{
    for ( size_t i = 0; i < N; i++ )
    {
        for ( size_t j = 0; j < N; j++ ) printf( "%2d ", ( *a )[i][j] );
        printf( "\n" );
    }
}

int main( void )
{
    int A[N][N] =
    {
        {  1,  2,  3,  4,  5 },
        {  6,  7,  8,  9, 10 },
        { 11, 12, 13, 14, 15 },
        { 16, 17, 18, 19, 20 },
        { 21, 22, 23, 24, 25 }
    };
    int B[N][N] =
    {
        { 25, 24, 23, 22, 21 },
        { 20, 19, 18, 17, 16 },
        { 15, 14, 13, 12, 11 },
        { 10,  9,  8,  7,  6 },
        {  5,  4,  3,  2,  1 }
    };

/*
    typedef int ( *T )[N][N];
    T C[] = { &A, &B };
*/

    int ( *C[] )[N][N] = { &A, &B };

    output( C[0] );
    printf( "\n" );

    output( C[1] );
    printf( "\n" );
}        

Выход программы

 1  2  3  4  5 
 6  7  8  9 10 
11 12 13 14 15 
16 17 18 19 20 
21 22 23 24 25 

25 24 23 22 21 
20 19 18 17 16 
15 14 13 12 11 
10  9  8  7  6 
 5  4  3  2  1 

или как

int A[5][5];
int B[5][5];
int ( *C[] )[N] = { A, B };

который представляет собой массив указателей на первые элементы двумерных массивов.

Например

#include <stdio.h>

#define N   5

void output( int ( *a )[N] )
{
    for ( size_t i = 0; i < N; i++ )
    {
        for ( size_t j = 0; j < N; j++ ) printf( "%2d ", a[i][j] );
        printf( "\n" );
    }
}

int main( void )
{
    int A[N][N] =
    {
        {  1,  2,  3,  4,  5 },
        {  6,  7,  8,  9, 10 },
        { 11, 12, 13, 14, 15 },
        { 16, 17, 18, 19, 20 },
        { 21, 22, 23, 24, 25 }
    };
    int B[N][N] =
    {
        { 25, 24, 23, 22, 21 },
        { 20, 19, 18, 17, 16 },
        { 15, 14, 13, 12, 11 },
        { 10,  9,  8,  7,  6 },
        {  5,  4,  3,  2,  1 }
    };

/*
    typedef int ( *T )[N];
    T C[] = { A, B };
*/

    int ( *C[] )[N] = { A, B };

    output( C[0] );
    printf( "\n" );

    output( C[1] );
    printf( "\n" );
}        

Вывод программы такой же, как и выше

 1  2  3  4  5 
 6  7  8  9 10 
11 12 13 14 15 
16 17 18 19 20 
21 22 23 24 25 

25 24 23 22 21 
20 19 18 17 16 
15 14 13 12 11 
10  9  8  7  6 
 5  4  3  2  1 

в зависимости от того, как вы собираетесь использовать третий массив.

Использование typedefs (показано в демонстрационной программе в комментариях) упрощает определение массивов.

Что касается этого объявления

int*** C = {&A, &B};

то в левой части объявлен указатель типа int ***, который является скалярным объектом, а в правой части - список инициализаторов, которые имеют другой тип int ( * )[N][N].

Итак, компилятор выдает сообщение.

Ответ 9

Я большой сторонник использования typedef:

#define SIZE 5

typedef int  OneD[SIZE]; // OneD is a one-dimensional array of ints
typedef OneD TwoD[SIZE]; // TwoD is a one-dimensional array of OneD's
                         // So it a two-dimensional array of ints!

TwoD a;
TwoD b;

TwoD *c[] = { &a, &b, 0 }; // c is a one-dimensional array of pointers to TwoD's
                           // That does NOT make it a three-dimensional array!

int main() {
    for (int i = 0; c[i] != 0; ++i) { // Test contents of c to not go too far!
        for (int j = 0; j < SIZE; ++j) {
            for (int k = 0; k < SIZE; ++k) {
//              c[i][j][k] = 0;    // Error! This proves it not a 3D array!
                (*c[i])[j][k] = 0; // You need to dereference the entry in c first
            } // for
        } // for
    } // for
    return 0;
} // main()