Utwórz wskaźnik do dwuwymiarowej tablicy


120

Potrzebuję wskaźnika do statycznej dwuwymiarowej tablicy. Jak to się robi?

static uint8_t l_matrix[10][20];

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

Otrzymuję różnego rodzaju błędy, takie jak:

  • ostrzeżenie: przypisanie z niezgodnego typu wskaźnika
  • wartość indeksowana nie jest tablicą ani wskaźnikiem
  • błąd: nieprawidłowe użycie elastycznego elementu tablicy


1
@ JohannesSchaub-litb To już nie istnieje. (Jak mogę to zobaczyć ponownie ...? Wiem, że członkowie o niskiej reputacji mogą to zobaczyć, ale zapomniałem, jak ...)
Mateen Ulhaq

Odpowiedzi:


140

Tutaj chcesz zrobić wskaźnik do pierwszego elementu tablicy

uint8_t (*matrix_ptr)[20] = l_matrix;

Z typedef wygląda to bardziej przejrzysto

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

Wtedy znów możesz cieszyć się życiem :)

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

Uważaj na świat wskaźników / tablic w C, wokół tego jest dużo zamieszania.


Edytować

Przejrzyj niektóre inne odpowiedzi tutaj, ponieważ pola komentarzy są zbyt krótkie, aby je tam zrobić. Zaproponowano wiele alternatyw, ale nie pokazano, jak się zachowują. Oto jak to robią

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

Jeśli naprawisz błąd i dodasz operator address-of, &jak w poniższym fragmencie

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

Następnie ten tworzy wskaźnik do niekompletnego typu tablicy elementów typu array o wartości 20 uint8_t. Ponieważ wskaźnik wskazuje tablicę tablic, musisz uzyskać do niej dostęp za pomocą

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

A ponieważ jest to wskaźnik do niekompletnej tablicy, nie można tego zrobić jako skrótu

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

Ponieważ indeksowanie wymaga znajomości rozmiaru typu elementu (indeksowanie oznacza dodanie liczby całkowitej do wskaźnika, więc nie będzie działać z niekompletnymi typami). Zauważ, że działa to tylko w C, ponieważ T[]i T[N]są kompatybilne typy. C ++ nie ma koncepcji kompatybilnych typów , więc odrzuci ten kod, ponieważ T[]i T[10]są różne typy.


Poniższa alternatywa w ogóle nie działa, ponieważ typ elementu tablicy, gdy widzisz ją jako tablicę jednowymiarową, nie jest uint8_t, aleuint8_t[20]

uint8_t *matrix_ptr = l_matrix; // fail

Oto dobra alternatywa

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

Masz do niego dostęp za pomocą

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

Ma tę zaletę, że zachowuje rozmiar wymiaru zewnętrznego. Więc możesz nałożyć na to sizeof

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

Jest jeszcze jedna odpowiedź, która wykorzystuje fakt, że elementy w tablicy są przechowywane w sposób ciągły

uint8_t *matrix_ptr = l_matrix[0];

Otóż, to formalnie pozwala tylko na dostęp do elementów pierwszego elementu dwuwymiarowej tablicy. Oznacza to, że spełniony jest następujący warunek

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

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

Zauważysz, że prawdopodobnie działa 10*20-1, ale jeśli użyjesz analizy aliasów i innych agresywnych optymalizacji, jakiś kompilator może przyjąć założenie, które może złamać ten kod. Powiedziawszy to, nigdy nie spotkałem kompilatora, który zawodzi (ale z drugiej strony nie użyłem tej techniki w prawdziwym kodzie), a nawet C FAQ zawiera tę technikę (z ostrzeżeniem o jej UB'ness ), a jeśli nie możesz zmienić typu tablicy, jest to ostatnia opcja, aby cię uratować :)


+1 - niezłe informacje o podziale int (*) [] [20] - nie mogę tego zrobić w C ++
Faisal Vali

@litb, przepraszam, ale to źle, ponieważ twoje rozwiązanie nie zapewnia alokacji pamięci dla macierzy.
Rob Wells

2
@Rob, nie całkiem cię rozumiem. pamięć we wszystkich tych przypadkach jest zapewniana przez samą tablicę l_matix. Wskaźniki do nich zajmują pamięć z dowolnego miejsca, w którym są zadeklarowane i jako (stos, statyczny segment danych, ...).
Johannes Schaub - litb

Ciekawe, dlaczego potrzebujemy adresu „&” l_matrix?
electro

1
@Sohaib - nie, to tworzy tylko jeden wskaźnik. Być może uint8_t *d[20]pomyliłeś to z , co tworzy tablicę 3 wskaźników do uint8_t, ale to nie zadziała w tym przypadku.
Palo

28

Aby w pełni to zrozumieć, musisz zrozumieć następujące pojęcia:

Tablice nie są wskaźnikami!

Przede wszystkim (i zostało to wystarczająco nauczone) tablice nie są wskaźnikami . Zamiast tego w większości zastosowań „rozpadają się” na adres do ich pierwszego elementu, który można przypisać do wskaźnika:

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

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

Zakładam, że działa to w ten sposób, że można uzyskać dostęp do zawartości tablicy bez kopiowania ich wszystkich. To tylko zachowanie typów tablicowych i nie ma na celu sugerowania, że ​​są one tym samym.



Tablice wielowymiarowe

Tablice wielowymiarowe to tylko sposób na „partycjonowanie” pamięci w sposób, który kompilator / maszyna może zrozumieć i na którym może operować.

Na przykład int a[4][3][5]= tablica zawierająca 4 * 3 * 5 (60) „fragmentów” pamięci o rozmiarze całkowitoliczbowym.

Zaletą używania int a[4][3][5]vs zwykłego int b[60]jest to, że są teraz „podzielone na partycje” (łatwiej jest pracować z ich „fragmentami”, jeśli to konieczne), a program może teraz wykonywać sprawdzanie powiązań.

W rzeczywistości int a[4][3][5]jest przechowywany dokładnie tak , jak int b[60]w pamięci - jedyną różnicą jest to, że program teraz zarządza nim tak, jakby były oddzielnymi jednostkami o określonych rozmiarach (w szczególności cztery grupy po trzy grupy po pięć).

Pamiętaj: oba int a[4][3][5]i int b[60]są takie same w pamięci, a jedyna różnica polega na tym, jak są obsługiwane przez aplikację / kompilator

{
  {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}
}

Z tego widać wyraźnie, że każda „partycja” jest po prostu tablicą, którą program śledzi.



Składnia

Teraz tablice różnią się składnią od wskaźników . W szczególności oznacza to, że kompilator / maszyna będzie traktować je inaczej. Może się to wydawać oczywiste, ale spójrz na to:

int a[3][3];

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

Powyższy przykład wyświetla ten sam adres pamięci dwukrotnie, na przykład:

0x7eb5a3b4 0x7eb5a3b4

Jednak tylko jeden może być przypisany do wskaźnika tak bezpośrednio :

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

int *p2 = a; // WRONG !

Dlaczego nie można a go przypisać do wskaźnika, ale a[0] można?

Jest to po prostu konsekwencja wielowymiarowych tablic i wyjaśnię, dlaczego:

Na poziomie „ a” nadal widzimy, że mamy inny „wymiar”, na który czekamy. Jednak na poziomie „ a[0]” jesteśmy już w najwyższym wymiarze, więc jeśli chodzi o program, patrzymy tylko na normalną tablicę.

Możesz zapytać:

Dlaczego ma to znaczenie, jeśli tablica jest wielowymiarowa, jeśli chodzi o tworzenie wskaźnika?

Najlepiej myśleć w ten sposób:

`` Zanik '' z wielowymiarowej tablicy to nie tylko adres, ale adres z danymi partycji (AKA nadal rozumie, że jego podstawowe dane składają się z innych tablic), który składa się z granic wyznaczonych przez tablicę poza pierwszym wymiarem.

Ta logika `` partycji '' nie może istnieć we wskaźniku, chyba że ją określimy:

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

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

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

W przeciwnym razie znaczenie właściwości sortowania tablicy zostanie utracone.

Zwróć również uwagę na użycie nawiasów wokół *p: int (*p)[5][95][8]- To jest określenie, że tworzymy wskaźnik z tymi granicami, a nie tablicą wskaźników z tymi granicami:int *p[5][95][8]



Wniosek

Przejrzyjmy:

  • Tablice rozpadają się na adresy, jeśli nie mają innego celu w używanym kontekście
  • Tablice wielowymiarowe są po prostu tablicami tablic - w związku z tym „zepsuty” adres będzie dźwigał ciężar „Mam wymiary podrzędne”
  • Dane wymiarowe nie mogą istnieć we wskaźniku, chyba że im je podasz .

W skrócie: tablice wielowymiarowe rozpadają się na adresy, które niosą zdolność zrozumienia ich zawartości.


1
Pierwsza część odpowiedzi jest świetna, ale druga nie. To nie jest poprawne: int *p1 = &(a[0]); // RIGHT !w rzeczywistości jest identyczne zint *p1 = a;
2501

@ 2501 Dziękuję za zauważenie tego błędu, poprawiłem go. Nie mogę z całą pewnością powiedzieć, dlaczego przykład definiujący tę „regułę” również jej przeciwstawiał. Warto powtórzyć, że tylko dlatego, że dwa byty można interpretować jako wskaźniki i dają tę samą wartość, nie oznacza, że ​​mają to samo znaczenie.
Super Cat

7

W

int *ptr= l_matrix[0];

możesz uzyskać dostęp jak

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

w końcu wszystkie dwuwymiarowe tablice są również przechowywane jako 1-d.


5

Dzień dobry,

Deklaracja

static uint8_t l_matrix[10][20];

zarezerwował miejsce na 10 rzędów po 20 jednostek unit8_t, tj. 200 lokalizacji o rozmiarze uint8_t, przy czym każdy element został znaleziony przez obliczenie 20 x wiersz + kolumna.

Tak nie jest

uint8_t (*matrix_ptr)[20] = l_matrix;

podać to, czego potrzebujesz i wskazać element zerowy kolumny w pierwszym wierszu tablicy?

Edycja: Rozważając to nieco dalej, czy nazwa tablicy nie jest z definicji wskaźnikiem? Oznacza to, że nazwa tablicy jest synonimem położenia pierwszego elementu, tj. L_matrix [0] [0]?

Edit2: Jak wspomnieli inni, miejsce na komentarze jest trochę za małe, aby można było je dalej omawiać. Tak czy siak:

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

nie zapewnia alokacji pamięci dla danej macierzy.

Jak wspomniano powyżej i zgodnie z definicją zawartą w normie, oświadczenie:

static uint8_t l_matrix[10][20];

odłożył 200 kolejnych lokalizacji typu uint8_t.

Odwołując się do l_matrix przy użyciu instrukcji w postaci:

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

poda zawartość colno'tego elementu znalezionego w wierszu rowno.

Wszystkie operacje na wskaźniku automatycznie uwzględniają rozmiar wskazywanego obiektu. - K&R sekcja 5.4, s.103

Dzieje się tak również w przypadku, gdy podczas przechowywania obiektu jest zaangażowane jakiekolwiek dopełnienie lub przesunięcie wyrównania bajtów. Kompilator automatycznie dostosuje się do nich. Z definicji standardu C ANSI.

HTH

Twoje zdrowie,


1
uint8_t (* matrix_ptr) [] [20] << pierwsze nawiasy należy pominąć, poprawna to uint8_t (* matrix_ptr) [20]
Aconcagua

5

W C99 (wspieranym przez clang i gcc) istnieje niejasna składnia do przekazywania wielowymiarowych tablic do funkcji przez odniesienie:

int l_matrix[10][20];

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

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

W przeciwieństwie do zwykłego wskaźnika, ta wskazówka dotyczy rozmiaru tablicy, teoretycznie pozwalając kompilatorowi na ostrzeżenie o przekazywaniu zbyt małej tablicy i zauważeniu oczywistego dostępu poza granicami.

Niestety, to nie naprawia, sizeof()a kompilatory nie wydają się jeszcze używać tych informacji, więc pozostaje ciekawostką.


1
Ta odpowiedź jest myląca: to nie sprawia, że ​​argument jest tablicą o stałym rozmiarze, nadal jest wskaźnikiem. static 10to pewnego rodzaju gwarancja, że obecnych jest co najmniej 10 elementów, co znowu oznacza, że ​​rozmiar nie jest ustalony.
bluss

1
@bluss pytanie dotyczyło wskaźnika, więc nie rozumiem, w jaki sposób udzielanie odpowiedzi za pomocą wskaźnika (notowanie przez odniesienie ) jest mylące. Tablica ma stały rozmiar z punktu widzenia funkcji, ponieważ dostęp do elementów poza tymi granicami jest niezdefiniowany.
Kornel

Nie sądzę, aby dostęp poza 10 był nieokreślony, nie widzę nic, co by na to wskazywało.
bluss

Ta odpowiedź wydaje się sugerować, że bez słowa kluczowego statictablica nie byłaby przekazywana przez odniesienie, co nie jest prawdą. Tablice i tak są przekazywane przez odwołanie. Pierwotne pytanie dotyczyło innego przypadku użycia - dostępu do elementów tablicy 2D za pomocą dodatkowego wskaźnika w ramach tej samej funkcji / przestrzeni nazw.
Palo

4

Zawsze możesz uniknąć majstrowania przy kompilatorze, deklarując tablicę jako liniową i wykonując (row, col) w celu obliczenia indeksu tablicy samodzielnie.

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

}

i tak by to zrobił kompilator.


1
To właśnie robi kompilator C. C tak naprawdę nie ma żadnego prawdziwego pojęcia "tablicy" - notacja [] jest tylko cukrem składniowym dla arytmetyki wskaźnikowej
Ken Keenan,

7
Wadą tego rozwiązania jest to, że nigdy nie można znaleźć właściwego sposobu, aby to zrobić.
Craig McQueen

2

Podstawowa składnia wskaźnika inicjalizującego wskazującego na tablicę wielowymiarową to

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

Podstawowa składnia jej wywołania to

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

Oto przykład:

#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;
}

Wynik to:

Subham
Messi

Zadziałało...


1

Możesz to zrobić w ten sposób:

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

1
czy to nie zajmuje 10 * 20 bajtów pamięci RAM? (jestem na mikrokontrolerze)
Dill

Zajmie 4 bajty lub dowolny rozmiar, w którym wskaźnik jest duży w twoim pudełku. Ale pamiętaj, że jeśli masz ten, musisz indeksować za pomocą matrix_ptr [0] [x] [y] lub (* matrix_ptr) [x] [y]. Jest to bezpośrednia i słowo po słowie interpretacja „wskaźnika do dwuwymiarowej tablicy”: p
Johannes Schaub - litb

Dzięki, zapomniałem wspomnieć, jak uzyskać do niego dostęp. Nie ma sensu edytować mojej odpowiedzi, ponieważ wykonałeś świetną robotę ze swoją odpowiedzią :)
Nick Dandoulakis,

Czy to zajmuje 10 * 20 bajtów pamięci RAM, czy nie?
Danijel

@Danijel, ponieważ jest to wskaźnik do dwuwymiarowej tablicy, zajmie tylko 4 bajty lub jakikolwiek inny rozmiar wskaźnika w twoim pudełku, tj. 16-bitowy, 32-bitowy, 64-bitowy itd.
Nick Dandoulakis

1

Chcesz mieć wskaźnik do pierwszego elementu, więc;

static uint8_t l_matrix[10][20];

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

0

Możesz również dodać przesunięcie, jeśli chcesz użyć indeksów ujemnych:

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

Jeśli Twój kompilator wyświetla błąd lub ostrzeżenie, możesz użyć:

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

Dzień dobry. To pytanie jest oznaczone tagiem c, więc odpowiedź powinna być w tym samym języku. Proszę zwrócić uwagę na tagi.
2501
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.