Створіть вказівник на двовимірний масив


119

Мені потрібен вказівник на статичний двовимірний масив. Як це робиться?

static uint8_t l_matrix[10][20];

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

Я отримую всілякі помилки, такі як:

  • попередження: призначення від несумісного типу вказівника
  • підписане значення не є ні масивом, ні вказівником
  • помилка: недійсне використання гнучких елементів масиву

Прочитайте stackoverflow.com/questions/423823/… це може вам допомогти
Йоханнес Шауб - ліб

1
@ JohannesSchaub-litb Цього більше не існує. (Як я переглядаю це знову ...? Я знаю, що учасники з низькою кількістю представників можуть переглядати це, але я забув, як ...)
Mateen Ulhaq

@muntoo: Ось його копія: gist.github.com/sharth/ede13c0502d5dd8d45bd
Білл Лінч

Відповіді:


139

Тут ви хочете зробити вказівник на перший елемент масиву

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 ), і якщо ви не можете змінити тип масиву, це останній варіант, щоб врятувати вас :)


+1 - приємна інформація про розрив int (*) [] [20] - не можу цього зробити на C ++
Faisal Vali

@litb, вибачте, але це неправильно, оскільки ваше рішення не передбачає виділення місця для зберігання масиву.
Роб Уеллс

2
@Rob, я не зовсім тебе розумію. зберігання у всіх цих випадках забезпечується самим масивом l_matix. Покажчики на них беруть сховище з того місця, де вони оголошені в і як (стек, статичний сегмент даних, ...).
Йоханнес Шауб - ліб

Цікаво, навіщо нам потрібна "&" адреса l_matrix?
електро

1
@Sohaib - ні, це створює лише один покажчик. Можливо, ви його плутали uint8_t *d[20], що створює масив з 3 покажчиків на uint8_t, але це не спрацювало б у цьому випадку.
Пало

27

Щоб повністю зрозуміти це, ви повинні зрозуміти такі поняття:

Масиви - не покажчики!

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

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

Ви можете запитати:

Чому це важливо, якщо масив багатовимірний щодо створення вказівника на нього?

Найкраще мислити так:

"Розпад" від багатовимірного масиву - це не просто адреса, а адреса з даними розділів (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]



Висновок

Розглянемо:

  • Масиви розпадаються на адреси, якщо вони не мають іншої мети у використаному контексті
  • Багатовимірні масиви - це просто масиви масивів - отже, "розкладена" адреса нестиме тягар "у мене підмірності"
  • Дані про розмір не можуть існувати в покажчику, якщо ви не надасте їх .

Якщо коротко: багатовимірні масиви розпадаються на адреси, що несуть можливість розуміти їхній вміст.


1
Перша частина відповіді чудова, а друга - ні. Це невірно: int *p1 = &(a[0]); // RIGHT !насправді це ідентичноint *p1 = a;
2501

@ 2501 Дякую, що помітили цю помилку, я її виправив. Я не можу сказати напевно, чому приклад, який визначає це "правило", також спростував його. Варто пересвідчитись у тому, що те, що два об'єкти можна інтерпретувати як покажчики та отримувати однакове значення, не означає, що вони несуть однакове значення.
Супер Кіт

7

В

int *ptr= l_matrix[0];

Ви можете отримати доступ, як

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

зрештою, двовимірні масиви також зберігаються як 1-d.


5

День,

Декларація

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.

HTH

ура,


1
uint8_t (* matrix_ptr) [] [20] << перші дужки повинні бути залишені, правильними є uint8_t (* matrix_ptr) [20]
Aconcagua,

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 елементів, що знову ж таки означає, що розмір не фіксований.
рум'яна

1
@bluss питання стосувалося вказівника, тому я не бачу, як відповідь за допомогою вказівника (зазначаючи за посиланням ) вводить в оману. Масив має фіксований розмір з точки зору функції, оскільки доступ до елементів за межами цих меж не визначений.
Корнель

Я не думаю, що доступ після 10 не визначений, я не бачу нічого, що це свідчить.
рум'янець

Ця відповідь, схоже static, говорить про те, що без ключового слова масив не був би переданий посиланням, що не відповідає дійсності. Масиви передаються посиланням у будь-якому випадку. В оригінальному запитанні було задано інший випадок використання - доступ до елементів двовимірного масиву з використанням додаткового вказівника в межах тієї ж функції / простору імен.
Пало

4

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

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
Таке рішення має недолік ніколи не з'ясувати правильний спосіб зробити це.
Крейг МакКуїн

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;

Привіт. Це питання позначене тегом c, тому відповідь має бути тією ж мовою. Будь ласка, дотримуйтесь тегів.
2501,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.