Wiem, że wskaźniki przechowują adresy. Wiem, że typy wskaźników są „ogólnie” znane na podstawie „typu” danych, na które wskazują. Jednak wskaźniki nadal są zmiennymi, a adresy, które przechowują, muszą mieć „typ” danych. Według moich informacji adresy są w formacie szesnastkowym. Ale nadal nie wiem, jaki „typ” danych to ten szesnastkowy. (Zauważ, że wiem, co to jest liczba szesnastkowa, ale kiedy powiesz 10CBA20
na przykład, czy ten ciąg znaków jest liczbą całkowitą? Co? Gdy chcę uzyskać dostęp do adresu i nim manipulować ... sam, muszę znać jego typ. To dlatego pytam.)
30
Odpowiedzi:
Typ zmiennej wskaźnikowej to .. wskaźnik.
Operacje, które formalnie możesz wykonywać w C, to porównywanie go (z innymi wskaźnikami lub specjalną wartością NULL / zero), dodawanie lub odejmowanie liczb całkowitych lub rzutowanie go na inne wskaźniki.
Gdy zaakceptujesz niezdefiniowane zachowanie , możesz spojrzeć na rzeczywistą wartość. Zwykle będzie to słowo maszynowe, takie samo jak liczba całkowita, i zwykle można je bezstratnie rzutować na i od typu liczb całkowitych. (Sporo kodu Windows robi to, ukrywając wskaźniki w typach DWORD lub HANDLE).
Istnieje kilka architektur, w których wskaźniki nie są proste, ponieważ pamięć nie jest płaska. DOS / 8086 „blisko” i „daleko”; Różne obszary pamięci i kodu PIC.
źródło
p1-p2
. Wynik jest podpisaną wartością całkowitą. W szczególności&(array[i])-&(array[j]) == i-j
intptr_t
iuintptr_t
które mają gwarancję „wystarczającej wielkości” dla wartości wskaźnika.p
specyfikatora do printf sprawia, że uzyskanie czytelnej dla człowieka reprezentacji pustego wskaźnika jest zdefiniowane, jeśli zachowanie zależne od implementacji w c.Zbyt komplikujesz rzeczy.
Adresy to tylko liczby całkowite, kropka. Idealnie jest to liczba przywoływanej komórki pamięci (w praktyce staje się to bardziej skomplikowane z powodu segmentów, pamięci wirtualnej itp.).
Składnia szesnastkowa to pełna fikcja, która istnieje tylko dla wygody programistów. 0x1A i 26 są dokładnie taką samą liczbą dokładnie tego samego typu , i żaden z nich nie jest używany przez komputer - wewnętrznie komputer zawsze używa 00011010 (szereg sygnałów binarnych).
To, czy kompilator pozwala traktować wskaźniki jako liczby, zależy od definicji języka - języki „programowania systemów” są tradycyjnie bardziej przejrzyste, jak działają rzeczy pod maską, podczas gdy języki „wysokiego poziomu” częściej próbują ukryć gołe elementy od programisty - ale to nic nie zmienia w fakcie, że wskaźniki są liczbami i zwykle najczęstszym typem liczb (ten z taką samą liczbą bitów, co architektura procesora).
źródło
Wskaźnik jest właśnie taki - wskaźnik. To nie jest coś innego. Nie próbuj myśleć, że to coś innego.
W językach takich jak C, C ++ i Objective-C wskaźniki danych mają cztery rodzaje możliwych wartości:
Istnieją również wskaźniki funkcji, które albo identyfikują funkcję, albo są wskaźnikami funkcji zerowej, albo mają nieokreśloną wartość.
Inne wskaźniki to „wskaźnik do elementu” w C ++. To zdecydowanie nie są adresy pamięci! Zamiast tego identyfikują członka dowolnej instancji klasy. W Objective-C masz selektory, które przypominają „wskaźnik do metody instancji o podanej nazwie metody i nazwach argumentów”. Podobnie jak wskaźnik członka, identyfikuje wszystkie metody wszystkich klas, o ile wyglądają tak samo.
Możesz sprawdzić, w jaki sposób konkretny kompilator implementuje wskaźniki, ale to jest zupełnie inne pytanie.
źródło
class A { public: int num; int x; }; int A::*pmi = &A::num; A a; int n = a.*pmi;
Zmiennapmi
nie byłaby użyteczna, gdyby nie zawierała adresu pamięci, a mianowicie, jak ustalono w ostatnim wierszu kodu, adresu członkanum
instancjia
klasyA
. Możesz rzucić to na zwykłyint
wskaźnik (chociaż kompilator prawdopodobnie dałby ci ostrzeżenie) i z powodzeniem wyłuskać go (udowadniając, że jest to cukier syntaktyczny dla każdego innego wskaźnika).Wskaźnik jest bitowym wzorcem adresującym (jednoznacznie identyfikującym w celu odczytu lub zapisu) słowo pamięci w pamięci RAM. Z powodów historycznych i konwencjonalnych jednostką aktualizacji jest osiem bitów, znanych w języku angielskim jako „bajt” lub w języku francuskim, a bardziej logicznie, jako oktet. Jest to wszechobecne, ale nie nieodłączne; istniały inne rozmiary.
Jeśli dobrze pamiętam, był jeden komputer, który użył 29-bitowego słowa; nie tylko nie jest to potęga dwóch osób, ale nawet liczba pierwsza. Myślałem, że to SILLIAC, ale odpowiedni artykuł na Wikipedii tego nie obsługuje. CAN BUS używa 29-bitowych adresów, ale zgodnie z konwencją adresy sieciowe nie są nazywane wskaźnikami, nawet jeśli są funkcjonalnie identyczne.
Ludzie twierdzą, że wskaźniki są liczbami całkowitymi. Nie jest to ani wewnętrzne, ani niezbędne, ale jeśli interpretujemy wzorce bitowe jako liczby całkowite, pojawia się użyteczna jakość porządkowości, umożliwiająca bardzo bezpośrednią (a zatem wydajną na małym sprzęcie) implementację konstrukcji takich jak „string” i „array”. Pojęcie ciągłej pamięci zależy od porządkowego sąsiedztwa i możliwe jest pozycjonowanie względne; Znacząco można zastosować porównania liczb całkowitych i operacje arytmetyczne. Z tego powodu prawie zawsze istnieje silna korelacja między wielkością słowa adresowania pamięci a jednostką ALU (rzecz, która wykonuje matematykę całkowitą).
Czasami te dwie rzeczy się nie zgadzają. We wczesnych komputerach magistrala adresowa miała szerokość 24 bitów.
źródło
char*
np. Do celów kopiowania / porównywania pamięci isizeof char==1
zgodnie z definicją w standardzie C), a nie słowo (chyba że rozmiar słowa procesora jest taki sam jak rozmiar bajtu).Zasadniczo każdy nowoczesny komputer jest maszyną do popychania. Zwykle przesuwa bity w grupach danych, zwanych bajtami, słowami, dwordami lub qwordami.
Bajt składa się z 8 bitów, słowa 2 bajtów (lub 16 bitów), dworda 2 słowa (lub 32 bity) i qword 2 dwordów (lub 64 bitów). To nie jedyny sposób na ułożenie bitów. Występuje również manipulacja 128-bitowa i 256-bitowa, często w instrukcjach SIMD.
Instrukcje montażu działają na rejestrach, a adresy pamięci zwykle działają w jednej z powyższych postaci.
ALU (arytmetyczne jednostki logiczne) działają na takich pakietach bitów, jakby reprezentowały liczby całkowite (zwykle format dopełnienia Twoa), a FPU tak, jakby to były wartości zmiennoprzecinkowe (zwykle w stylu IEEE 754
float
idouble
). Inne części będą działać tak, jakby były połączonymi danymi o określonym formacie, znakach, wpisach w tabeli, instrukcjach procesora lub adresach.Na typowym komputerze 64-bitowym pakiety 8 bajtów (64 bity) to adresy. Wyświetlamy te adresy w sposób konwencjonalny, jak w formacie szesnastkowym (jak
0xabcd1234cdef5678
), ale jest to prosty sposób na odczytanie przez użytkowników wzorów bitowych. Każdy bajt (8 bitów) jest zapisywany jako dwa znaki szesnastkowe (równoważnie każdy znak szesnastkowy - od 0 do F - reprezentuje 4 bity).To, co się właściwie dzieje (na pewnym poziomie), to fakt, że są bity, zwykle przechowywane w rejestrze lub przechowywane w sąsiednich lokalizacjach w banku pamięci, a my po prostu próbujemy je opisać innemu człowiekowi.
Podążanie za wskaźnikiem polega na zwróceniu się do kontrolera pamięci o podanie danych w tym miejscu. Zwykle pytasz kontroler pamięci o określoną liczbę bajtów w określonej lokalizacji (cóż, domyślnie zakres lokalizacji, zwykle sąsiadujących), i jest on dostarczany za pośrednictwem różnych mechanizmów, do których nie mogę się dostać.
Kod zwykle określa miejsce docelowe pobierania danych - rejestr, inny adres pamięci itp. - i zwykle złym pomysłem jest ładowanie danych zmiennoprzecinkowych do rejestru oczekującego liczby całkowitej lub odwrotnie.
Typ danych w C / C ++ jest czymś, co kompilator śledzi i zmienia generowany kod. Zwykle w danych nie ma nic nieodłącznego, co sprawia, że faktycznie są one dowolnego typu. Po prostu kolekcja bitów (ułożonych w bajty), które są manipulowane przez kod w sposób całkowity (lub zmiennoprzecinkowy lub adresowy).
Istnieją wyjątki od tego. Istnieją architektury, w których pewne rzeczy są innym rodzajem bitów. Najczęstszym przykładem są chronione strony wykonania - podczas gdy instrukcje informujące CPU, co mają robić, są bitami, w czasie wykonywania strony (pamięci) zawierające kod do wykonania są specjalnie oznaczone, nie można ich modyfikować i nie można wykonywać stron, które nie są oznaczone jako strony wykonania.
Istnieją również dane tylko do odczytu (czasami przechowywane w pamięci ROM, których fizycznie nie można zapisać!), Problemy z wyrównaniem (niektóre procesory nie mogą załadować
double
pamięci z pamięci, chyba że są one dostosowane w określony sposób, lub instrukcje SIMD, które wymagają pewnego wyrównania) i mnóstwo inne dziwactwa architektury.Nawet powyższy poziom szczegółowości jest kłamstwem. Komputery nie „naprawdę” przepychają się wokół bitów, naprawdę przepychają się wokół napięć i prądów. Te napięcia i prądy czasami nie robią tego, co „powinny” robić na poziomie abstrakcji bitów. Chipy są zaprojektowane do wykrywania większości takich błędów i ich korygowania bez konieczności abstrakcyjnego rozpoznawania wyższego poziomu.
Nawet to kłamstwo.
Każdy poziom abstrakcji ukrywa poniższy poziom i pozwala myśleć o rozwiązywaniu problemów bez konieczności pamiętania diagramów Feynmana w celu wydrukowania
"Hello World"
.Zatem na wystarczającym poziomie uczciwości komputery wypychają bity, a te bity mają znaczenie dzięki sposobowi ich użycia.
źródło
Ludzie mieli duży wpływ na to, czy wskaźniki są liczbami całkowitymi, czy nie. W rzeczywistości istnieją odpowiedzi na te pytania. Będziesz jednak musiał zrobić krok w krainę specyfikacji, która nie jest dla osób o słabym sercu. Przyjrzymy się specyfikacji C, ISO / IEC 9899: TC2
6.3.2.3 Wskaźniki
Liczba całkowita może być konwertowana na dowolny typ wskaźnika. Z wyjątkiem przypadków określonych wcześniej, wynik jest zdefiniowany w implementacji, może nie być poprawnie wyrównany, może nie wskazywać na encję typu odniesienia i może być reprezentacją pułapki.
Dowolny typ wskaźnika można przekonwertować na typ całkowity. Z wyjątkiem przypadków określonych wcześniej, wynik jest zdefiniowany w implementacji. Jeśli wyniku nie można przedstawić w postaci liczby całkowitej, zachowanie jest niezdefiniowane. Wynik nie musi znajdować się w zakresie wartości dowolnego typu liczby całkowitej.
W tym celu musisz znać kilka popularnych terminów specyfikacji. „zdefiniowane wdrożenie” oznacza, że każdy kompilator może go definiować inaczej. W rzeczywistości kompilator może nawet zdefiniować go na różne sposoby, w zależności od ustawień kompilatora. Niezdefiniowane zachowanie oznacza, że kompilator może wykonywać absolutnie wszystko, od dawania błędu czasu kompilacji po niewyjaśnione zachowania, aż do perfekcyjnego działania.
Z tego wynika, że podstawowa forma przechowywania nie jest określona, poza tym, że może nastąpić konwersja na typ całkowity. Prawdę mówiąc, praktycznie każdy kompilator pod słońcem reprezentuje wskaźniki pod maską jako adresy całkowite (z garstką specjalnych przypadków, w których można je przedstawić jako 2 liczby całkowite zamiast tylko 1), ale specyfikacja dopuszcza absolutnie wszystko, na przykład reprezentowanie adresy jako ciąg 10 znaków!
Jeśli szybko przejdziemy do przodu z C i spojrzymy na specyfikację C ++, uzyskamy nieco większą przejrzystość
reinterpret_cast
, ale jest to inny język, więc jego wartość dla ciebie może się różnić:ISO / IEC N337: C ++ 11 specyfikacja projektu (mam tylko projekt pod ręką)
5.2.10 Reinterpretacja obsady
Wskaźnik można jawnie przekonwertować na dowolny typ integralny wystarczająco duży, aby go utrzymać. Funkcja mapowania jest zdefiniowana w implementacji. [Uwaga: Nie ma w tym nic zaskakującego dla tych, którzy znają strukturę adresowania komputera bazowego. —End note] Wartość typu std :: nullptr_t można przekonwertować na typ całkowy; konwersja ma takie samo znaczenie i ważność jak konwersja (void *) 0 na typ całkowy. [Uwaga: Nie można użyć reinterpret_cast do konwersji wartości dowolnego typu na typ std :: nullptr_t. —Wskazówka]
Wartość typu integralnego lub typu wyliczenia można jawnie przekonwertować na wskaźnik. Wskaźnik skonwertowany na liczbę całkowitą o wystarczającym rozmiarze (jeśli taki istnieje w implementacji) i wraca do tego samego typu wskaźnika, będzie miał swoją oryginalną wartość; odwzorowania między wskaźnikami i liczbami całkowitymi są inaczej zdefiniowane w implementacji. [Uwaga: Z wyjątkiem przypadków opisanych w 3.7.4.3, wynikiem takiej konwersji nie będzie bezpiecznie uzyskana wartość wskaźnika. —Wskazówka]
Jak widać tutaj, mając jeszcze kilka lat za sobą, C ++ odkrył, że można bezpiecznie założyć, że istnieje mapowanie na liczby całkowite, więc nie ma już mowy o nieokreślonym zachowaniu (chociaż istnieje interesująca sprzeczność między częściami 4 i 5 z frazą „jeśli takie istnieją w implementacji”)
Co powinieneś z tego zabrać?
Najlepszy zakład: rzut na (char *). Specyfikacje C i C ++ są pełne reguł określających upakowanie tablic i struktur i oba pozwalają na rzutowanie dowolnego wskaźnika na znak char *. char ma zawsze 1 bajt (nie jest gwarantowany w C, ale w C ++ 11 stał się obowiązkową częścią języka, więc względnie bezpiecznie jest założyć, że wszędzie jest 1 bajt). Pozwala to na wykonanie arytmetyki wskaźnika na poziomie bajt po bajcie bez konieczności faktycznej znajomości specyficznych dla implementacji reprezentacji wskaźników.
źródło
char *
? Mam na myśli hipotetyczną maszynę z oddzielnymi przestrzeniami adresowymi dla kodu i danych.char
ma zawsze 1 bajt w C. Cytując ze standardu C: „Operator sizeof zwraca rozmiar (w bajtach) swojego argumentu” i „Kiedy sizeof jest stosowane do argumentu typu char, unsigned char lub podpisanego char, (lub jego kwalifikowana wersja) wynik to 1. ” Być może myślisz, że bajt ma 8 bitów. Niekoniecznie tak jest. Aby zachować zgodność ze standardem, bajt musi zawierać co najmniej 8 bitów.W przypadku większości architektur typ wskaźnika przestaje istnieć po przetłumaczeniu na kod maszynowy (z wyjątkiem być może „grubych wskaźników”). Dlatego wskaźnik do znaku
int
byłby nie do odróżnienia od wskaźnika do znakudouble
, przynajmniej sam. *[*] Chociaż nadal możesz zgadywać na podstawie rodzajów operacji, które do niego stosujesz.
źródło
Ważną rzeczą do zrozumienia w C i C ++ jest to, jakie są typy. Wszystko, co tak naprawdę robią, to wskazuje kompilatorowi, jak interpretować zestaw bitów / bajtów. Zacznijmy od następującego kodu:
W zależności od architektury liczba całkowita zwykle zawiera 32 bity miejsca do przechowywania tej wartości. Oznacza to, że miejsce w pamięci, w którym przechowywany jest var, będzie wyglądać jak „11111111 11111111 11111010 11000111” lub w postaci szesnastkowej „0xFFFFFAC7”. to jest to! To wszystko, co jest przechowywane w tej lokalizacji. Wszystkie typy mówią kompilatorowi, jak interpretować te informacje. Wskaźniki nie różnią się. Jeśli zrobię coś takiego:
Następnie kompilator pobierze lokalizację var, a następnie zapisze ten adres w ten sam sposób, w jaki pierwszy fragment kodu zapisuje wartość -1337. Nie ma różnicy w sposobie ich przechowywania, tylko sposób ich użycia. Nie ma nawet znaczenia, że zmieniłem var_ptr wskaźnikiem na int. Jeśli chcesz, możesz to zrobić.
Spowoduje to skopiowanie powyższej wartości szesnastkowej var (0xFFFFFAC7) do lokalizacji przechowującej wartość var2. Gdybyśmy wtedy użyli var2, stwierdzilibyśmy, że wartość wynosiłaby 4294965959. Bajty w var2 są takie same jak var, ale wartość liczbowa jest różna. Kompilator interpretował je inaczej, ponieważ powiedzieliśmy, że te bity reprezentują długi bez znaku. To samo możesz zrobić dla wartości wskaźnika.
Ostatecznie interpretujesz wartość reprezentującą adres var jako int bez znaku w tym przykładzie.
Mam nadzieję, że to wyjaśni ci wszystko i da ci lepszy wgląd w działanie C. Pamiętaj, że NIE powinieneś robić żadnych szalonych rzeczy, które zrobiłem w dwóch poniższych wierszach w rzeczywistym kodzie produkcyjnym. To było tylko na pokaz.
źródło
Liczba całkowita.
Przestrzeń adresowa w komputerze jest numerowana sekwencyjnie, zaczynając od 0, i zwiększa się o 1. Zatem wskaźnik będzie przechowywał liczbę całkowitą, która odpowiada adresowi w przestrzeni adresowej.
źródło
Rodzaje łączą się.
W szczególności niektóre typy łączą się, prawie tak, jakby były sparametryzowane za pomocą symboli zastępczych. Typy tablic i wskaźników są takie; mają jeden taki symbol zastępczy, który jest odpowiednio typem elementu tablicy lub wskazanej rzeczy. Typy funkcji są również takie; mogą mieć wiele symboli zastępczych dla parametrów i symbol zastępczy dla typu zwrotu.
Zmienna zadeklarowana jako utrzymująca wskaźnik na char ma typ „wskaźnik na char”. Zmienna zadeklarowana jako utrzymująca wskaźnik na wskaźnik na int ma typ „wskaźnik na wskaźnik na int”.
Typ (wartość) typu „wskaźnik na wskaźnik na int” można zmienić na „wskaźnik na int” za pomocą operacji dereferencji. Pojęcie typu nie jest więc tylko słowami, ale konstrukcją znaczącą matematycznie, dyktującą, co możemy zrobić z wartościami typu (takimi jak dereferencja lub przekazać jako parametr lub przypisać do zmiennej; określa również rozmiar (liczbę bajtów) operacje indeksowania, arytmetyki oraz zwiększania / zmniejszania).
PS Jeśli chcesz zagłębić się w typy, wypróbuj tego bloga: http://www.goodmath.org/blog/2015/05/13/expressions-and-arity-part-1/
źródło