W renomowanym źródle o C po omówieniu &
operatora podane są następujące informacje :
... To trochę niefortunne, że terminologia [adres] pozostaje, ponieważ dezorientuje tych, którzy nie wiedzą, o co chodzi w adresach, i wprowadza w błąd tych, którzy to robią: myślenie o wskaźnikach tak, jakby były adresami, zwykle prowadzi do smutku ... .
Inne materiały, które przeczytałem (powiedziałbym, że z równie renomowanych źródeł) zawsze bezwstydnie odnosiły się do wskaźników i &
operatora, podając adresy pamięci. Chciałbym nadal szukać aktualności sprawy, ale jest to trochę trudne, gdy renomowane źródła KIND OF nie zgadzają się.
Teraz jestem nieco zdezorientowany - co to właściwie jest wskaźnik, jeśli nie adres pamięci?
PS
Później autor mówi: ... Będę jednak nadal używał terminu „adres”, ponieważ wymyślenie innego [terminu] byłoby jeszcze gorsze.
źródło
Odpowiedzi:
Standard C nie określa, czym jest wewnętrzny wskaźnik i jak działa wewnętrznie. Jest to celowe, aby nie ograniczać liczby platform, na których C można zaimplementować jako język kompilowany lub interpretowany.
Wartość wskaźnika może być jakimś identyfikatorem lub uchwytem lub kombinacją kilku identyfikatorów (przywitaj się z segmentami i przesunięciami x86) i niekoniecznie rzeczywistym adresem pamięci. Ten identyfikator może być dowolny, nawet ciąg tekstowy o stałym rozmiarze. Reprezentacje bezadresowe mogą być szczególnie przydatne dla interpretera C.
źródło
Nie jestem pewien co do twojego źródła, ale opisywany przez ciebie język pochodzi ze standardu C:
Więc ... tak, wskaźniki wskazują adresy pamięci. Przynajmniej tak sugeruje standard C.
Mówiąc bardziej precyzyjnie, wskaźnik to zmienna przechowująca wartość jakiegoś adresu . Adres obiektu (który może być przechowywany we wskaźniku) jest zwracany przez jednoargumentowy
&
operator.Mogę zapisać adres „42 Wallaby Way, Sydney” w zmiennej (i ta zmienna byłaby swego rodzaju „wskaźnikiem”, ale ponieważ nie jest to adres pamięci, nie nazwalibyśmy go właściwie „wskaźnikiem”). Twój komputer ma adresy pojemników pamięci. Wskaźniki przechowują wartość adresu (tzn. Wskaźnik przechowuje wartość „42 Wallaby Way, Sydney”, która jest adresem).
Edycja: Chcę rozwinąć komentarz Alexey Frunze.
Czym dokładnie jest wskaźnik? Spójrzmy na standard C:
Zasadniczo wskaźniki przechowują wartość, która zapewnia odniesienie do jakiegoś obiektu lub funkcji. Rodzaj. Wskaźniki są przeznaczone do przechowywania wartości, która zapewnia odwołanie do jakiegoś obiektu lub funkcji, ale nie zawsze tak jest:
Powyższy cytat mówi, że możemy zamienić liczbę całkowitą na wskaźnik. Jeśli to zrobimy (to znaczy, jeśli umieścimy we wskaźniku wartość całkowitą zamiast konkretnego odwołania do obiektu lub funkcji), wówczas wskaźnik „może nie wskazywać na jednostkę typu odniesienia” (tzn. Może nie dostarczyć odwołanie do obiektu lub funkcji). Może dostarczyć nam czegoś innego. I to jest jedno miejsce, w którym możesz umieścić jakiś uchwyt lub identyfikator we wskaźniku (tzn. Wskaźnik nie wskazuje na obiekt; przechowuje wartość, która coś reprezentuje, ale ta wartość może nie być adresem).
Tak więc, jak mówi Alexey Frunze, możliwe, że wskaźnik nie przechowuje adresu obiektu lub funkcji. Możliwe, że wskaźnik przechowuje zamiast tego jakiś „uchwyt” lub identyfikator, i można to zrobić, przypisując wskaźnikowi dowolną wartość całkowitą. To, co reprezentuje ten uchwyt lub identyfikator, zależy od systemu / środowiska / kontekstu. Tak długo, jak twój system / implementacja może zrozumieć wartość, jesteś w dobrej formie (ale to zależy od konkretnej wartości i konkretnego systemu / implementacji).
Zwykle wskaźnik przechowuje adres obiektu lub funkcji. Jeśli nie jest przechowywany rzeczywisty adres (do obiektu lub funkcji), wynik jest zdefiniowany w implementacji (co oznacza, że dokładnie to, co się dzieje i co reprezentuje teraz wskaźnik, zależy od systemu i implementacji, więc może to być uchwyt lub identyfikator na określonego systemu, ale użycie tego samego kodu / wartości w innym systemie może spowodować awarię programu).
To skończyło się dłużej, niż myślałem, że będzie ...
źródło
Na tym zdjęciu,
pointer_p to wskaźnik, który znajduje się w 0x12345 i wskazuje zmienną zmienną_v w 0x34567.
źródło
Myślenie o wskaźniku jako o adresie jest przybliżeniem . Jak wszystkie przybliżenia, czasami jest wystarczająco użyteczny, ale czasem nie jest dokładny, co oznacza, że poleganie na nim powoduje problemy.
Wskaźnik jest jak adres, ponieważ wskazuje, gdzie znaleźć obiekt. Jednym z bezpośrednich ograniczeń tej analogii jest to, że nie wszystkie wskaźniki faktycznie zawierają adres.
NULL
jest wskaźnikiem, który nie jest adresem. Zawartość zmiennej wskaźnikowej może w rzeczywistości być jednego z trzech rodzajów:p
zawiera adresx
czym ekspresja*p
ma taką samą wartość, cox
);NULL
jest przykładem;p
nie ma prawidłowej wartości,*p
może zrobić wszystko („niezdefiniowane zachowanie”), z awarią programu dość powszechną możliwością).Ponadto dokładniej byłoby powiedzieć, że wskaźnik (jeśli jest poprawny i nie jest pusty) zawiera adres: wskaźnik wskazuje, gdzie znaleźć obiekt, ale wiąże się z nim więcej informacji.
W szczególności wskaźnik ma typ. Na większości platform typ wskaźnika nie ma wpływu w czasie wykonywania, ale ma wpływ wykraczający poza typ w czasie kompilacji. Jeśli
p
jest wskaźnikiem doint
(int *p;
), top + 1
wskazuje na liczbę całkowitą, która jestsizeof(int)
bajtem późniejp
(zakładając, żep + 1
nadal jest poprawnym wskaźnikiem). Jeśliq
wskaźnik dochar
tego wskazuje na ten sam adres cop
(char *q = p;
),q + 1
to nie jest to ten sam adres cop + 1
. Jeśli myślisz o wskaźniku jako adresach, nie jest zbyt intuicyjne, że „następny adres” jest inny dla różnych wskaźników w tej samej lokalizacji.W niektórych środowiskach możliwe jest posiadanie wielu wartości wskaźnika z różnymi reprezentacjami (różne wzorce bitów w pamięci), które wskazują na to samo miejsce w pamięci. Można je traktować jako różne wskaźniki posiadające ten sam adres lub jako różne adresy dla tej samej lokalizacji - w tym przypadku metafora nie jest jasna.
==
Operator zawsze powie Ci, czy oba operandy są skierowane w tym samym miejscu, więc na tych warunkach można miećp == q
chociażp
iq
mają różne wzory bitowe.Istnieją nawet środowiska, w których wskaźniki przenoszą inne informacje poza adres, takie jak informacje o typie lub zezwoleniu. Możesz łatwo przejść przez życie jako programista, nie spotykając się z nimi.
Istnieją środowiska, w których różne rodzaje wskaźników mają różne reprezentacje. Możesz myśleć o tym jako o różnych rodzajach adresów posiadających różne reprezentacje. Na przykład niektóre architektury mają wskaźniki bajtów i wskaźników słów lub wskaźniki obiektów i wskaźników funkcji.
Podsumowując, myślenie o wskaźnikach jako adresach nie jest takie złe, o ile się o tym pamięta
Odwrotna sytuacja jest o wiele bardziej kłopotliwa. Nie wszystko, co wygląda jak adres, może być wskaźnikiem . Gdzieś głęboko w dół dowolny wskaźnik jest reprezentowany jako wzór bitowy, który można odczytać jako liczbę całkowitą, i można powiedzieć, że ta liczba całkowita jest adresem. Ale w drugą stronę, nie każda liczba całkowita jest wskaźnikiem.
Najpierw są pewne znane ograniczenia; na przykład liczba całkowita oznaczająca lokalizację poza przestrzenią adresową programu nie może być prawidłowym wskaźnikiem. Niewłaściwie ustawiony adres nie stanowi poprawnego wskaźnika dla typu danych, który wymaga wyrównania; na przykład na platformie, która
int
wymaga wyrównania 4 bajtów, 0x7654321 nie może być prawidłowąint*
wartością.Jednak wykracza to daleko poza to, że gdy zrobisz wskaźnik na liczbę całkowitą, czeka cię świat kłopotów. Duża część tego problemu polega na tym, że optymalizacja kompilatorów jest znacznie lepsza w mikrooptymalizacji, niż się spodziewa większość programistów, tak więc ich mentalny model działania programu jest głęboko błędny. To, że masz wskaźniki o tym samym adresie, nie oznacza, że są one równoważne. Rozważmy na przykład następujący fragment kodu:
Można się spodziewać, że na maszynie run-of-the-mill, gdzie
sizeof(int)==4
asizeof(short)==2
, to albo drukuje1 = 1?
(little-endian) lub65536 = 1?
(big-endian). Ale na moim 64-bitowym komputerze z systemem Linux z GCC 4.4:GCC jest na tyle uprzejmy, aby ostrzec nas, co dzieje się nie tak w tym prostym przykładzie - w bardziej złożonych przykładach kompilator może nie zauważyć. Ponieważ
p
ma inny typ niż&x
zmiana, którep
punkty nie mogą wpływać na które&x
punkty (poza pewnymi dobrze zdefiniowanymi wyjątkami). Dlatego kompilator może zachować wartośćx
rejestru i nie aktualizować tego rejestru jako*p
zmian. Program usuwa dwa wskaźniki do tego samego adresu i uzyskuje dwie różne wartości!Morał tego przykładu jest taki, że myślenie o (nieważnym) wskaźniku jako adresie jest w porządku, pod warunkiem, że przestrzegasz precyzyjnych reguł języka C. Drugą stroną monety jest to, że reguły języka C są skomplikowane i trudne do uzyskania intuicyjnego wyczucia, chyba że wiesz, co dzieje się pod maską. A pod maską jest to, że więź między wskaźnikami i adresami jest nieco luźna, zarówno w celu obsługi „egzotycznych” architektur procesorów, jak i w celu optymalizacji kompilatorów.
Pomyśl więc, że wskaźniki są adresami jako pierwszym krokiem do zrozumienia, ale nie podążaj za intuicją zbyt daleko.
źródło
*p = 3
jest mało prawdopodobne, na przykład, że odniesie sukces, gdy p nie zostanie zainicjowane.NULL
nie jest, ale dla wymaganego poziomu szczegółowości jest to nieistotne rozproszenie. Nawet w przypadku codziennego programowania, fakt, któryNULL
może być zaimplementowany jako coś, co nie mówi „wskaźnik”, nie pojawia się często (głównie przechodzącNULL
do funkcji variadic - ale nawet tam, jeśli jej nie używasz) , już przyjmujesz założenie, że wszystkie typy wskaźników mają tę samą reprezentację).Wskaźnik to zmienna przechowująca adres pamięci, a nie sam adres. Możesz jednak wyrejestrować wskaźnik i uzyskać dostęp do lokalizacji pamięci.
Na przykład:
Otóż to. To takie proste.
Program do zademonstrowania tego, co mówię i jego wyników jest tutaj:
http://ideone.com/rcSUsb
Program:
źródło
fopen
w zmiennej tylko wtedy, gdy musisz użyć jej więcej niż jeden raz (bofopen
tak naprawdę jest to cały czas).Trudno powiedzieć dokładnie, co dokładnie znaczą autorzy tych książek. To, czy wskaźnik zawiera adres, zależy od tego, jak zdefiniujesz adres i jak zdefiniujesz wskaźnik.
Sądząc po wszystkich napisanych odpowiedziach, niektórzy zakładają, że (1) adres musi być liczbą całkowitą, a (2) wskaźnik nie musi być wirtualny, ponieważ nie podano tego w specyfikacji. Przy tych założeniach jasne wskaźniki niekoniecznie zawierają adresy.
Widzimy jednak, że chociaż (2) jest prawdopodobnie prawdziwe, (1) prawdopodobnie nie musi być prawdziwe. A co sądzić o tym, że & jest nazywany adresem operatora zgodnie z odpowiedzią @ CornStalks? Czy to oznacza, że autorzy specyfikacji zamierzają, aby wskaźnik zawierał adres?
Czy możemy więc powiedzieć, że wskaźnik zawiera adres, ale adres nie musi być liczbą całkowitą? Może.
Wydaje mi się, że wszystko to jest rozmownym semantycznym rozmowem pedantycznym. Jest praktycznie całkowicie bezwartościowy. Czy możesz pomyśleć o kompilatorze, który generuje kod w taki sposób, że wartość wskaźnika nie jest adresem? Jeśli tak to co? Tak myślałem...
Myślę, że to, do czego autor książki (pierwszy fragment, który twierdzi, że wskaźniki niekoniecznie są tylko adresami) prawdopodobnie odnosi się do faktu, że wskaźnik zawiera nieodłączną informację o typie.
Na przykład,
oba y i z są wskaźnikami, ale y + 1 i z + 1 są różne. jeśli są to adresy pamięci, czy te wyrażenia nie dadzą ci takiej samej wartości?
I tutaj leży myślenie o wskaźnikach tak, jakby były adresami, zwykle prowadzi do smutku . Błędy zostały napisane, ponieważ ludzie myślą o wskaźnikach jak o adresach , a to zwykle prowadzi do smutku .
55555 prawdopodobnie nie jest wskaźnikiem, chociaż może być adresem, ale (int *) 55555 jest wskaźnikiem. 55555 + 1 = 55556, ale (int *) 55555 + 1 to 55559 (różnica +/- pod względem wielkościof (int)).
źródło
far
wskaźnik nie jest po prostu „liczbą całkowitą”.Wskaźnik jest abstrakcją reprezentującą lokalizację pamięci. Zauważ, że cytat nie mówi, że myślenie o wskaźnikach tak, jakby były adresami pamięci, jest błędne, po prostu mówi, że „zwykle prowadzi do smutku”. Innymi słowy, prowadzi to do niepoprawnych oczekiwań.
Najbardziej prawdopodobnym źródłem smutku jest z pewnością arytmetyka wskaźników, która w rzeczywistości jest jedną z mocnych stron C. Gdyby wskaźnik był adresem, można oczekiwać, że arytmetyka wskaźnika będzie arytmetyką adresu; ale nie jest. Na przykład dodanie 10 do adresu powinno dać adres większy o 10 jednostek adresowych; ale dodanie 10 do wskaźnika zwiększa go o 10-krotność wielkości obiektu, na który wskazuje (a nawet rzeczywistego rozmiaru, ale zaokrąglonego w górę do granicy wyrównania). Przy
int *
zwykłej architekturze z 32-bitowymi liczbami całkowitymi dodanie 10 do niego zwiększyłoby ją o 40 jednostek adresowych (bajtów). Doświadczeni programiści C zdają sobie z tego sprawę i żyją z tym, ale twój autor najwyraźniej nie jest fanem niechlujnych metafor.Istnieje dodatkowe pytanie, w jaki sposób zawartość wskaźnika reprezentuje lokalizację pamięci: Jak wyjaśniło wiele odpowiedzi, adres nie zawsze jest liczbą całkowitą (lub długą). W niektórych architekturach adres to „segment” plus przesunięcie. Wskaźnik może zawierać nawet przesunięcie do bieżącego segmentu (wskaźnik „bliski”), który sam w sobie nie jest unikalnym adresem pamięci. Zawartość wskaźnika może mieć tylko pośredni związek z adresem pamięci, gdy sprzęt go rozumie. Ale autor cytowanego cytatu nawet nie wspomina o reprezentacji, więc myślę, że miała na myśli równoważność konceptualną, a nie reprezentację.
źródło
Oto jak wyjaśniłem to niektórym zdezorientowanym ludziom w przeszłości: Wskaźnik ma dwa atrybuty, które wpływają na jego zachowanie. Ma wartość , która jest (w typowych środowiskach) adresem pamięci i typem , który informuje o typie i rozmiarze obiektu, na który wskazuje.
Na przykład biorąc pod uwagę:
Możesz mieć trzy różne wskaźniki wskazujące na ten sam obiekt:
Jeśli porównasz wartości tych wskaźników, wszystkie są równe:
Jeśli jednak zwiększysz każdy wskaźnik, zobaczysz, że typ , na który wskazują, staje się odpowiedni.
Zmienne
i
ic
będą miały w tym momencie różne wartości, ponieważi++
powodująi
zawarcie adresu najbliższej liczby całkowitej ic++
powodujec
wskazanie znaku następnego adresowalnego. Zazwyczaj liczby całkowite zajmują więcej pamięci niż znaków, więci
kończy się na większej wartości niżc
po zwiększeniu obu znaków .źródło
i == c
jest źle sformułowany (wskaźniki można porównywać z różnymi typami tylko wtedy, gdy istnieje niejawna konwersja z jednego na drugi). Co więcej, poprawienie tego za pomocą rzutowania oznacza, że zastosowałeś konwersję, a następnie można dyskutować, czy konwersja zmienia wartość, czy nie. (Możesz twierdzić, że tak nie jest, ale to tylko potwierdza to samo, co próbujesz udowodnić na tym przykładzie).Mark Bessey już to powiedział, ale należy to ponownie podkreślić, dopóki nie zostanie zrozumiane.
Wskaźnik ma tyle samo wspólnego ze zmienną, co literał 3.
Wskaźnik jest krotką wartości (adresu) i typu (z dodatkowymi właściwościami, takimi jak tylko do odczytu). Typ (i dodatkowe parametry, jeśli istnieją) mogą dodatkowo definiować lub ograniczać kontekst; na przykład.
__far ptr, __near ptr
: jaki jest kontekst adresu: stos, stos, adres liniowy, przesunięcie gdzieś, pamięć fizyczna lub co.Jest to właściwość typu, która sprawia, że arytmetyka wskaźnika nieco różni się od arytmetyki liczb całkowitych.
Przeciwnych przykładów wskaźnika braku bycia zmienną jest zbyt wiele, aby je zignorować
fopen zwraca wskaźnik PLIKU. (gdzie jest zmienna)
wskaźnik stosu lub wskaźnik ramki są zwykle rejestrami nieadresowalnymi
*(int *)0x1231330 = 13;
- rzutowanie dowolnej liczby całkowitej na typ wskaźnik_integera i zapisywanie / czytanie liczb całkowitych bez wprowadzania zmiennejW czasie istnienia programu C będzie wiele innych wystąpień wskaźników tymczasowych, które nie mają adresów - i dlatego nie są zmiennymi, ale wyrażeniami / wartościami z typem związanym z czasem kompilacji.
źródło
Masz rację i rozum. Zwykle wskaźnik jest tylko adresem, więc możesz rzucić go na liczbę całkowitą i wykonać dowolną arytmetykę.
Ale czasami wskaźniki są tylko częścią adresu. W niektórych architekturach wskaźnik jest konwertowany na adres z dodatkiem bazy lub używany jest inny rejestr procesora .
Ale w dzisiejszych czasach, w architekturze PC i ARM z natywną kompilacją modelu pamięci i językiem C, można pomyśleć, że wskaźnik jest liczbą całkowitą w jednym miejscu w jednowymiarowej adresowalnej pamięci RAM.
źródło
Wskaźnik, jak każda inna zmienna w C, jest zasadniczo zbiorem bitów, które mogą być reprezentowane przez jedną lub więcej połączonych
unsigned char
wartości (jak w przypadku każdego innego typu cariable,sizeof(some_variable)
wskaże liczbęunsigned char
wartości). Tym, co odróżnia wskaźnik od innych zmiennych, jest to, że kompilator C interpretuje bity wskaźnika jako identyfikujące w jakiś sposób miejsce, w którym zmienna może być przechowywana. W C, w przeciwieństwie do niektórych innych języków, można poprosić o miejsce dla wielu zmiennych, a następnie przekonwertować wskaźnik na dowolną wartość w tym zestawie na wskaźnik na dowolną inną zmienną w tym zestawie.Wiele kompilatorów implementuje wskaźniki za pomocą swoich bitów przechowujących rzeczywiste adresy maszyn, ale nie jest to jedyna możliwa implementacja. Implementacja może utrzymywać jedną tablicę - niedostępną dla kodu użytkownika - wyświetlającą adres sprzętowy i przydzielony rozmiar wszystkich obiektów pamięci (zestawów zmiennych) używanych przez program, a każdy wskaźnik zawiera indeks w tablicy wzdłuż z przesunięciem od tego indeksu. Taki projekt pozwoliłby systemowi nie tylko ograniczyć kod do działania tylko na pamięci, którą posiadał, ale także zapewnić, że wskaźnik do jednego elementu pamięci nie może zostać przypadkowo przekształcony w wskaźnik do innego elementu pamięci (w systemie, który używa sprzętu adresy, jeśli
foo
ibar
są tablicami 10 pozycji, które są kolejno przechowywane w pamięci, wskaźnik do „jedenastej” pozycjifoo
może zamiast tego wskazywać pierwszy elementbar
, ale w systemie, w którym każdy „wskaźnik” jest identyfikatorem obiektu i przesunięciem, system może pułapkować, jeśli kod spróbuje zindeksować wskaźnikfoo
poza swój przydzielony zakres). Byłoby również możliwe, aby taki system wyeliminował problemy związane z fragmentacją pamięci, ponieważ adresy fizyczne związane z dowolnymi wskaźnikami mogą być przenoszone.Zauważ, że chociaż wskaźniki są nieco abstrakcyjne, nie są wystarczająco abstrakcyjne, aby umożliwić kompilatorowi C w pełni zgodnemu ze standardami wdrożenie modułu wyrzucania elementów bezużytecznych. Kompilator C określa, że każda zmienna, w tym wskaźniki, jest reprezentowana jako sekwencja
unsigned char
wartości. Biorąc pod uwagę dowolną zmienną, można ją rozłożyć na sekwencję liczb, a następnie przekształcić tę sekwencję liczb z powrotem na zmienną pierwotnego typu. W związku z tym program byłby możliwycalloc
trochę pamięci (otrzymanie do niej wskaźnika), zapisz coś tam, rozłóż wskaźnik na serię bajtów, wyświetl te na ekranie, a następnie usuń wszystkie odniesienia do nich. Jeśli następnie program zaakceptuje niektóre liczby z klawiatury, odtworzy je do wskaźnika, a następnie spróbuje odczytać dane z tego wskaźnika, a jeśli użytkownik wprowadzi te same liczby, które program wcześniej wyświetlał, program będzie musiał wysłać dane które zostały zapisane wcalloc
pamięci. Ponieważ nie ma możliwego sposobu, aby komputer wiedział, czy użytkownik wykonał kopię wyświetlanych liczb, nie byłoby możliwe, aby komputer wiedział, czy wspomniana pamięć będzie kiedykolwiek dostępna w przyszłości.źródło
free
oczywiście nie jest to jawnie wywoływane). To, czy wynikowa implementacja byłaby tak przydatna, to inna sprawa, ponieważ jej zdolność do kolekcjonowania może być zbyt ograniczona, ale możesz przynajmniej nazwać ją zbieraczem śmieci :-) Przypisanie wskaźnika i arytmetyka nie „wyciekłyby” wartości, ale każdy dostęp dochar*
nieznanego pochodzenia musiałby zostać sprawdzony.free
nie został wywołany, lub zapobiec, aby jakiekolwiek odwołanie do uwolnionego obiektu stało się odniesieniem do obiektu na żywo [nawet przy użyciu zasobów, które wymagają wyraźne zarządzanie czasem życia, GC może nadal użytecznie wykonywać tę ostatnią funkcję]; system GC, który czasem fałszywie traktuje obiekty jako posiadające żywe odniesienia do nich, może być użyteczny, jeśli prawdopodobieństwo niepotrzebnego przypięcia N obiektów jednocześnie zbliża się do zera, gdy N staje się duże . Chyba że ktoś chceWskaźnik jest typem zmiennej, który jest natywnie dostępny w C / C ++ i zawiera adres pamięci. Jak każda inna zmienna ma swój własny adres i zajmuje pamięć (ilość zależy od platformy).
Jednym z problemów, który zobaczysz w wyniku zamieszania, jest próba zmiany odnośnika w funkcji przez zwykłe przekazanie wskaźnika przez wartość. Spowoduje to skopiowanie wskaźnika w zakresie funkcji i wszelkie zmiany w miejscach, w których ten nowy wskaźnik „wskazuje” nie zmieni odniesienia do wskaźnika w zakresie, który wywołał funkcję. Aby zmodyfikować rzeczywisty wskaźnik w funkcji, zwykle przekazuje się wskaźnik do wskaźnika.
źródło
KRÓTKIE PODSUMOWANIE (które również umieszczę na górze):
(0) Myślenie o wskaźnikach jako adresach jest często dobrym narzędziem do nauki i często stanowi faktyczną implementację wskaźników do zwykłych typów danych.
(1) Ale w wielu, być może większości, kompilatorach wskaźniki do funkcji nie są adresami, ale są większe niż adres (zazwyczaj 2x, czasem więcej), lub w rzeczywistości są wskaźnikami do struktury w pamięci niż zawierają adresy funkcji i rzeczy takich jak stała pula.
(2) Wskaźniki do członków danych i wskaźniki do metod są często jeszcze dziwniejsze.
(3) Starszy kod x86 z problemami ze wskaźnikami FAR i NEAR
(4) Kilka przykładów, w szczególności IBM AS / 400, z bezpiecznymi „wskaźnikami tłuszczu”.
Jestem pewien, że możesz znaleźć więcej.
SZCZEGÓŁ:
UMMPPHHH !!!!! Wiele dotychczasowych odpowiedzi jest dość typowymi odpowiedziami na „programistów” - ale nie na kompilatory ani na sprzętowe. Ponieważ udaję, że jestem zaprzeczeniem sprzętowym i często pracuję z kompilatorami, pozwól mi wrzucić moje dwa centy:
W wielu, prawdopodobnie większości kompilatorach C, wskaźnikiem do danych typu
T
jest w rzeczywistości adresT
.W porządku.
Ale nawet w wielu z tych kompilatorów pewne wskaźniki NIE są adresami. Możesz to powiedzieć, patrząc na
sizeof(ThePointer)
.Na przykład wskaźniki do funkcji są czasami znacznie większe niż zwykłe adresy. Lub mogą obejmować poziom pośredni. Ten artykułzawiera jeden opis dotyczący procesora Intel Itanium, ale widziałem inne. Zwykle, aby wywołać funkcję, musisz znać nie tylko adres kodu funkcji, ale także adres stałej puli funkcji - obszar pamięci, z którego stałe są ładowane za pomocą pojedynczej instrukcji ładowania, zamiast kompilatora, który musi generować 64-bitowa stała z kilku instrukcji Load Immediate oraz Shift i OR. Tak więc zamiast jednego 64-bitowego adresu potrzebujesz 2 64-bitowych adresów. Niektóre ABI (interfejsy binarne aplikacji) przenoszą to jako 128 bitów, podczas gdy inne używają poziomu pośredniego, przy czym wskaźnik funkcji faktycznie jest adresem deskryptora funkcji, który zawiera 2 właśnie wspomniane adresy. Który jest lepszy? Zależy od twojego punktu widzenia: wydajność, rozmiar kodu, oraz niektóre problemy ze zgodnością - często kod zakłada, że wskaźnik może być rzutowany na długi lub długi długi, ale może również zakładać, że długi długi ma dokładnie 64 bity. Taki kod może nie być zgodny ze standardami, ale klienci mogą chcieć, aby działał.
Wielu z nas ma bolesne wspomnienia o starej architekturze segmentowej Intel x86, z BLISKIMI WSKAŹNIKAMI i DALSZYMI WSKAŹNIKAMI. Na szczęście są już prawie wymarłe, więc tylko krótkie podsumowanie: w 16-bitowym trybie rzeczywistym rzeczywisty adres liniowy był
W trybie chronionym może tak być
wynikowy adres jest sprawdzany pod kątem limitu ustawionego w segmencie. Niektóre programy nie używały tak naprawdę standardowych deklaracji wskaźników FAR i NEAR C / C ++, ale wiele z nich właśnie powiedziało
*T
--- ale były przełączniki kompilatora i linkera, więc na przykład wskaźniki kodu mogą znajdować się w pobliżu wskaźników, tylko 32-bitowe przesunięcie w stosunku do tego, co jest w rejestr CS (segment segmentu), podczas gdy wskaźnikami danych mogą być wskaźniki FAR, określające zarówno 16-bitowy numer segmentu, jak i 32-bitowe przesunięcie dla wartości 48-bitowej. Teraz obie te wielkości są z pewnością powiązane z adresem, ale ponieważ nie są one tego samego rozmiaru, który z nich jest adresem? Co więcej, segmenty zawierały również uprawnienia - tylko do odczytu, do odczytu i zapisu, wykonywalne - oprócz rzeczy związanych z rzeczywistym adresem.Bardziej interesującym przykładem IMHO jest (lub być może była) rodzina IBM AS / 400. Ten komputer był jednym z pierwszych, którzy wdrożyli system operacyjny w C ++. Wskaźniki na tym machime zazwyczaj były dwukrotnie większe niż rzeczywisty rozmiar adresu - np. Jako ta prezentacjamówi, 128-bitowe wskaźniki, ale rzeczywiste adresy miały 48-64 bity, i znowu kilka dodatkowych informacji, co nazywa się zdolnością, która zapewniała uprawnienia, takie jak odczyt, zapis, a także limit zapobiegający przepełnieniu bufora. Tak: możesz to zrobić kompatybilnie z C / C ++ - a gdyby było to wszechobecne, chińska PLA i słowiańska mafia nie włamaliby się do tak wielu zachodnich systemów komputerowych. Ale historycznie większość programów C / C ++ zaniedbywała bezpieczeństwo wydajności. Co najciekawsze, rodzina AS400 pozwoliła systemowi operacyjnemu na tworzenie bezpiecznych wskaźników, które mogłyby być przekazywane nieuprzywilejowanemu kodowi, ale których nieuprawniony kod nie mógł sfałszować ani manipulować. Ponownie, bezpieczeństwo, i chociaż jest zgodny ze standardami, dużo niechlujny, niezgodny ze standardami kod C / C ++ nie będzie działał w tak bezpiecznym systemie. Ponownie istnieją oficjalne standardy,
Teraz zsiadam z mydła bezpieczeństwa i wspomnę o kilku innych sposobach, w których wskaźniki (różnych typów) często nie są tak naprawdę adresami: Wskaźniki do elementów danych, wskaźniki do metod funkcji składowych, a ich statyczne wersje są większe niż zwykły adres. Jak mówi ten post :
Jak zapewne możecie zgadnąć z mojego pontyfikatu dotyczącego (nie) bezpieczeństwa, brałem udział w projektach sprzętowych / programowych C / C ++, w których wskaźnik był traktowany bardziej jak zdolność niż surowy adres.
Mógłbym kontynuować, ale mam nadzieję, że wpadłeś na pomysł.
KRÓTKIE PODSUMOWANIE (które również umieszczę na górze):
(0) myślenie o wskaźnikach jako adresach jest często dobrym narzędziem do nauki i często stanowi faktyczną implementację wskaźników do zwykłych typów danych.
(1) Ale w wielu, być może większości, kompilatorach wskaźniki do funkcji nie są adresami, ale są większe niż adres (zazwyczaj 2X, czasem więcej), lub w rzeczywistości są wskaźnikami do struktury w pamięci niż zawierają adresy funkcji i rzeczy takich jak stała pula.
(2) Wskaźniki do członków danych i wskaźniki do metod są często jeszcze dziwniejsze.
(3) Starszy kod x86 z problemami ze wskaźnikami FAR i NEAR
(4) Kilka przykładów, w szczególności IBM AS / 400, z bezpiecznymi „wskaźnikami tłuszczu”.
Jestem pewien, że możesz znaleźć więcej.
źródło
LinearAddress = SegmentRegister.Selector * 16 + Offset
(notatka 16, bez przesunięcia o 16). W trybie chronionymLinearAddress = SegmentRegister.base + offset
(bez mnożenia dowolnego rodzaju, podstawa segment jest przechowywany w GDT i LDT / zapisywane w rejestrze segmentu , jak to ).Wskaźnik to po prostu kolejna zmienna, która służy do przechowywania adresu miejsca w pamięci (zwykle adresu innej zmiennej).
źródło
Możesz to zobaczyć w ten sposób. Wskaźnik to wartość reprezentująca adres w adresowalnej przestrzeni pamięci.
źródło
Wskaźnik to po prostu kolejna zmienna, która może zawierać adres pamięci zwykle innej zmiennej. Wskaźnik będący zmienną również ma adres pamięci.
źródło
Wskaźnik prądu przemiennego jest bardzo podobny do adresu pamięci, ale z oddalonymi szczegółami zależnymi od maszyny, a także niektórymi funkcjami nie znajdującymi się w zestawie instrukcji niższego poziomu.
Na przykład wskaźnik C jest stosunkowo bogato pisany. Jeśli zwiększysz wskaźnik przez szereg struktur, ładnie przeskakuje z jednej struktury do drugiej.
Wskaźniki podlegają regułom konwersji i zapewniają sprawdzanie typu czasu kompilacji.
Istnieje specjalna wartość „wskaźnika zerowego”, która jest przenośna na poziomie kodu źródłowego, ale której reprezentacja może się różnić. Jeśli przypiszesz wskaźnikowi liczbę całkowitą, której wartość wynosi zero, wskaźnik ten przyjmuje wartość wskaźnika zerowego. Podobnie jeśli zainicjujesz wskaźnik w ten sposób.
Wskaźnik może być użyty jako zmienna boolowska: sprawdza wartość true, jeśli jest inna niż null, i false, jeśli ma wartość null.
W języku maszynowym, jeśli wskaźnik zerowy jest śmiesznym adresem, takim jak 0xFFFFFFFF, może być konieczne przeprowadzenie jawnych testów dla tej wartości. C ukrywa to przed tobą. Nawet jeśli wskaźnik zerowy ma wartość 0xFFFFFFFF, możesz go przetestować za pomocą
if (ptr != 0) { /* not null! */}
.Wykorzystanie wskaźników, które podważają system typów, prowadzi do nieokreślonego zachowania, podczas gdy podobny kod w języku maszynowym może być dobrze zdefiniowany. Asemblery skompilują napisane instrukcje, ale kompilatory C zoptymalizują się w oparciu o założenie, że nie zrobiłeś nic złego. Jeśli
float *p
wskaźnik wskazujelong n
zmienną i*p = 0.0
jest wykonywany, kompilator nie musi tego obsłużyć. Dalsze użycien
nie będzie konieczne odczytanie wzorca bitowego wartości zmiennoprzecinkowej, ale być może będzie to zoptymalizowany dostęp oparty na założeniu „ścisłego aliasingu”, któregon
nie zmieniono! To znaczy założenie, że program jest dobrze zachowany i dlategop
nie powinien wskazywać nan
.W C wskaźniki do kodu i wskaźniki do danych są różne, ale w wielu architekturach adresy są takie same. Można opracować kompilatory C, które mają wskaźniki „gruby”, nawet jeśli architektura docelowa nie. Wskaźniki tłuszczu oznaczają, że wskaźniki nie są tylko adresami maszyny, ale zawierają inne informacje, takie jak informacje o wielkości wskazywanego obiektu, do sprawdzania granic. Przenośnie napisane programy łatwo przenoszą się na takie kompilatory.
Jak widać, istnieje wiele różnic semantycznych między adresami maszyny a wskaźnikami C.
źródło
ptr != 0
nie występuje test zerowy, ujawnij jego tożsamość (ale zanim to zrobisz, wyślij raport o błędzie do dostawcy).Przed zrozumieniem wskaźników musimy zrozumieć obiekty. Obiekty to istnienie, które istnieje i ma specyfikator lokalizacji zwany adresem. Wskaźnik jest po prostu zmienną, jak każda inna zmienna w
C
typie o nazwie,pointer
którego treść jest interpretowana jako adres obiektu, który obsługuje następną operację.Wskaźnik jest klasyfikowany na podstawie typu obiektu, do którego się obecnie odnosi. Jedyną istotną informacją jest rozmiar obiektu.
Każdy obiekt obsługuje operację
&
(adres), która pobiera specyfikator lokalizacji (adres) obiektu jako typ obiektu wskaźnika. Powinno to przezwyciężyć zamieszanie wokół nomenklatury, ponieważ rozsądnie byłoby wywoływać ją&
jako operację obiektu, a nie wskaźnik, którego wynikowy typ jest wskaźnikiem typu obiektu.Uwaga W tym objaśnieniu pominąłem pojęcie pamięci.
źródło
&
jako „Adres”, ponieważ jest to bardziej powiązane z Obiektem niż wskaźnikiem per se ”Adres służy do identyfikowania części pamięci o stałym rozmiarze, zwykle dla każdego bajtu, jako liczby całkowitej. Jest to dokładnie nazywane adresem bajtu , który jest również używany przez ISO C. Mogą istnieć inne metody konstruowania adresu, np. Dla każdego bitu. Jednak tak często używany jest tylko adres bajtowy, zwykle pomijamy „bajt”.
Technicznie adres nigdy nie jest wartością w C, ponieważ definicja terminu „wartość” w (ISO) C to:
(Podkreślone przeze mnie.) Jednak nie ma takiego „typu adresu” w C.
Wskaźnik nie jest taki sam. Wskaźnik jest rodzajem typu w języku C. Istnieje kilka różnych typów wskaźników. Oni niekoniecznie posłuszeństwa do identycznego zestawu reguł języka, np wpływu
++
na wartości typuint*
vs.char*
.Wartość w C może być typu wskaźnika. Nazywa się to wartością wskaźnika . Dla jasności, wartość wskaźnika nie jest wskaźnikiem w języku C. Ale jesteśmy przyzwyczajeni do mieszania ich ze sobą, ponieważ w C raczej nie będzie to dwuznaczne: jeśli nazwiemy wyrażenie
p
jako „wskaźnik”, będzie to tylko wartość wskaźnika, ale nie typ, ponieważ nazwany typ w C nie jest wyrażone wyrażeniem , ale nazwą typu lub nazwą typu .Niektóre inne rzeczy są subtelne. Jako użytkownik C, po pierwsze, należy wiedzieć, co
object
oznacza:Obiekt jest bytem reprezentującym wartości, które są określonego typu. Wskaźnik jest typem obiektu . Jeśli więc zadeklarujemy
int* p;
,p
oznacza to „obiekt typu wskaźnika” lub „obiekt wskaźnika”.Zauważ, że nie ma „zmiennej” normalnie zdefiniowanej przez normę (w rzeczywistości nigdy nie jest używana jako rzeczownik przez ISO C w tekście normatywnym). Jednak nieoficjalnie nazywamy obiekt zmienną, tak jak robi to inny język. (Ale wciąż nie tak dokładnie, np. W C ++ zmienna może być normalnie typu referencyjnego , co nie jest obiektem.) Wyrażenia „obiekt wskaźnika” lub „zmienna wskaźnika” są czasami traktowane jak „wartość wskaźnika” jak powyżej, za pomocą prawdopodobna niewielka różnica. (Kolejny zestaw przykładów to „tablica”).
Ponieważ wskaźnik jest typem, a adres w C jest faktycznie „pozbawiony typu”, wartość wskaźnika z grubsza „zawiera” adres. A wyrażenie typu wskaźnika może dać adres, np
ISO C11 6.5.2.3
Uwaga: to sformułowanie zostało wprowadzone przez WG14 / N1256, tj. ISO C99: TC3. W C99 jest
Odzwierciedla opinię komisji: adres nie jest wartością wskaźnika zwróconą przez jednoargumentowego
&
operatora.Pomimo powyższego sformułowania, nawet w normach nadal panuje bałagan.
ISO C11 6.6
ISO C ++ 11 5.19
(Ostatnia wersja robocza C ++ używa innego sformułowania, więc nie ma tego problemu).
W rzeczywistości zarówno „stała adresowa” w C, jak i „stała adresowa wyrażenia” w C ++ są stałym wyrażeniem typów wskaźników (lub przynajmniej „podobnych do wskaźników” od C ++ 11).
Wbudowany
&
operator jednoargumentowy nazywany jest w C i C ++ jako „adres”; podobniestd::addressof
jest wprowadzony w C ++ 11.Te nazwy mogą wprowadzać w błąd. Uzyskaną wyrażenie jest typu wskaźnik, tak że będą interpretowane jako: wynik zawiera / daje adres, niż jest adres.
źródło
Mówi „bo myli tych, którzy nie wiedzą, o co chodzi w adresach” - to też prawda: jeśli dowiesz się, o co chodzi, nie będziesz zdezorientowany. Teoretycznie wskaźnik jest zmienną wskazującą na inną, praktycznie posiada adres, który jest adresem zmiennej, na którą wskazuje. Nie wiem, dlaczego miałbym ukrywać ten fakt, to nie jest nauka o rakietach. Jeśli zrozumiesz wskaźniki, będziesz o krok bliżej, aby zrozumieć, jak działają komputery. Śmiało!
źródło
Pomyśl o tym, myślę, że to kwestia semantyki. Nie sądzę, aby autor miał rację, ponieważ standard C odnosi się do wskaźnika jako trzymającego adres do obiektu, do którego istnieje odwołanie, jak inni już tu wspominali. Jednak adres! = Adres pamięci. Adres może być naprawdę dowolny, jak w standardzie C, chociaż ostatecznie doprowadzi do adresu pamięci, sam wskaźnik może być identyfikatorem, przesunięciem + selektorem (x86), naprawdę wszystko, o ile może opisać (po mapowaniu) dowolną pamięć adres w przestrzeni adresowalnej.
źródło
int i=5
-> i wynosi 5, wówczas wskaźnikiem jest adres tak. Również null ma również adres. Zwykle niepoprawny adres zapisu (ale niekoniecznie patrz tryb rzeczywisty x86), ale jednak adres. Są tak naprawdę tylko 2 wymagania dla null: gwarantuje się, że porówna nierówny wskaźnik do rzeczywistego obiektu, a dwa dowolne wskaźniki null będą równe.p
jest wskaźnikiem,p+1
nie zawsze jest to adres zwiększany o 1.it's guaranteed to compare unequal to a pointer to an actual object
. Jeśli chodzi o arytmetykę wskaźnika, nie widzę sensu, wartość wskaźnika jest nadal adresem, nawet jeśli operacja „+” niekoniecznie doda do niego jeden bajt.Jeszcze jeden sposób, w jaki wskaźnik C lub C ++ różni się od prostego adresu pamięci z powodu różnych typów wskaźników, których nie widziałem w innych odpowiedziach (chociaż biorąc pod uwagę ich całkowity rozmiar, mogłem go przeoczyć). Ale to chyba najważniejszy, ponieważ nawet doświadczeni programiści C / C ++ mogą się o niego potknąć:
Kompilator może założyć, że wskaźniki niezgodnych typów nie wskazują tego samego adresu, nawet jeśli wyraźnie to robią, co może dać zachowanie, które nie byłoby możliwe przy prostym modelu adresu ==. Rozważ następujący kod (przy założeniu
sizeof(int) = 2*sizeof(short)
):Należy pamiętać, że istnieje wyjątek
char*
, dlategochar*
możliwe jest manipulowanie wartościami (choć niezbyt przenośne).źródło
Szybkie podsumowanie: Adres AC to wartość, zwykle reprezentowana jako adres pamięci na poziomie komputera, z określonym typem.
Niekwalifikowane słowo „wskaźnik” jest dwuznaczne. C ma obiekty wskaźnika (zmienne), typy wskaźników, wyrażenia wskaźnika i wartości wskaźnika .
Bardzo często używa się słowa „wskaźnik”, co oznacza „obiekt wskaźnika”, co może prowadzić do pewnych nieporozumień - dlatego staram się używać słowa „wskaźnik” zamiast przymiotnika.
Standard C, przynajmniej w niektórych przypadkach, używa słowa „wskaźnik”, co oznacza „wartość wskaźnika”. Na przykład opis malloc mówi, że „zwraca albo zerowy wskaźnik, albo wskaźnik do przydzielonego miejsca”.
Więc jaki jest adres w C? Jest to wartość wskaźnika, tzn. Wartość określonego typu wskaźnika. (Z wyjątkiem tego, że wartość wskaźnika zerowego niekoniecznie jest określana jako „adres”, ponieważ nie jest to adres niczego).
Opis standardowego
&
operatora jednoargumentowego mówi, że „podaje adres swojego operandu”. Poza standardem C słowo „adres” jest powszechnie używane w odniesieniu do adresu pamięci (fizycznej lub wirtualnej), zwykle o rozmiarze jednego słowa (cokolwiek „słowo” znajduje się w danym systemie).„Adres” prądu przemiennego jest zwykle implementowany jako adres maszyny - podobnie jak
int
wartość C jest zwykle implementowana jako słowo maszynowe. Ale adres C (wartość wskaźnika) to coś więcej niż adres maszyny. Jest to wartość zwykle reprezentowana jako adres maszyny i jest to wartość określonego typu .źródło
Wartość wskaźnika to adres. Zmienna wskaźnikowa to obiekt, który może przechowywać adres. Jest to prawda, ponieważ to właśnie standard określa wskaźnik. Ważne jest, aby powiedzieć o tym nowicjuszom C, ponieważ nowicjusze C często nie są pewni różnicy między wskaźnikiem a rzeczą, na którą wskazuje (to znaczy, nie znają różnicy między kopertą a budynkiem). Pojęcie adresu (każdy obiekt ma adres i to właśnie przechowuje wskaźnik) jest ważne, ponieważ rozwiązuje to problem.
Jednak standardowe rozmowy na określonym poziomie abstrakcji. Ci ludzie, o których autor mówi, o których „wiedzą, o co chodzi”, ale którzy są nowicjuszami w C, musieli koniecznie dowiedzieć się o adresach na innym poziomie abstrakcji - być może przez programowanie w asemblerze. Nie ma gwarancji, że implementacja języka C używa tej samej reprezentacji adresów, co używane przez kodery procesorów procesorów (zwanej w tym fragmencie „adresem sklepu”), o której ci ludzie już wiedzą.
Mówi dalej o „całkowicie rozsądnej manipulacji adresem”. Jeśli chodzi o standard C, to w zasadzie nie ma czegoś takiego jak „całkowicie rozsądna manipulacja adresem”. Dodawanie jest zdefiniowane na wskaźnikach i to w zasadzie tyle. Jasne, możesz przekonwertować wskaźnik na liczbę całkowitą, wykonać operacje bitowe lub arytmetyczne, a następnie przekonwertować go z powrotem. Nie gwarantuje się, że będzie działać zgodnie ze standardem, dlatego przed napisaniem tego kodu lepiej wiedzieć, w jaki sposób konkretna implementacja C reprezentuje wskaźniki i wykonuje tę konwersję. To prawdopodobnie korzysta z reprezentacji adresów można się spodziewać, ale to nie robi to twoja wina, bo nie czytać instrukcji. To nie jest zamieszanie, to nieprawidłowa procedura programowania ;-)
Krótko mówiąc, C używa bardziej abstrakcyjnej koncepcji adresu niż autor.
Autorska koncepcja adresu nie jest oczywiście słowem najniższego poziomu w tej sprawie. Z mapami pamięci wirtualnej i adresowaniem fizycznej pamięci RAM na wiele układów scalonych, liczba, którą podajesz CPU jest „adresem sklepu”, do którego chcesz uzyskać dostęp, nie ma w zasadzie nic wspólnego z tym, gdzie dane są faktycznie zlokalizowane sprzętowo. To są wszystkie warstwy pośrednie i reprezentacyjne, ale autor wybrał jedną do uprzywilejowania. Jeśli zamierzasz to zrobić, mówiąc o C, wybierz poziom C do uprzywilejowania !
Osobiście nie sądzę, aby uwagi autora były tak pomocne, z wyjątkiem kontekstu wprowadzenia C do programistów asemblera. Z pewnością nie jest pomocne osobom pochodzącym z języków wyższego poziomu stwierdzenie, że wartości wskaźnika nie są adresami. O wiele lepiej byłoby uznać złożoność niż powiedzieć, że CPU ma monopol na powiedzenie, czym jest adres, a zatem wartości wskaźnika C „nie są” adresami. Są to adresy, ale mogą być napisane w innym języku niż adresy, które ma na myśli. Myślę, że rozróżnienie tych dwóch rzeczy w kontekście C jako „adres” i „adres sklepu” byłoby odpowiednie.
źródło
Mówiąc wprost, wskaźniki są w rzeczywistości przesuniętą częścią mechanizmu segmentacji, które tłumaczą się na adres liniowy po segmentacji, a następnie na adres fizyczny po stronicowaniu. Adresy fizyczne są faktycznie adresowane od ciebie pamięci RAM.
źródło