Создать указатель на двумерный массив - PullRequest
106 голосов
/ 27 июня 2009

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

static uint8_t l_matrix[10][20];

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

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

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

Ответы [ 10 ]

127 голосов
/ 27 июня 2009

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

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 ), и если вы не можете изменить тип массива, это последний вариант, чтобы спасти вас:)

23 голосов
/ 10 января 2016

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

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

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

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]', однако, мы уже находимся в верхнем измерении, поэтому, что касается программы, мы просто смотрим на обычный массив.

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

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

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

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

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

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]



Заключение

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

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

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

7 голосов
/ 18 февраля 2012

В

int *ptr= l_matrix[0];

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

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

после того, как все 2-мерные массивы также сохранены как 1-й.

5 голосов
/ 01 сентября 2013

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

int l_matrix[10][20];

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

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

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

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

5 голосов
/ 27 июня 2009

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.

НТН

ура

4 голосов
/ 27 июня 2009

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

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

static uint8_t l_matrix[10][20];

void test(){
   uint8_t *matrix_ptr = l_matrix[0]; //wrong idea 
}
1 голос
/ 27 июня 2009

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

uint8_t (*matrix_ptr)[10][20] = &l_matrix;
0 голосов
/ 25 марта 2018

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

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

Вот и сделал ...

0 голосов
/ 29 января 2015

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

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;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...