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 *x
jest 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]
?
x[0]
,x[0][0]
Ix[0][0][0]
mają różne typy. Nie można ich porównać. Co masz na myśli!=
?int **x[5]
to tablica 5 elementów. Element jest wskaźnikiem do wskaźnika do int`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.x[0] != x[0][0] != x[0][0][0]
znaczy? To nie jest poprawne porównanie w C ++. Nawet jeśli podzielisz go nax[0] != x[0][0]
ix[0][0] != x[0][0][0]
nadal nie jest ważny. Więc co oznacza twoje pytanie?Odpowiedzi:
x
jest wskaźnikiem do tablicy 5 wskaźników doint
.x[0]
jest tablicą 5 wskaźników doint
.x[0][0]
jest wskaźnikiem do plikuint
.x[0][0][0]
jestint
.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). Dlategox[0]
poda adres swojego pierwszego elementu,x[0][0]
którym jest0x500
.x[0][0]
zawiera adres,int
który jest0x100
.x[0][0][0]
zawieraint
wartość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]
.źródło
0x100
powinien pojawić się natychmiast po lewej stronie pola zawierającego10
, w ten sam sposób, w jaki0x500
pojawia się po lewej stronie pola. Zamiast być daleko w lewo i poniżej.jest, zgodnie z Twoim własnym postem,
co jest uproszczone
Dlaczego miałoby być równe?
Pierwsza to adres jakiegoś wskaźnika.
Drugi to adres innego wskaźnika.
A trzecia to jakaś
int
wartość.źródło
x[0][0]
jest(x[0])[0]
, tj .*((*(x+0))+0)
Nie*(x+0+0)
. Dereferencja ma miejsce przed sekundą[0]
.x[0][0] != *(x+0+0)
tak jakx[2][3] != x[3][2]
.Oto układ pamięci twojego wskaźnika:
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ą:Możesz przejść
x[0]
dosizeof
operatora. Ale to nie jest tak naprawdę użycie wartości, jej wynik zależy tylko od typu.Możesz wziąć jego adres, który daje wartość
x
, czyli "adres tablicy" z typemint*(*)[5]
. Innymi słowy:&x[0] <=> &*(x + 0) <=> (x + 0) <=> x
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 typemint**
. Efekt jest taki sam, jak w przypadku rzutowaniax
na wskaźnik typuint**
.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łanieprintf("%p", x[0])
wypisze zawartość komórek pamięci oznaczonych jako „adres tablicy”.źródło
x[0]
nie jest adresem tablicy.x[0]
nie jest adresem tablicy, jest to sama tablica. Dodałem dogłębne wyjaśnienie tego i dlaczego napisałem, żex[0]
jest to „adres tablicy”. Mam nadzieję, że to lubisz.printf("%zu\n", sizeof x[0]);
podaje rozmiar tablicy, a nie rozmiar wskaźnika.sizeof x[0]
...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 doint
;x[0][0]
wyłuskuje najbardziej zewnętrzny wskaźnik i indeksuje tablicę, co daje w wyniku wskaźnik doint
;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 .
źródło
Rozważmy krok po kroku wyrażeń
x[0]
,x[0][0]
ix[0][0][0]
.Jak
x
określono w następujący sposóbto wyrażenie
x[0]
jest tablicą typuint *[5]
. Weź pod uwagę, że wyrażeniex[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ęWyrażenie
x[0][0]
jest równoważney[0]
i ma typint *
. Oznaczmy to jak z, czyli mamy deklaracjęwyrażenie
x[0][0][0]
jest równoważne wyrażeniu,y[0][0]
które z kolei jest równoważne wyrażeniuz[0]
i ma typint
.Więc mamy
x[0]
ma typint *[5]
x[0][0]
ma typint *
x[0][0][0]
ma typint
Są to więc obiekty różnego typu i przy okazji o różnych rozmiarach.
Biegnij na przykład
źródło
Po pierwsze muszę to powiedzieć
Z poniższego obrazu wszystko jest jasne.
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:
Wynik
Program 2:
Wynik
źródło
x[0]
nie zawiera adresu mrówkowego. To tablica. Rozpadnie się, wskazując na swój pierwszy element.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
źródło
x[0][0]
byłaby pojedyncza skrzynia pełna kawałków papieru z wypisanymi lokalizacjami pudełek po butach?W C ++ jest zasada, że: deklaracja zmiennej dokładnie wskazuje sposób użycia zmiennej. Rozważ swoją deklarację:
który można przepisać jako (dla jaśniejszego):
Ze względu na zasadę mamy:
W związku z tym:
Możesz więc zrozumieć różnicę.
źródło
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).*(*x)[5]
jest anint
, więc(*x)[5]
jest anint *
, więc*x
jest(int *)[5]
, więcx
jest*((int *)[5])
. Oznacza to, żex
jest wskaźnikiem do tablicy 5 wskaźników doint
.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
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 adresnie zajmuje przyległej przestrzeni.
x
ix [0]
mają ten sam adres, alex[0][0]
ix[0][0][0]
od siebie pod różnymi adresamiźródło
int *(*x)[5]
różni się odint **x[5]
Bycie
p
wskaźnikiem: stosujesz wyłuskiwanie zp[0][0]
, co jest równoważne z*((*(p+0))+0)
.W notacji referencyjnej (&) i dereferencyjnej (*) C:
Jest równa:
Spójrz, & * można refaktoryzować, po prostu usuwając go:
źródło
p == p
.&(&p[0])[0]
różni się odp[0][0]
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
x
wskazuje 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 .
To kompiluje się bez ostrzeżeń w obu C89 i C99, a wynik jest następujący:
Co ciekawe, wszystkie trzy wartości są identyczne. Ale to nie powinno być zaskoczeniem! Najpierw podzielmy program.
Deklarujemy
x
jako 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ęcx
odnosi się do rzeczywistej lokalizacji pamięci. W rodzinie języków C zawartośćx
jest po prostu śmieciami, czymś pozostałym po poprzednim użyciu lokalizacji, więcx
sama nigdzie nie wskazuje - na pewno nie dotyczy przydzielonej przestrzeni.Więc naturalnie możemy wziąć adres zmiennej
x
i 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ż&x
ma inny typ niżx
, musimy wykonać rzut, aby nie otrzymywać ostrzeżeń.Model pamięci wyglądałby mniej więcej tak:
Zatem 4-bajtowy blok pamięci pod adresem
0xbfd9198c
zawiera wzorzec bitów odpowiadający wartości szesnastkowej0xbfd9198c
. 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
malloc
do utworzenia tablicy 5 wskaźników int, a następnie do utworzenia ints, które są wskazywane w tej tablicy.malloc
zawsze 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]
ix[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ć!źródło
x[0]
w rzeczywistości nie reprezentuje prawidłowego obiektu właściwego typux
jest 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? Wynikiemx[0]
jest tablica, a C nie narzeka, jeśli drukujesz to przy użyciu,%p
ponieważ i tak jest to zaimplementowane pod spodem.-pedantic
flagą nie daje żadnych ostrzeżeń, więc C jest w porządku z typami ...Typ
int *(*x)[5]
toint* (*)[5]
np. Wskaźnik do tablicy 5 wskaźników do liczb całkowitych.x
to adres pierwszej tablicy zawierającej 5 wskaźników do int (adres z typemint* (*)[5]
)x[0]
adres pierwszej tablicy 5 wskaźników do int (ten sam adres z typemint* [5]
) (przesunięcie adresu x0*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 typemint*
) (przesunięcie adresu x by0*sizeof(int* [5])
i dereference, a następnie by0*sizeof(int*)
i dereference)x[0][0][0]
to pierwsza liczba int wskazywana przez wskaźnik do int (przesunięcie adresu x o0*sizeof(int* [5])
i wyłuskiwanie i przesunięcie tego adresu o0*sizeof(int*)
oraz dereferencja i przesunięcie tego adresu o0*sizeof(int)
i dereferencja)Typ
int *(*y)[5][5][5]
toint* (*)[5][5][5]
np. Wskaźnik do tablicy 3d 5x5x5 wskaźników do intsx
to adres pierwszej trójwymiarowej tablicy wskaźników 5x5x5 do liczb całkowitych z typemint*(*)[5][5][5]
x[0]
jest adresem pierwszej trójwymiarowej tablicy wskaźników 5x5x5 do ints (przesunięcie adresu x by0*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 o0*sizeof(int* [5][5][5])
i dereferencja, a następnie przesunięcie tego adresu o0*sizeof(int* [5][5])
)x[0][0][0]
jest adresem pierwszej tablicy 5 wskaźników do liczb całkowitych (przesunięcie adresu x o0*sizeof(int* [5][5][5])
i wyłuskiwanie oraz przesunięcie tego adresu o0*sizeof(int* [5][5])
i przesunięcie tego adresu o0*sizeof(int* [5])
)x[0][0][0][0]
jest pierwszym wskaźnikiem do int w tablicy (przesunięcie adresu x o0*sizeof(int* [5][5][5])
i dereferencji oraz przesunięcie tego adresu o0*sizeof(int* [5][5])
i przesunięcie tego adresu o0*sizeof(int* [5])
i przesunięcie tego adresu o0*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 o0*sizeof(int* [5][5][5])
i wyłuskiwanie i przesunięcie tego adresu o0*sizeof(int* [5][5])
oraz przesunięcie tego adresu o0*sizeof(int* [5])
i przesunięcie tego adresu o0*sizeof(int*)
oraz dereferencja i przesunięcie tego adresu o0*sizeof(int)
i dereferencja)Jeśli chodzi o rozpad tablicy:
Jest to równoważne z przejściem
int* x[][5][5]
lubint* (*x)[5][5]
np. Wszystkie rozpadają się na to drugie. Dlatego nie otrzymasz ostrzeżenia kompilatora dotyczącego użyciax[6][0][0]
w funkcji, ale otrzymasz,x[0][6][0]
ponieważ informacje o rozmiarze są zachowywanex[0]
to adres pierwszej trójwymiarowej tablicy wskaźników 5x5x5 do liczb całkowitychx[0][0]
jest adresem pierwszej tablicy 2d zawierającej wskaźniki 5x5 do liczb całkowitychx[0][0][0]
jest adresem pierwszej tablicy 5 wskaźników do liczb całkowitychx[0][0][0][0]
jest pierwszym wskaźnikiem do int w tablicyx[0][0][0][0][0]
jest pierwszym int wskazywanym przez wskaźnik do intW 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 dereferencjaJeś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 typint* (*)[1][2][3]
*x
ma typint* [1][2][3]
(offset 0 + idempotentne wyłuskiwanie)**x
ma typint* [2][3]
(offset 0 + idempotentne wyłuskiwanie)***x
ma typint* [3]
(offset 0 + idempotentne wyłuskiwanie)****x
ma typint*
(przesunięcie 0 + dereferencja)*****x
ma typint
(przesunięcie 0 + dereferencja)źródło