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

120

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

static uint8_t l_matrix[10][20];

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

Я получаю всевозможные ошибки, например:

  • предупреждение: присваивание из несовместимого типа указателя
  • индексируемое значение не является ни массивом, ни указателем
  • ошибка: недопустимое использование гибкого элемента массива
Укроп
источник
Прочтите stackoverflow.com/questions/423823/… это может вам помочь
Йоханнес Шауб - litb
1
@ JohannesSchaub-litb Этого больше не существует. (Как мне просмотреть это снова? Я знаю, что участники с низким уровнем репутации могут просматривать его, но я забыл, как ...)
Матин Улхак
1
@muntoo: Вот его копия: gist.github.com/sharth/ede13c0502d5dd8d45bd
Билл Линч,

Ответы:

140

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

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, но если вы включите анализ псевдонимов и другие агрессивные оптимизации, какой-нибудь компилятор может сделать предположение, которое может нарушить этот код. Сказав это, я никогда не сталкивался с компилятором, который не работал бы на нем (но опять же, я не использовал эту технику в реальном коде), и даже в C FAQ есть эта техника (с предупреждением о ее UB'ness ), и если вы не можете изменить тип массива, это последний вариант, который вас спасет :)

Йоханнес Шауб - litb
источник
+1 - хорошая информация о разбивке int (*) [] [20] - этого нельзя сделать на C ++
Фейсал Вали
@litb, извините, но это неверно, поскольку ваше решение не обеспечивает выделения памяти для массива.
Роб Уэллс,
2
@ Роб, я тебя не совсем понимаю. хранилище во всех этих случаях обеспечивает сам массив l_matix. Указатели на них берут память из того места, где они объявлены и как (стек, сегмент статических данных, ...).
Йоханнес Шауб - лит,
Просто любопытно, зачем нам нужен "&" адрес l_matrix?
электро
1
@Sohaib - нет, это создает только один указатель. Возможно, вы его перепутали с тем uint8_t *d[20], что создает массив из трех указателей на uint8_t, но в данном случае это не сработает.
Пало
28

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

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

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

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]vs plainint 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]



Вывод

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

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

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

Супер Кот
источник
1
Первая часть ответа хороша, а вторая - нет. Это неверно: на int *p1 = &(a[0]); // RIGHT !самом деле это идентичноint *p1 = a;
2501
@ 2501 Спасибо, что заметили ошибку, я ее исправил. Я не могу сказать наверняка, почему пример, определяющий это «правило», также противоречит ему. Стоит повторить, что то, что две сущности могут интерпретироваться как указатели и давать одно и то же значение, не означает, что они несут одно и то же значение.
Super Cat
7

В

int *ptr= l_matrix[0];

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

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

ведь двумерные массивы также хранятся как одномерные.

Сагар
источник
5

G'day,

Декларация

static uint8_t l_matrix[10][20];

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

Так не

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.

НТН

веселит,

Роб Уэллс
источник
1
uint8_t (* matrix_ptr) [] [20] << первые квадратные скобки нужно опустить, правильным является uint8_t (* matrix_ptr) [20]
Аконкагуа
5

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

int l_matrix[10][20];

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

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

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

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

Корнель
источник
1
Этот ответ вводит в заблуждение: это не делает аргумент массивом фиксированного размера, он остается указателем. static 10это своего рода гарантия присутствия как минимум 10 элементов, что опять же означает, что размер не фиксирован.
bluss
1
@bluss вопрос касался указателя, поэтому я не понимаю, как ответ с указателем ( с указанием ссылки ) вводит в заблуждение. Массив имеет фиксированный размер с точки зрения функции, поскольку доступ к элементам за этими пределами не определен.
Kornel
Я не думаю, что доступ за пределами 10 неопределен, я не вижу ничего, указывающего на это.
bluss
Этот ответ, кажется, предполагает, что без ключевого слова staticмассив не будет передан по ссылке, что неверно. В любом случае массивы передаются по ссылке. В исходном вопросе был задан другой вариант использования - доступ к элементам 2D-массива с использованием дополнительного указателя в той же функции / пространстве имен.
Пало
4

Вы всегда можете избежать возиться с компилятором, объявив массив как линейный и выполнив (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

}

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

гностицизм
источник
1
В любом случае это то, что делает компилятор C. В C действительно нет никакого реального понятия «массив» - нотация [] - это просто синтаксический сахар для арифметики указателей
Кен Кинан
7
Недостатком этого решения является то, что никогда не удается найти правильный способ сделать это.
Craig McQueen
2

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

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() {
   // The multidimentional array...
   char balance[5][100] = {
       "Subham",
       "Messi"
   };

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

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

  return 0;
}

Выход:

Subham
Messi

Это сработало...

Субхам Дебнат
источник
1

Сделать это можно так:

uint8_t (*matrix_ptr)[10][20] = &l_matrix;
Ник Дандулакис
источник
1
Разве это не занимает 10 * 20 байт оперативной памяти? (Я на микроконтроллере)
Дилл
Он будет занимать 4 байта или любого размера указателя в вашем поле. Но помните, что если у вас есть этот, вы должны индексировать с помощью matrix_ptr [0] [x] [y] или (* matrix_ptr) [x] [y]. Это прямая и
пословная
Спасибо, литб, я забыл упомянуть, как получить к нему доступ. Нет смысла редактировать мой ответ, так как вы отлично поработали со своим ответом :)
Ник Дандулакис,
Итак, занимает это 10 * 20 байт ОЗУ или нет?
Даниэль
@Danijel, поскольку это указатель на двумерный массив, он будет занимать только 4 байта или любого размера указателя в вашем ящике, то есть 16-битного, 32-битного, 64-битного и т. Д.
Ник Дандулакис,
1

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

static uint8_t l_matrix[10][20];

void test(){
   uint8_t *matrix_ptr = l_matrix[0]; //wrong idea 
}
Кен Кинан
источник
0

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

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;
mathengineer
источник
Привет. Этот вопрос отмечен значком c, поэтому ответ должен быть на том же языке. Обратите внимание на теги.
2501