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

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

static uint8_t l_matrix[10][20];

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

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

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

Ответ 1

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

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] являются совместимыми типами. С++ не имеет понятия совместимых типов, поэтому он будет отклонять этот код, потому что 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), и если вы не можете изменить тип массива, это последний вариант для сохранения:)

Ответ 2

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

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

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

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

Возможно, вы спрашиваете:

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

Лучше всего думать так:

A "распад" из многомерного массива - это не просто адрес, а адрес с данными раздела (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]



Заключение

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

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

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

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

Ответ 3

В

int *ptr= l_matrix[0];

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

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

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

Ответ 4

G'day,

Объявление

static uint8_t l_matrix[10][20];

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

Так что не

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, найденного в строке rowno.

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

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

НТН

веселит,

Ответ 5

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

int l_matrix[10][20];

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

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

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

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

Ответ 6

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

}

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

Ответ 7

Вы можете сделать это следующим образом:

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

Ответ 8

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

static uint8_t l_matrix[10][20];

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

Ответ 9

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

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

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

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

Ответ 10

В общем случае указатель на 2-D массив объявляется следующим образом:

int ***matrix_ptr = &l_matrix;

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

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

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

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

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

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