Podaj przykład z wyjaśnieniem.
c++
c
pointers
dereference
asir
źródło
źródło
int *p;
zdefiniowałby wskaźnik do liczby całkowitej i*p
wyreżyserowałby ten wskaźnik, co oznacza, że faktycznie pobierałby dane, na które wskazuje p.Odpowiedzi:
Przegląd podstawowej terminologii
To zwykle wystarczająco dobre - chyba że montaż programowania - wyobrazić sobie wskaźnik zawierający liczbowy adres pamięci, z 1 odnosząc się do drugiego bajtu w pamięci procesu, 2 trzecia, czwarta i 3 itd ....
Jeśli chcesz uzyskać dostęp do danych / wartości w pamięci, na którą wskazuje wskaźnik - zawartość adresu z tym indeksem liczbowym - wówczas odznaczasz wskaźnik.
Różne języki komputerowe mają różne oznaczenia, aby poinformować kompilator lub tłumacza, że jesteś teraz zainteresowany wartością (bieżącą) wskazanego obiektu - poniżej skupiam się na C i C ++.
Scenariusz wskaźnikowy
Rozważ w C, biorąc pod uwagę wskaźnik taki jak
p
poniżej ...... cztery bajty z wartościami liczbowymi użytymi do kodowania liter „a”, „b”, „c” oraz 0 bajtami oznaczającymi koniec danych tekstowych, są przechowywane gdzieś w pamięci i pod tym adresem numerycznym dane są przechowywane w
p
. W ten sposób C koduje tekst w pamięci znany jest jako ASCIIZ .Na przykład, jeśli literał ciągu byłby pod adresem 0x1000, a
p
32-bitowy wskaźnik pod 0x2000, zawartość pamięci byłaby:Należy pamiętać, że nie istnieje zmienna nazwa / identyfikator adresu 0x1000, ale możemy pośrednio odnoszą się do łańcucha dosłowne stosując wskaźnik przechowującą adres:
p
.Dereferencje wskaźnika
Aby odnieść się do znaków,
p
do których odwołujemy się , odrzucamyp
jedną z tych notacji (ponownie dla C):Możesz także przenosić wskaźniki przez wskazane dane, odsuwając je w trakcie:
Jeśli masz jakieś dane, które można zapisać, możesz wykonać następujące czynności:
Powyżej musisz wiedzieć w czasie kompilacji, że potrzebujesz zmiennej o nazwie
x
, a kod prosi kompilator, aby ustalił, gdzie powinien być przechowywany, zapewniając, że adres będzie dostępny za pośrednictwem&x
.Dereferencje i uzyskiwanie dostępu do elementu danych struktury
W C, jeśli masz zmienną będącą wskaźnikiem struktury z elementami danych, możesz uzyskać dostęp do tych członków za pomocą
->
operatora dereferencji:Wielobajtowe typy danych
Aby użyć wskaźnika, program komputerowy potrzebuje również wglądu w rodzaj danych, na które jest wskazywany - jeśli ten typ danych wymaga więcej niż jednego bajtu do reprezentowania, wówczas wskaźnik zwykle wskazuje bajt o najniższym numerze w danych.
Patrząc na nieco bardziej złożony przykład:
Wskaźniki do dynamicznie przydzielanej pamięci
Czasami nie wiesz, ile pamięci potrzebujesz, dopóki twój program nie uruchomi się i nie zobaczy, jakie dane są do niego rzucane ... wtedy możesz dynamicznie przydzielić pamięć
malloc
. Powszechną praktyką jest przechowywanie adresu we wskaźniku ...W C ++ alokacja pamięci jest zwykle wykonywana przez
new
operatora, a zwalnianie za pomocądelete
:Zobacz także inteligentne wskaźniki C ++ poniżej.
Utrata i wyciek adresów
Często wskaźnik może być jedynym wskaźnikiem tego, gdzie w pamięci istnieją jakieś dane lub bufor. Jeśli potrzebne jest ciągłe korzystanie z tych danych / bufora lub możliwość wywołania
free()
lubdelete
uniknięcia wycieku pamięci, programista musi działać na kopii wskaźnika ...... lub starannie zaplanuj cofnięcie wszelkich zmian ...
Inteligentne wskaźniki C ++
W C ++ najlepszą praktyką jest używanie inteligentnych obiektów wskaźnikowych do przechowywania wskaźników i zarządzania nimi, automatycznie zwalniając je po uruchomieniu niszczycieli inteligentnych wskaźników. Od wersji C ++ 11 biblioteka standardowa udostępnia dwa,
unique_ptr
na wypadek gdy dla przydzielonego obiektu istnieje jeden właściciel ...... i
shared_ptr
do własności udziałów (z wykorzystaniem liczenia referencji ) ...Wskaźniki zerowe
W języku C
NULL
i0
- i dodatkowo w języku C ++nullptr
- można użyć do wskazania, że wskaźnik nie posiada obecnie adresu pamięci zmiennej i nie powinien być wyzerowany ani używany w arytmetyce wskaźników. Na przykład:W C i C ++, podobnie jak wbudowane typy liczbowe niekoniecznie są domyślnie ustawione na
0
, anibools
nafalse
, wskaźniki nie zawsze są ustawione naNULL
. Wszystkie te są ustawione na 0 / false / NULL, gdy są tostatic
zmienne lub (tylko C ++) bezpośrednie lub pośrednie zmienne składowe obiektów statycznych lub ich zasad, lub podlegają zerowej inicjalizacji (np.new T();
Inew T(x, y, z);
przeprowadzają zerową inicjalizację elementów T, w tym wskaźników, podczas gdynew T;
nie).Ponadto, gdy można przypisać
0
,NULL
anullptr
do wskaźnika bity we wskaźniku niekoniecznie wszystko resetu: wskaźnik nie może zawierać „0” na poziomie sprzętowym, lub skierować do adresu 0 w wirtualnej przestrzeni adresowej. Kompilator może tam coś innego sklepu, jeśli ma powody, ale co robi - jeśli przyjść i porównać wskaźnik do0
,NULL
,nullptr
lub inny wskaźnik, który został przypisany do żadnego z powyższych prac porównania muszą zgodnie z oczekiwaniami. Zatem poniżej kodu źródłowego na poziomie kompilatora „NULL” jest potencjalnie nieco „magiczny” w językach C i C ++ ...Więcej informacji na temat adresów pamięci i powodów, dla których prawdopodobnie nie musisz tego wiedzieć
Mówiąc ściślej, zainicjowane wskaźniki przechowują wzór bitowy identyfikujący albo
NULL
(często wirtualny ) adres pamięci.Prosty przypadek polega na tym, że jest to numeryczne przesunięcie w całej wirtualnej przestrzeni adresowej procesu; w bardziej złożonych przypadkach wskaźnik może odnosić się do określonego obszaru pamięci, który procesor może wybrać na podstawie rejestrów „segmentu” procesora lub innego rodzaju identyfikatora segmentu zakodowanego we wzorcu bitowym i / lub szukać w różnych miejscach w zależności od instrukcje kodu maszynowego przy użyciu adresu.
Na przykład
int*
poprawnie zainicjowany, aby wskazywałint
zmienną, może - po rzutowaniu dofloat*
- dostępu do pamięci w pamięci „GPU” zupełnie różnić się od pamięci, w którejint
znajduje się zmienna, a następnie po rzutowaniu do i użyciu jako wskaźnik funkcji może wskazywać na dalsze odrębne kody maszyn przechowujących pamięć dla programu (z wartością liczbowąint*
efektywnie losowego, niepoprawnego wskaźnika w tych innych obszarach pamięci).Języki programowania 3GL, takie jak C i C ++, zwykle ukrywają tę złożoność, na przykład:
Jeśli kompilator daje Ci wskaźnik do zmiennej lub funkcji, możesz swobodnie wyrejestrować ją (pod warunkiem, że zmienna nie zostanie w międzyczasie zniszczona / cofnięta) i problem kompilatora polega na tym, czy np. Należy wcześniej przywrócić konkretny rejestr segmentu procesora, czy też użyta odrębna instrukcja kodu maszynowego
Jeśli otrzymasz wskaźnik do elementu w tablicy, możesz użyć arytmetyki wskaźnika, aby przenieść się w dowolne miejsce w tablicy, a nawet utworzyć adres znajdujący się za końcem tablicy, który można porównać z innymi wskaźnikami do elementów w tablicy (lub które zostały podobnie przesunięte przez arytmetykę wskaźnika do tej samej wartości jeden za końcem); ponownie w C i C ++, od kompilatora zależy, czy to „po prostu działa”
Określone funkcje systemu operacyjnego, np. Mapowanie pamięci współużytkowanej, mogą dać wskazówki, a one „po prostu będą działać” w zakresie adresów, który ma dla nich sens
Próby przeniesienia legalnych wskaźników poza te granice lub rzucenia dowolnych liczb na wskaźniki lub użycia wskaźników rzutowanych na niepowiązane typy, zwykle mają niezdefiniowane zachowanie , więc należy tego unikać w bibliotekach i aplikacjach wyższego poziomu, ale kod dla systemów operacyjnych, sterowników urządzeń itp. Może być konieczne poleganie na zachowaniu niezdefiniowanym przez standard C lub C ++, który jest jednak dobrze określony przez ich konkretną implementację lub sprzęt.
źródło
p[1]
i*(p + 1)
identyczny ? To znaczy: Czyp[1]
i*(p + 1)
generuje te same instrukcje?p
to tylko 2000: gdybyś miał inny wskaźnikp
, musiałby zapisać 2000 w swoich czterech lub ośmiu bajtach. Mam nadzieję, że to pomaga! Twoje zdrowie.u
zawiera tablicęarr
, zarówno gcc, jak i clang rozpoznają, że wartośću.arr[i]
może uzyskać dostęp do tego samego magazynu, co inni członkowie związku, ale nie rozpozna, że wartość*(u.arr+i)
może to zrobić. Nie jestem pewien, czy autorzy tych kompilatorów uważają, że ten ostatni wywołuje UB, czy też pierwszy wywołuje UB, ale i tak powinni go z powodzeniem przetworzyć, ale wyraźnie widzą te dwa wyrażenia jako różne.Dereferencjowanie wskaźnika oznacza uzyskanie wartości przechowywanej w miejscu pamięci wskazywanym przez wskaźnik. Do tego służy operator *, który nazywa się operatorem dereferencyjnym.
źródło
[]
również odznacza wskaźnik (a[b]
jest zdefiniowany jako oznaczający*(a + b)
).Wskaźnik jest „odniesieniem” do wartości. Podobnie jak numer wywoławczy w bibliotece jest odniesieniem do książki. „Dereferencje” numer telefonu fizycznie przechodzi i pobiera tę książkę.
Jeśli nie ma tej książki, bibliotekarz zaczyna krzyczeć, zamyka bibliotekę, a kilka osób postanawia zbadać przyczynę znalezienia książki, której nie ma.
źródło
Krótko mówiąc, dereferencje oznaczają dostęp do wartości z określonego miejsca w pamięci, na które wskazuje ten wskaźnik.
źródło
Kod i objaśnienia z Podstawy wskaźnika :
źródło
Myślę, że wszystkie poprzednie odpowiedzi są błędne, ponieważ stwierdzają, że dereferencje oznaczają dostęp do rzeczywistej wartości. Zamiast tego Wikipedia podaje poprawną definicję: https://en.wikipedia.org/wiki/Dereference_operator
To powiedziawszy, możemy wyrejestrować wskaźnik bez dostępu do wartości, na którą wskazuje. Na przykład:
Wyrejestrowaliśmy wskaźnik NULL bez dostępu do jego wartości. Lub możemy zrobić:
Ponownie, dereferencje, ale nigdy nie uzyskując dostępu do wartości. Taki kod NIE ulega awarii: Awaria występuje, gdy faktycznie uzyskujesz dostęp do danych za pomocą nieprawidłowego wskaźnika. Jednak, niestety, zgodnie ze standardem, dereferencjowanie nieprawidłowego wskaźnika jest niezdefiniowanym zachowaniem (z kilkoma wyjątkami), nawet jeśli nie spróbujesz dotknąć rzeczywistych danych.
W skrócie: dereferencja wskaźnika oznacza zastosowanie do niego operatora dereferencji. Ten operator po prostu zwraca wartość l do przyszłego wykorzystania.
źródło
*p;
powoduje niezdefiniowane zachowanie. Chociaż masz rację, że nie ma dostępu do wyłuskania wartość per se , kod*p;
ma dostęp do wartości.