Dlaczego x [0]! = X [0] [0]! = X [0] [0] [0]?

149

Uczę się trochę C ++ i walczę ze wskazówkami. Rozumiem, że mogę mieć 3 poziomy wskaźników, deklarując:

int *(*x)[5];

więc *xjest to wskaźnik do tablicy 5 elementów, które są wskaźnikami int. Również wiem, że x[0] = *(x+0);, x[1] = *(x+1)i tak dalej ....

A więc, biorąc pod uwagę powyższe oświadczenie, dlaczego x[0] != x[0][0] != x[0][0][0]?

Leo91
źródło
58
x[0], x[0][0]I x[0][0][0]mają różne typy. Nie można ich porównać. Co masz na myśli !=?
bolov
4
@celticminstrel nie są takie same: int **x[5]to tablica 5 elementów. Element jest wskaźnikiem do wskaźnika do int`
bolov
5
@celticminstrel int** x[5]byłaby tablicą pięciu wskaźników wskazujących na wskaźniki, które wskazują na int. int *(*x)[5]jest wskaźnikiem do tablicy pięciu wskaźników, które wskazują na int.
emlai
5
@celticminstrel prawo-lewo regułę , Spirale regułę , C bełkot ↔ angielskich i twój”na swojej drodze, aby stać się programista trzygwiazdkowy :)
bolov
5
@ Leo91: Po pierwsze, masz tutaj dwa poziomy wskaźników, a nie trzy. Po drugie, co to x[0] != x[0][0] != x[0][0][0]znaczy? To nie jest poprawne porównanie w C ++. Nawet jeśli podzielisz go na x[0] != x[0][0]i x[0][0] != x[0][0][0]nadal nie jest ważny. Więc co oznacza twoje pytanie?
AnT

Odpowiedzi:

261

xjest wskaźnikiem do tablicy 5 wskaźników do int.
x[0]jest tablicą 5 wskaźników do int.
x[0][0]jest wskaźnikiem do pliku int.
x[0][0][0]jest int.

                       x[0]
   Pointer to array  +------+                                 x[0][0][0]         
x -----------------> |      |         Pointer to int           +-------+
               0x500 | 0x100| x[0][0]---------------->   0x100 |  10   |
x is a pointer to    |      |                                  +-------+
an array of 5        +------+                        
pointers to int      |      |         Pointer to int                             
               0x504 | 0x222| x[0][1]---------------->   0x222                    
                     |      |                                             
                     +------+                                             
                     |      |         Pointer to int                              
               0x508 | 0x001| x[0][2]---------------->   0x001                    
                     |      |                                             
                     +------+                                             
                     |      |         Pointer to int                              
               0x50C | 0x123| x[0][3]---------------->   0x123                    
                     |      |                                             
                     +------+                                             
                     |      |         Pointer to int                              
               0x510 | 0x000| x[0][4]---------------->   0x000                    
                     |      |                                             
                     +------+                                             

Możesz to zobaczyć

  • x[0]jest tablicą i zostanie przekonwertowana na wskaźnik do swojego pierwszego elementu, gdy zostanie użyta w wyrażeniu (z pewnymi wyjątkami). Dlatego x[0]poda adres swojego pierwszego elementu, x[0][0]którym jest 0x500.
  • x[0][0]zawiera adres, intktóry jest 0x100.
  • x[0][0][0]zawiera intwartość 10.

Więc x[0]jest równa, &x[0][0]a zatem &x[0][0] != x[0][0].
Dlatego x[0] != x[0][0] != x[0][0][0].

haccks
źródło
Ten diagram jest dla mnie nieco zagmatwany: 0x100powinien pojawić się natychmiast po lewej stronie pola zawierającego 10, w ten sam sposób, w jaki 0x500pojawia się po lewej stronie pola. Zamiast być daleko w lewo i poniżej.
MM
@MattMcNabb; Nie sądzę, aby to było mylące, ale zmiany zgodnie z twoją sugestią dla większej jasności.
haccks
4
@haccks - Z przyjemnością :) Powodem, dla którego ten diagram jest świetny, jest to, że nie potrzebujesz nawet wyjaśnień, które podałeś, które są po nim. Sam ten diagram jest oczywisty, ponieważ już odpowiada na pytanie. Poniższy tekst jest po prostu bonusem.
rayryeng
1
Możesz także użyć yed, oprogramowania do tworzenia diagramów. Bardzo mi to pomaga w
uporządkowaniu
@GrijeshChauhan Używam asciiflow do komentarzy do kodu, yeD do prezentacji :)
rpax
133
x[0] != x[0][0] != x[0][0][0]

jest, zgodnie z Twoim własnym postem,

*(x+0) != *(*(x+0)+0) != *(*(*(x+0)+0)+0)`  

co jest uproszczone

*x != **x != ***x

Dlaczego miałoby być równe?
Pierwsza to adres jakiegoś wskaźnika.
Drugi to adres innego wskaźnika.
A trzecia to jakaś intwartość.

deviantfan
źródło
Nie rozumiem ... jeśli x [0], x [0] [0], x [0] [0] [0] jest równoważne z * (x + 0), * (x + 0 + 0) , * (x + 0 + 0 + 0), dlaczego miałyby mieć różne adresy?
Leo91,
41
@ Leo91 x[0][0]jest (x[0])[0], tj . *((*(x+0))+0)Nie *(x+0+0). Dereferencja ma miejsce przed sekundą [0].
emlai
4
@ Leo91 x[0][0] != *(x+0+0)tak jak x[2][3] != x[3][2].
özg
@ Leo91 Drugi komentarz, że „masz to teraz”, został usunięty. Czy czegoś nie rozumiesz (co można lepiej wyjaśnić w odpowiedzi), czy to nie twoja wina? (niektórzy lubią usuwać komentarze bez zbytniej treści informacyjnej)
deviantfan
@deviantfan przepraszam, nie mogę zrozumieć, co masz na myśli. Rozumiem odpowiedzi, a także wiele komentarzy, które pomogły mi wyjaśnić tę koncepcję.
Leo91,
50

Oto układ pamięci twojego wskaźnika:

   +------------------+
x: | address of array |
   +------------------+
            |
            V
            +-----------+-----------+-----------+-----------+-----------+
            | pointer 0 | pointer 1 | pointer 2 | pointer 3 | pointer 4 |
            +-----------+-----------+-----------+-----------+-----------+
                  |
                  V
                  +--------------+
                  | some integer |
                  +--------------+

x[0]daje "adres tablicy",
x[0][0]daje "wskaźnik 0",
x[0][0][0] zwraca "jakąś liczbę całkowitą".

Uważam, że teraz powinno być oczywiste, dlaczego wszystkie są różne.


Powyższe jest wystarczająco bliskie dla podstawowego zrozumienia, dlatego napisałem to tak, jak napisałem. Jednak, jak słusznie zauważa Hacck, pierwsza linia nie jest w 100% dokładna. Oto wszystkie drobne szczegóły:

Z definicji języka C, wartość x[0]to cała tablica wskaźników całkowitych. Jednak tablice są czymś, z czym tak naprawdę nie można nic zrobić w C. Zawsze manipulujesz ich adresem lub elementami, a nigdy całą tablicą jako całością:

  1. Możesz przejść x[0]do sizeofoperatora. Ale to nie jest tak naprawdę użycie wartości, jej wynik zależy tylko od typu.

  2. Możesz wziąć jego adres, który daje wartość x, czyli "adres tablicy" z typem int*(*)[5]. Innymi słowy:&x[0] <=> &*(x + 0) <=> (x + 0) <=> x

  3. We wszystkich innych kontekstach wartość x[0]will zamieni się w wskaźnik do pierwszego elementu tablicy. To znaczy wskaźnik z wartością „adres tablicy” i typem int**. Efekt jest taki sam, jak w przypadku rzutowania xna wskaźnik typu int**.

Ze względu na zanik wskaźnika tablicy w przypadku 3., wszystkie zastosowania x[0]ostatecznie skutkują wskaźnikiem wskazującym początek tablicy wskaźników; wywołanie printf("%p", x[0])wypisze zawartość komórek pamięci oznaczonych jako „adres tablicy”.

cmaster - przywróć monikę
źródło
1
x[0]nie jest adresem tablicy.
haccks
1
@haccks Tak, po literze standardu x[0]nie jest adresem tablicy, jest to sama tablica. Dodałem dogłębne wyjaśnienie tego i dlaczego napisałem, że x[0]jest to „adres tablicy”. Mam nadzieję, że to lubisz.
cmaster
niesamowite wykresy, które doskonale to wyjaśniają!
MK
„Jednak tablice są czymś, z czym nie da się nic zrobić w C.” -> Przykład licznika: printf("%zu\n", sizeof x[0]);podaje rozmiar tablicy, a nie rozmiar wskaźnika.
chux - Przywróć Monikę
@ chux-ReinstateMonica I powiedziałem "Zawsze manipulujesz ich adresem lub ich elementami, nigdy całą tablicą jako całością", a następnie punkt 1 wyliczenia, w którym mówię o efekcie sizeof x[0]...
cmaster - przywróć monikę
18
  • x[0]wyłuskuje najbardziej zewnętrzny wskaźnik ( wskaźnik do tablicy o rozmiarze 5 lub wskaźnik do int) i daje w wyniku tablicę o rozmiarze 5 wskaźnika do int;
  • x[0][0]wyłuskuje najbardziej zewnętrzny wskaźnik i indeksuje tablicę, co daje w wyniku wskaźnik do int;
  • x[0][0][0] wyłuskuje wszystko, co daje konkretną wartość.

Przy okazji, jeśli kiedykolwiek poczujesz się zdezorientowany tym, co znaczą tego rodzaju deklaracje, użyj cdecl .

d125q
źródło
11

Rozważmy krok po kroku wyrażeń x[0], x[0][0]i x[0][0][0].

Jak xokreślono w następujący sposób

int *(*x)[5];

to wyrażenie x[0]jest tablicą typu int *[5]. Weź pod uwagę, że wyrażenie x[0]jest równoważne wyrażeniu *x. To jest dereferencja wskaźnika do tablicy, którą otrzymujemy samą tablicę. Oznaczmy to jako y, czyli mamy deklarację

int * y[5];

Wyrażenie x[0][0]jest równoważne y[0]i ma typ int *. Oznaczmy to jak z, czyli mamy deklarację

int *z;

wyrażenie x[0][0][0]jest równoważne wyrażeniu, y[0][0]które z kolei jest równoważne wyrażeniu z[0]i ma typ int.

Więc mamy

x[0] ma typ int *[5]

x[0][0] ma typ int *

x[0][0][0] ma typ int

Są to więc obiekty różnego typu i przy okazji o różnych rozmiarach.

Biegnij na przykład

std::cout << sizeof( x[0] ) << std::endl;
std::cout << sizeof( x[0][0] ) << std::endl;
std::cout << sizeof( x[0][0][0] ) << std::endl;
Vlad z Moskwy
źródło
10

Po pierwsze muszę to powiedzieć

x [0] = * (x + 0) = * x;

x [0] [0] = * (* (x + 0) + 0) = * * x;

x [0] [0] [0] = * (* (* (x + 0) + 0)) = * * * x;

Więc * x ≠ * * x ≠ * * * x

Z poniższego obrazu wszystko jest jasne.

  x[0][0][0]= 2000

  x[0][0]   = 1001

  x[0]      = 10

wprowadź opis obrazu tutaj

To tylko przykład, gdzie wartość x [0] [0] [0] = 10

a adres x [0] [0] [0] to 1001

ten adres jest przechowywany w x [0] [0] = 1001

a adres x [0] [0] to 2000

i ten adres jest przechowywany w x [0] = 2000

Więc x [0] [0] [0] x [0] [0] x [0]

.

EDYCJE

Program 1:

{
int ***x;
x=(int***)malloc(sizeof(int***));
*x=(int**)malloc(sizeof(int**));
**x=(int*)malloc(sizeof(int*));
***x=10;
printf("%d   %d   %d   %d\n",x,*x,**x,***x);
printf("%d   %d   %d   %d   %d",x[0][0][0],x[0][0],x[0],x,&x);
}

Wynik

142041096 142041112 142041128 10
10 142041128 142041112 142041096 -1076392836

Program 2:

{
int x[1][1][1]={10};
printf("%d   %d   %d   %d \n ",x[0][0][0],x[0][0],x[0],&x);
}

Wynik

10   -1074058436   -1074058436   -1074058436 
apm
źródło
3
Twoja odpowiedź jest myląca. x[0]nie zawiera adresu mrówkowego. To tablica. Rozpadnie się, wskazując na swój pierwszy element.
haccks
Ummm ... co to znaczy? Twoja zmiana jest jak wisienka na torcie dla złej odpowiedzi. To nie ma sensu
haccks
@haccks Jeśli używa samych wskaźników, ta odpowiedź będzie prawidłowa. Będą pewne zmiany w sekcji adresu podczas korzystania z Array
apm
7

Gdybyś miał oglądać tablice z perspektywy świata rzeczywistego, wyglądałoby to następująco:

x[0]to kontener towarowy pełen skrzyń.
x[0][0]to pojedyncza skrzynia pełna pudełek po butach w kontenerze towarowym.
x[0][0][0]to pojedyncze pudełko na buty w skrzyni, wewnątrz kontenera towarowego.

Nawet gdyby było to jedyne pudełko na buty w jedynej skrzyni w kontenerze towarowym, nadal jest to pudełko na buty, a nie kontener towarowy

David Opcjonalny Courtenay
źródło
1
czy nie x[0][0]byłaby pojedyncza skrzynia pełna kawałków papieru z wypisanymi lokalizacjami pudełek po butach?
wchargin
4

W C ++ jest zasada, że: deklaracja zmiennej dokładnie wskazuje sposób użycia zmiennej. Rozważ swoją deklarację:

int *(*x)[5];

który można przepisać jako (dla jaśniejszego):

int *((*x)[5]);

Ze względu na zasadę mamy:

*((*x)[i]) is treated as an int value (i = 0..4)
 (*x)[i] is treated as an int* pointer (i = 0..4)
 *x is treated as an int** pointer
 x is treated as an int*** pointer

W związku z tym:

x[0] is an int** pointer
 x[0][0] = (x[0]) [0] is an int* pointer
 x[0][0][0] = (x[0][0]) [0] is an int value

Możesz więc zrozumieć różnicę.

Nghia Bui
źródło
1
x[0]jest tablicą 5 liczb int, a nie wskaźnikiem. (w większości kontekstów może rozpadać się do postaci wskaźnika, ale tutaj ważne jest rozróżnienie).
MM
OK, ale powinieneś powiedzieć: x [0] to tablica 5 wskaźników int *
Nghia Bui
Aby podać poprawne wyprowadzenie dla @MattMcNabb: *(*x)[5]jest an int, więc (*x)[5]jest an int *, więc *xjest (int *)[5], więc xjest *((int *)[5]). Oznacza to, że xjest wskaźnikiem do tablicy 5 wskaźników do int.
wchargin
2

Próbujesz porównać różne typy według wartości

Jeśli weźmiesz adresy, możesz uzyskać więcej tego, czego oczekujesz

Pamiętaj, że Twoja deklaracja ma znaczenie

 int y [5][5][5];

Pozwoliłoby porównań chcesz, ponieważ y, y[0], y[0][0], y[0][0][0]że mają różne wartości i typów, ale ten sam adres

int **x[5];

nie zajmuje przyległej przestrzeni.

xi x [0]mają ten sam adres, ale x[0][0]i x[0][0][0]od siebie pod różnymi adresami

Glenn Teitelbaum
źródło
2
int *(*x)[5]różni się odint **x[5]
MM
2

Bycie pwskaźnikiem: stosujesz wyłuskiwanie z p[0][0], co jest równoważne z *((*(p+0))+0).

W notacji referencyjnej (&) i dereferencyjnej (*) C:

p == &p[0] == &(&p[0])[0] == &(&(&p[0])[0])[0])

Jest równa:

p == &*(p+0) == &*(&*(p+0))+0 == &*(&*(&*(p+0))+0)+0

Spójrz, & * można refaktoryzować, po prostu usuwając go:

p == p+0 == p+0+0 == p+0+0+0 == (((((p+0)+0)+0)+0)+0)
Luciano
źródło
Co próbujesz pokazać wszystkim po swoim pierwszym zdaniu? Masz po prostu wiele odmian p == p . &(&p[0])[0]różni się odp[0][0]
MM
Facet zapytał, dlaczego „x [0]! = X [0] [0]! = X [0] [0] [0]”, kiedy x jest wskaźnikiem, prawda? Próbowałem mu pokazać, że może zostać uwięziony przez notację dereferencji C (*), kiedy układał [0] w stosie. Jest to więc próba pokazania mu prawidłowej notacji, aby x było równe x [0], odwołując się ponownie do x [0] za pomocą & i tak dalej.
Luciano,
1

Pozostałe odpowiedzi są poprawne, ale żadna z nich nie podkreśla idei, że wszystkie trzy mogą zawierać tę samą wartość , a więc są w pewnym sensie niekompletne.

Powodem, dla którego nie można tego zrozumieć na podstawie innych odpowiedzi, jest to, że wszystkie ilustracje, choć pomocne i zdecydowanie rozsądne w większości przypadków, nie obejmują sytuacji, w której wskaźnik xwskazuje na siebie.

Jest to dość łatwe do skonstruowania, ale wyraźnie trudniejsze do zrozumienia. W poniższym programie zobaczymy, jak możemy wymusić identyczność wszystkich trzech wartości.

UWAGA: Zachowanie w tym programie jest nieokreślone, ale zamieszczam je tutaj wyłącznie jako interesującą demonstrację czegoś, co wskaźniki mogą zrobić, ale nie powinny .

#include <stdio.h>

int main () {
  int *(*x)[5];

  x = (int *(*)[5]) &x;

  printf("%p\n", x[0]);
  printf("%p\n", x[0][0]);
  printf("%p\n", x[0][0][0]);
}

To kompiluje się bez ostrzeżeń w obu C89 i C99, a wynik jest następujący:

$ ./ptrs
0xbfd9198c
0xbfd9198c
0xbfd9198c

Co ciekawe, wszystkie trzy wartości są identyczne. Ale to nie powinno być zaskoczeniem! Najpierw podzielmy program.

Deklarujemy xjako wskaźnik do tablicy 5 elementów, gdzie każdy element jest wskaźnikiem typu do int. Ta deklaracja przydziela 4 bajty na stosie środowiska wykonawczego (lub więcej w zależności od implementacji; na moim komputerze wskaźniki mają 4 bajty), więc xodnosi się do rzeczywistej lokalizacji pamięci. W rodzinie języków C zawartość xjest po prostu śmieciami, czymś pozostałym po poprzednim użyciu lokalizacji, więc xsama nigdzie nie wskazuje - na pewno nie dotyczy przydzielonej przestrzeni.

Więc naturalnie możemy wziąć adres zmiennej xi gdzieś go umieścić, więc to jest dokładnie to, co robimy. Ale pójdziemy dalej i umieścimy to w samym x. Ponieważ &xma inny typ niż x, musimy wykonać rzut, aby nie otrzymywać ostrzeżeń.

Model pamięci wyglądałby mniej więcej tak:

0xbfd9198c
+------------+
| 0xbfd9198c |
+------------+

Zatem 4-bajtowy blok pamięci pod adresem 0xbfd9198czawiera wzorzec bitów odpowiadający wartości szesnastkowej 0xbfd9198c. Wystarczająco proste.

Następnie drukujemy trzy wartości. Pozostałe odpowiedzi wyjaśniają, do czego odnosi się każde wyrażenie, więc związek powinien być teraz jasny.

Widzimy, że wartości są takie same, ale tylko w sensie bardzo niskiego poziomu ... ich wzorce bitowe są identyczne, ale typ danych skojarzonych z każdym wyrażeniem oznacza, że ​​ich interpretowane wartości są różne. Na przykład, jeśli wydrukowaliśmy przy x[0][0][0]użyciu ciągu formatu%d , otrzymalibyśmy ogromną liczbę ujemną, więc „wartości” są w praktyce różne, ale wzór bitowy jest taki sam.

To jest naprawdę proste ... na diagramach strzałki wskazują po prostu ten sam adres pamięci, a nie różne. Jednakże, chociaż byliśmy w stanie wymusić oczekiwany rezultat z niezdefiniowanego zachowania, to po prostu - niezdefiniowany. To nie jest kod produkcyjny, ale po prostu demonstracja w celu zapewnienia kompletności.

W rozsądnej sytuacji użyjesz mallocdo utworzenia tablicy 5 wskaźników int, a następnie do utworzenia ints, które są wskazywane w tej tablicy.malloczawsze zwraca unikalny adres (chyba że brakuje Ci pamięci, w takim przypadku zwraca NULL lub 0), więc nigdy nie będziesz musiał martwić się o takie wskaźniki odwołujące się do siebie.

Mam nadzieję, że to pełna odpowiedź, której szukasz. Nie należy się spodziewać x[0], x[0][0]i x[0][0][0]być równe, ale mogą być, jeśli zmuszony. Jeśli coś przeszło Ci przez głowę, daj mi znać, abym mógł wyjaśnić!

Purag
źródło
Powiedziałbym, że to kolejne dziwne użycie wskaźników, które widziałem kiedykolwiek.
haccks
@haccks Tak, to dość dziwne, ale kiedy to podzielisz, jest tak samo podstawowe, jak inne przykłady. Tak się składa, że ​​wszystkie wzory bitów są takie same.
Purag,
Twój kod powoduje niezdefiniowane zachowanie. x[0]w rzeczywistości nie reprezentuje prawidłowego obiektu właściwego typu
MM
@MattMcNabb to JEST niezdefiniowane i jestem o tym bardzo jasny. Nie zgadzam się co do rodzaju. xjest wskaźnikiem do tablicy, więc możemy użyć []operatora do określenia przesunięcia od tego wskaźnika i wyłuskiwania go. Co tam jest dziwnego? Wynikiem x[0]jest tablica, a C nie narzeka, jeśli drukujesz to przy użyciu, %pponieważ i tak jest to zaimplementowane pod spodem.
Purag,
Kompilowanie tego z -pedanticflagą nie daje żadnych ostrzeżeń, więc C jest w porządku z typami ...
Purag
0

Typ int *(*x)[5]to int* (*)[5]np. Wskaźnik do tablicy 5 wskaźników do liczb całkowitych.

  • xto adres pierwszej tablicy zawierającej 5 wskaźników do int (adres z typem int* (*)[5])
  • x[0]adres pierwszej tablicy 5 wskaźników do int (ten sam adres z typem int* [5]) (przesunięcie adresu x 0*sizeof(int* [5])np. indeks * rozmiar typu wskazywanego na i wyłuskiwanie)
  • x[0][0]jest pierwszym wskaźnikiem do int w tablicy (ten sam adres z typem int*) (przesunięcie adresu x by 0*sizeof(int* [5])i dereference, a następnie by 0*sizeof(int*)i dereference)
  • x[0][0][0]to pierwsza liczba int wskazywana przez wskaźnik do int (przesunięcie adresu x o 0*sizeof(int* [5])i wyłuskiwanie i przesunięcie tego adresu o 0*sizeof(int*)oraz dereferencja i przesunięcie tego adresu o 0*sizeof(int)i dereferencja)

Typ int *(*y)[5][5][5]to int* (*)[5][5][5]np. Wskaźnik do tablicy 3d 5x5x5 wskaźników do ints

  • x to adres pierwszej trójwymiarowej tablicy wskaźników 5x5x5 do liczb całkowitych z typem int*(*)[5][5][5]
  • x[0]jest adresem pierwszej trójwymiarowej tablicy wskaźników 5x5x5 do ints (przesunięcie adresu x by 0*sizeof(int* [5][5][5])i dereference)
  • x[0][0]jest adresem pierwszej tablicy 2D składającej się z 5x5 wskaźników do liczb całkowitych (przesunięcie adresu x o 0*sizeof(int* [5][5][5])i dereferencja, a następnie przesunięcie tego adresu o 0*sizeof(int* [5][5]))
  • x[0][0][0]jest adresem pierwszej tablicy 5 wskaźników do liczb całkowitych (przesunięcie adresu x o 0*sizeof(int* [5][5][5])i wyłuskiwanie oraz przesunięcie tego adresu o 0*sizeof(int* [5][5])i przesunięcie tego adresu o 0*sizeof(int* [5]))
  • x[0][0][0][0]jest pierwszym wskaźnikiem do int w tablicy (przesunięcie adresu x o 0*sizeof(int* [5][5][5])i dereferencji oraz przesunięcie tego adresu o 0*sizeof(int* [5][5])i przesunięcie tego adresu o 0*sizeof(int* [5])i przesunięcie tego adresu o 0*sizeof(int*)i wyłuskiwanie)
  • x[0][0][0][0][0]to pierwsza liczba int wskazywana przez wskaźnik do int (przesunięcie adresu x o 0*sizeof(int* [5][5][5])i wyłuskiwanie i przesunięcie tego adresu o 0*sizeof(int* [5][5])oraz przesunięcie tego adresu o 0*sizeof(int* [5])i przesunięcie tego adresu o 0*sizeof(int*)oraz dereferencja i przesunięcie tego adresu o 0*sizeof(int)i dereferencja)

Jeśli chodzi o rozpad tablicy:

void function (int* x[5][5][5]){
  printf("%p",&x[0][0][0][0]); //get the address of the first int pointed to by the 3d array
}

Jest to równoważne z przejściem int* x[][5][5]lub int* (*x)[5][5]np. Wszystkie rozpadają się na to drugie. Dlatego nie otrzymasz ostrzeżenia kompilatora dotyczącego użycia x[6][0][0]w funkcji, ale otrzymasz, x[0][6][0]ponieważ informacje o rozmiarze są zachowywane

void function (int* (*x)[5][5][5]){
  printf("%p",&x[0][0][0][0][0]); //get the address of the first int pointed to by the 3d array
}
  • x[0] to adres pierwszej trójwymiarowej tablicy wskaźników 5x5x5 do liczb całkowitych
  • x[0][0] jest adresem pierwszej tablicy 2d zawierającej wskaźniki 5x5 do liczb całkowitych
  • x[0][0][0] jest adresem pierwszej tablicy 5 wskaźników do liczb całkowitych
  • x[0][0][0][0] jest pierwszym wskaźnikiem do int w tablicy
  • x[0][0][0][0][0] jest pierwszym int wskazywanym przez wskaźnik do int

W ostatnim przykładzie semantycznie jest znacznie jaśniejsze użycie *(*x)[0][0][0]niżx[0][0][0][0][0] , ponieważ pierwszy i ostatni [0]tutaj są interpretowane jako wyłuskiwanie wskaźnika, a nie indeks do tablicy wielowymiarowej, ze względu na typ. Są jednak identyczne, ponieważ (*x) == x[0]niezależnie od semantyki. Możesz również użyć *****x, które wyglądałoby tak, jakbyś wyłuskiwał wskaźnik 5 razy, ale w rzeczywistości jest interpretowany dokładnie tak samo: przesunięcie, wyłuskiwanie, wyłuskiwanie, 2 przesunięcia do tablicy i wyłuskiwanie, wyłącznie ze względu na typ do której stosujesz operację.

Zasadniczo, gdy ty [0]lub* a *do typu innego niż tablica, jest to przesunięcie i dereferencja ze względu na kolejność pierwszeństwa *(a + 0).

Kiedy ty [0] lub *a *do typu tablicy, jest to przesunięcie, a następnie idempotentne wyłuskiwanie (wyłuskiwanie jest rozwiązywane przez kompilator w celu uzyskania tego samego adresu - jest to operacja idempotentna).

Kiedy ty [0] lub *typ z typem tablicy 1d, jest to przesunięcie, a następnie dereferencja

Jeśli ty [0] lub **typ tablicy 2d, jest to tylko przesunięcie, tj. Przesunięcie, a następnie idempotentna dereferencja.

Jeśli ty [0][0][0] lub ***typ tablicy 3d, to jest to offset + idempotent dereference, a następnie offset + idempotent dereference, a następnie offset + idempotent dereference, a następnie dereference. Prawdziwe wyłuskiwanie występuje tylko wtedy, gdy typ tablicy jest całkowicie pozbawiony.

Na przykład int* (*x)[1][2][3] typ jest rozpakowywany w kolejności.

  • x ma typ int* (*)[1][2][3]
  • *x ma typ int* [1][2][3] (offset 0 + idempotentne wyłuskiwanie)
  • **x ma typ int* [2][3] (offset 0 + idempotentne wyłuskiwanie)
  • ***x ma typ int* [3] (offset 0 + idempotentne wyłuskiwanie)
  • ****x ma typ int* (przesunięcie 0 + dereferencja)
  • *****xma typ int(przesunięcie 0 + dereferencja)
Lewis Kelsey
źródło