Создать указатель на двумерный массив

Мне нужен указатель на статический 2-мерный массив. Как это сделать?

static uint8_t l_matrix[10][20];

void test(){
   uint8_t **matrix_ptr = l_matrix; //wrong idea 
}

Я получаю все виды ошибок, таких как:

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

10 ответов

Решение

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

uint8_t (*matrix_ptr)[20] = l_matrix;

С typedef это выглядит чище

typedef uint8_t array_of_20_uint8_t[20];
array_of_20_uint8_t *matrix_ptr = l_matrix;

Тогда ты снова сможешь наслаждаться жизнью:)

matrix_ptr[0][1] = ...;

Остерегайтесь мира указателей / массивов в C, вокруг этого много путаницы.


редактировать

Рассматривая некоторые другие ответы здесь, потому что поля комментариев слишком короткие, чтобы делать там. Было предложено несколько альтернатив, но не было показано, как они себя ведут. Вот как они делают

uint8_t (*matrix_ptr)[][20] = l_matrix;

Если вы исправите ошибку и добавите адрес оператора & как в следующем фрагменте

uint8_t (*matrix_ptr)[][20] = &l_matrix;

Затем этот создает указатель на неполный тип массива элементов массива типа 20 uint8_t. Поскольку указатель на массив массивов, вы должны получить к нему доступ

(*matrix_ptr)[0][1] = ...;

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

matrix_ptr[0][0][1] = ...;

Поскольку индексация требует, чтобы размер типа элемента был известен (индексирование подразумевает добавление целого числа к указателю, поэтому оно не будет работать с неполными типами). Обратите внимание, что это работает только в C, так как T[] а также T[N] совместимые типы. C++ не имеет концепции совместимых типов, и поэтому он будет отклонять этот код, потому что T[] а также T[10] разные типы.


Следующая альтернатива не работает вообще, потому что тип элемента массива, когда вы рассматриваете его как одномерный массив, не uint8_t, но uint8_t[20]

uint8_t *matrix_ptr = l_matrix; // fail

Следующее является хорошей альтернативой

uint8_t (*matrix_ptr)[10][20] = &l_matrix;

Вы получаете к нему доступ с

(*matrix_ptr)[0][1] = ...;
matrix_ptr[0][0][1] = ...; // also possible now

Преимущество в том, что он сохраняет размер внешнего измерения. Таким образом, вы можете применить sizeof к нему

sizeof (*matrix_ptr) == sizeof(uint8_t) * 10 * 20

Есть еще один ответ, который использует тот факт, что элементы в массиве хранятся непрерывно

uint8_t *matrix_ptr = l_matrix[0];

Теперь это только формально позволяет получить доступ к элементам первого элемента двумерного массива. То есть выполняется следующее условие

matrix_ptr[0] = ...; // valid
matrix_ptr[19] = ...; // valid

matrix_ptr[20] = ...; // undefined behavior
matrix_ptr[10*20-1] = ...; // undefined behavior

Вы заметите, что это, вероятно, работает до 10*20-1, но если вы добавите анализ псевдонимов и другие агрессивные оптимизации, какой-то компилятор может сделать предположение, что он может нарушить этот код. Сказав это, я никогда не сталкивался с компилятором, который не работает на нем (но, опять же, я не использовал эту технику в реальном коде), и даже в FAQ C содержится эта техника (с предупреждением о его UB'ness), и если вы не можете изменить тип массива, это последний вариант, чтобы спасти вас:)

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

Массивы не указатели!

Прежде всего (и это было достаточно проповедано), массивы не указатели. Вместо этого в большинстве случаев они "распадаются" по адресу к первому элементу, который может быть назначен указателю:

int a[] = {1, 2, 3};

int *p = a; // p now points to a[0]

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



Многомерные массивы

Многомерные массивы - это просто способ "разделить" память таким образом, чтобы компилятор / машина могли понимать и работать с ней.

Например, int a[4][3][5] = массив, содержащий 4*3*5 (60) "кусочков" памяти целого размера.

Преимущество перед использованием int a[4][3][5] против равнины int b[60] в том, что они теперь "разделены" (проще работать со своими "кусками", если необходимо), и теперь программа может выполнять проверку границ.

По факту, int a[4][3][5] хранится в точности как int b[60] в памяти - единственное отличие состоит в том, что программа теперь управляет им, как будто они являются отдельными объектами определенных размеров (в частности, четыре группы по три группы по пять).

Имейте в виду: оба int a[4][3][5] а также int b[60] одинаковы в памяти, и единственное отличие состоит в том, как они обрабатываются приложением / компилятором

{
  {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}
  {26, 27, 28, 29, 30}
}
{
  {31, 32, 33, 34, 35}
  {36, 37, 38, 39, 40}
  {41, 42, 43, 44, 45}
}
{
  {46, 47, 48, 49, 50}
  {51, 52, 53, 54, 55}
  {56, 57, 58, 59, 60}
}

Отсюда ясно видно, что каждый "раздел" - это просто массив, за которым программа следит.



Синтаксис

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

int a[3][3];

printf("%p %p", a, a[0]);

Приведенный выше пример печатает один и тот же адрес памяти дважды, например так:

0x7eb5a3b4 0x7eb5a3b4

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

int *p1 = a[0]; // RIGHT !

int *p2 = a; // WRONG !

Почему не могу a быть назначенным на указатель, но a[0] Можно?

Это, просто, является следствием многомерных массивов, и я объясню почему:

На уровнеa"Мы все еще видим, что у нас есть еще одно" измерение ", которого мы с нетерпением ждем. На уровнеa[0]Однако мы уже находимся в верхнем измерении, поэтому, что касается программы, мы просто смотрим на обычный массив.

Вы можете спросить:

Почему имеет значение, если массив является многомерным в отношении создания для него указателя?

Лучше думать так:

"Распад" из многомерного массива - это не просто адрес, а адрес с данными раздела (он все еще понимает, что его лежащие в основе данные состоят из других массивов), который состоит из границ, установленных массивом за пределами первого измерения.

Эта логика "раздела" не может существовать в указателе, если мы не укажем ее:

int a[4][5][95][8];

int (*p)[5][95][8];

p = a; // p = *a[0] // p = a+0

В противном случае значение свойств сортировки массива теряется.

Также обратите внимание на использование скобок вокруг *p: int (*p)[5][95][8] - Это означает, что мы делаем указатель с этими границами, а не массив указателей с этими границами: int *p[5][95][8]



Заключение

Давайте рассмотрим:

  • Массивы распадаются на адреса, если у них нет другого назначения в используемом контексте
  • Многомерные массивы - это просто массивы массивов - следовательно, "распавшийся" адрес будет нести бремя "у меня есть субразмеры"
  • Данные измерения не могут существовать в указателе, если вы не передадите его ему.

Вкратце: многомерные массивы распадаются на адреса, которые несут способность понимать их содержание.

В

int *ptr= l_matrix[0];

вы можете получить доступ как

*p
*(p+1)
*(p+2)

ведь 2-мерные массивы также сохраняются как 1-й.

G'day,

Декларация

static uint8_t l_matrix[10][20];

выделил хранилище для 10 строк из 20 местоположений unit8_t, то есть 200 местоположений размером uint8_t, причем каждый элемент был найден путем вычисления 20 x row + column.

Так не

uint8_t (*matrix_ptr)[20] = l_matrix;

дать вам то, что вам нужно, и указать на нулевой элемент столбца первой строки массива?

Изменить: Думая об этом немного дальше, не является ли имя массива, по определению, указателем? То есть имя массива является синонимом местоположения первого элемента, т.е. l_matrix [0] [0]?

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

typedef uint8_t array_of_20_uint8_t[20];
array_of_20_uint8_t *matrix_ptr = l_matrix;

не обеспечивает никакого выделения памяти для рассматриваемого массива.

Как упомянуто выше, и как определено стандартом, заявление:

static uint8_t l_matrix[10][20];

выделил 200 последовательных местоположений типа uint8_t.

Ссылаясь на l_matrix, используя операторы вида:

(*l_matrix + (20 * rowno) + colno)

даст вам содержимое элемента colno'th, найденного в строке rowno.

Все манипуляции с указателями автоматически учитывают размер объекта, на который указывает. - K&R Раздел 5.4, стр.103

Это также относится к случаю, если при сохранении объекта под рукой задействовано любое заполнение или сдвиг выравнивания байтов. Компилятор автоматически подстраивается под них. По определению стандарта C ANSI.

НТН

веселит,

В C99 (поддерживается clang и gcc) есть неясный синтаксис для передачи многомерных массивов в функции по ссылке:

int l_matrix[10][20];

void test(int matrix_ptr[static 10][20]) {
}

int main(void) {
    test(l_matrix);
}

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

К сожалению, это не исправляет sizeof() и компиляторы, кажется, еще не используют эту информацию, поэтому она остается любопытной.

Вы всегда можете избежать возни с компилятором, объявив массив как линейный и выполнив (row,col) для расчета индекса массива самостоятельно.

static uint8_t l_matrix[200];

void test(int row, int col, uint8_t val)

{

   uint8_t* matrix_ptr = l_matrix;
   matrix_ptr [col+y*row] = val; // to assign a value

}

это то, что компилятор сделал бы в любом случае.

Основной синтаксис инициализирующего указателя, который указывает на многомерный массив:

type (*pointer)[ 1st dimension size ][2nd dimension size ][..]=&array_name

Основной синтаксис для вызова это

(*pointer_name)[ 1st index][2nd index][...]

Вот пример

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
   char balance[5][100] = { {"Subham"},{"Messi"} };//The multidimentional array...

   char (*p)[5][100]=&balance;//Pointer initialization...

   printf("%s\n",(*p)[0]);//Calling...
   printf("%s\n",(*p)[1]);//Calling...

  return 0;
}

Выход:

Subham
Messi

Это сделал это...

Вы хотите указатель на первый элемент, поэтому;

static uint8_t l_matrix[10][20];

void test(){
   uint8_t *matrix_ptr = l_matrix[0]; //wrong idea 
}

Вы можете сделать это так:

uint8_t (*matrix_ptr)[10][20] = &l_matrix;

Вы также можете добавить смещение, если хотите использовать отрицательные индексы:

uint8_t l_matrix[10][20];
uint8_t (*matrix_ptr)[20] = l_matrix+5;
matrix_ptr[-4][1]=7;

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

uint8_t (*matrix_ptr)[20] = (uint8_t (*)[20]) l_matrix;

В общем случае указатель на двумерный массив объявляется так:

int ***matrix_ptr = &l_matrix;

Определения указателей разрешаются изнутри - или из обозначения, которое ближе всего к имени переменной, и удаляется с каждым шагом. Слева направо:

Третий " " - это "указатель". Второе " " относится к первому измерению массива. Первое " " для второго измерения массива.
Вы можете продолжить добавлять " " для любых дополнительных измерений, которые вы хотите добавить в массив.

Нет необходимости знать размеры массива, чтобы объявить указатель на этот массив.

Нотация [] является синтаксическим сахаром для вычисления смещений массива, но это хороший сахар:

matrix_ptr[0][1] = ...;

Не требуется, чтобы во втором измерении двумерной матрицы было одинаковое количество элементов в каждой позиции.

Другие вопросы по тегам