Jaki jest „typ” danych przechowywanych przez wskaźniki w języku C?


30

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 10CBA20na 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.)


17
Wskaźniki nie są zmiennymi , ale wartościami . Zmienne przechowują wartości (a jeśli ich typ jest wskaźnikiem, ta wartość jest wskaźnikiem i może być adresem strefy pamięci zawierającej coś znaczącego). Dana strefa pamięci może służyć do przechowywania różnych wartości różnych typów.
Basile Starynkevitch,

29
„adresy są w formacie szesnastkowym” Nie, to tylko debugger lub bity formatujące bibliotekę. Z tym samym argumentem można powiedzieć, że są w formacie binarnym lub ósemkowym.
usr

Lepiej byłoby zapytać o format , a nie o typ . Stąd poniżej niektóre odpowiedzi poza trasą ... (chociaż Kilian's jest na miejscu).
Lekkość ściga się z Monicą

1
Myślę, że głębszym problemem jest zrozumienie typu przez OP . Jeśli chodzi o to, wartości, którymi manipulujesz w swoim programie, są tylko bitami w pamięci. Typy są sposobem programisty na informowanie kompilatora, jak traktować te bity podczas generowania kodu asemblera.
Justin Lardinois

Przypuszczam, że jest już za późno na edycję z tymi wszystkimi odpowiedziami, ale to pytanie byłoby lepsze, gdybyś ograniczył sprzęt i / lub system operacyjny, na przykład „na Linuksie x64”.
hyde

Odpowiedzi:


64

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.


2
Możesz także wziąć różnicę między dwoma wskaźnikami p1-p2. Wynik jest podpisaną wartością całkowitą. W szczególności&(array[i])-&(array[j]) == i-j
MSalters

13
W rzeczywistości określana jest również konwersja na typ całkowy, a konkretnie na intptr_ti uintptr_tktóre mają gwarancję „wystarczającej wielkości” dla wartości wskaźnika.
Matthieu M.

3
Możesz polegać na konwersji do pracy, ale mapowanie między liczbami całkowitymi a wskaźnikami jest zdefiniowane w implementacji. (Jedynym wyjątkiem jest 0 -> null, a nawet to jest określone tylko wtedy, gdy 0 jest stałą IIRC.)
cHao

7
Dodanie pspecyfikatora do printf sprawia, że ​​uzyskanie czytelnej dla człowieka reprezentacji pustego wskaźnika jest zdefiniowane, jeśli zachowanie zależne od implementacji w c.
dmckee,

6
Ta odpowiedź ma ogólnie słuszny pomysł, ale nie odnosi się do konkretnych twierdzeń. Zmuszenie wskaźnika do typu integralnego nie jest niezdefiniowanym zachowaniem, a typy danych Windows HANDLE nie są wartościami wskaźnika (nie są wskaźnikami ukrytymi w zintegrowanych typach danych, są liczbami całkowitymi ukrytymi w typach wskaźników, aby zapobiec arytmetyki).
Ben Voigt,

44

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).


26
Adresy zdecydowanie nie są liczbami całkowitymi. Podobnie jak liczby zmiennoprzecinkowe zdecydowanie nie są liczbami całkowitymi.
gnasher729

8
W rzeczy samej. Najbardziej znanym kontrprzykładem jest Intel 8086, w którym wskaźniki to dwie liczby całkowite.
MSalters,

5
@Rob W modelu pamięci podzielonej na segmenty wskaźnikiem może być pojedyncza wartość (adres względem początku segmentu; przesunięcie) z sugerowanym segmentem lub para segment / selektor i przesunięcie . (Wydaje mi się, że Intel użył terminu „selektor”; jestem zbyt leniwy, by to sprawdzić). W 8086 były one reprezentowane jako dwie 16-bitowe liczby całkowite, które połączyły się, tworząc jeden 20-bitowy adres fizyczny. (Tak, mógłbyś adresować tę samą komórkę pamięci na wiele, wiele różnych sposobów, gdybyś był tak skłonny: adres = (segment << 4 + przesunięcie) i 0xfffff.) Przeniesiono to przez wszystkie kompatybilne z x86, gdy działasz w trybie rzeczywistym.
CVn

4
Jako programista asemblera długoterminowego mogę zaświadczyć, że pamięć komputera jest niczym innym jak lokalizacjami pamięci zawierającymi liczby całkowite. Ważne jest jednak to, jak je traktujesz i śledzisz, co reprezentują te liczby całkowite. Np. W moim systemie liczba dziesiętna 4075876853 jest przechowywana jako x'F2F0F1F5 ', czyli ciąg „2015” w EBCDIC. Dziesiętny 2015 byłby przechowywany jako 000007DF, podczas gdy x'0002015C 'reprezentuje dziesiętny 2015 w formacie dziesiętnym upakowanym. Jako programista asemblera musisz je śledzić; kompilator robi to dla języków HL.
Steve Ives,

7
Adresy można umieszczać w korespondencji typu „jeden do jednego” z liczbami całkowitymi, ale podobnie jak wszystko inne na komputerze :)
hobbs

16

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:

  1. Wskaźnik może być adresem obiektu.
  2. Wskaźnik może wskazywać tuż obok ostatniego elementu tablicy.
  3. Wskaźnik może być wskaźnikiem zerowym, co oznacza, że ​​nie wskazuje na nic.
  4. Wskaźnik może mieć nieokreśloną wartość, innymi słowy, jest to śmieć i wszystko może się zdarzyć (w tym złe rzeczy), jeśli spróbujesz go użyć.

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.


4
Istnieją wskaźniki do funkcji, aw C ++ wskaźniki do członków.
sdenham

Wskaźniki C ++ dla członków nie są adresami pamięci? Jasne, że są. class A { public: int num; int x; }; int A::*pmi = &A::num; A a; int n = a.*pmi;Zmienna pminie byłaby użyteczna, gdyby nie zawierała adresu pamięci, a mianowicie, jak ustalono w ostatnim wierszu kodu, adresu członka numinstancji aklasy A. Możesz rzucić to na zwykły intwskaź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).
dodgethesteamroller

9

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.


Nitpick, obecnie w powszechnych systemach operacyjnych, wskaźnik identyfikuje lokalizację w pamięci wirtualnej i nie ma nic wspólnego z fizycznym słowem RAM (lokalizacja pamięci wirtualnej może nawet nie istnieć fizycznie, jeśli znajduje się na stronie pamięci znanej przez system operacyjny jako zero) ).
hyde

@hyde - Twój argument ma sens w kontekście, w którym oczywiście go zamierzałeś, ale dominującą formą komputera jest wbudowany kontroler, w którym cuda, takie jak pamięć wirtualna, są najnowszymi innowacjami z ograniczonym wdrożeniem. Ponadto to, co wskazałeś w żaden sposób, nie pomaga PO zrozumieć wskaźników. Myślałem, że jakiś kontekst historyczny sprawiłby, że byłoby to o wiele mniej arbitralne.
Peter Wone

Nie wiem, czy mówimy o pamięci RAM pomoże OP, aby zrozumieć, jak jest pytanie o to, co konkretnie wskaźniki naprawdę są. Aha, inny nitpick we wskaźniku c z definicji wskazuje bajt (można go bezpiecznie przesłać char*np. Do celów kopiowania / porównywania pamięci i sizeof char==1zgodnie z definicją w standardzie C), a nie słowo (chyba że rozmiar słowa procesora jest taki sam jak rozmiar bajtu).
hyde

Zasadniczo kluczami są klucze skrótu do przechowywania. Jest to niezmienny język i platforma.
Peter Wone

Pytanie dotyczy wskaźników c . Wskaźniki zdecydowanie nie są kluczami skrótu, ponieważ nie ma tabeli skrótów, nie ma algorytmu skrótu. Oczywiście są one pewnego rodzaju klawiszami mapy / słownika (dla wystarczająco szerokiej definicji „mapy”), ale nie kluczami skrótu .
hyde

6

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 floati double). 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ć doublepamię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.


3

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

  1. 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.

  2. 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

  1. 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]

  2. 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ć?

  • Dokładna reprezentacja wskaźników jest zdefiniowana w implementacji. (w rzeczywistości, aby uczynić go bardziej chaotycznym, niektóre małe komputery wbudowane reprezentują wskaźnik zerowy, (void ) 0, jako adres 255, aby obsługiwać niektóre sztuczki polegające na aliasingu adresów) *
  • Jeśli musisz zapytać o reprezentację wskaźników w pamięci, prawdopodobnie nie jesteś w karierze programistycznej, w której chciałbyś się nimi bawić.

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.


Czy koniecznie możesz rzucić wskaźnik funkcji na char *? Mam na myśli hipotetyczną maszynę z oddzielnymi przestrzeniami adresowymi dla kodu i danych.
Philip Kendall,

@PhilipKendall Dobra uwaga. Nie zawarłem tej części specyfikacji, ale wskaźniki funkcji są traktowane w specyfikacji zupełnie inaczej niż wskaźniki danych ze względu na dokładnie podnoszony problem. Wskaźniki członków są również traktowane odmiennie (ale działają również bardzo odmiennie)
Cort Ammon - Przywróć Monikę

A charma 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.
David Hammen,

Specyfikacja opisuje konwersję między typem wskaźnika i liczbą całkowitą. Należy zawsze pamiętać, że „konwersja” między typami nie oznacza równości typów, ani nawet że binarna reprezentacja dwóch typów w pamięci miałaby ten sam wzór bitowy. (ASCII można „przekonwertować” na EBCDIC. Big-endian można „przekonwertować” na little-endian. Itd.)
użytkownik2338816

1

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 intbyłby nie do odróżnienia od wskaźnika do znaku double, przynajmniej sam. *

[*] Chociaż nadal możesz zgadywać na podstawie rodzajów operacji, które do niego stosujesz.


1

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:

int var = -1337;

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:

int* var_ptr = &var;   //the ampersand is telling C "get the address where var's value is located"

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ć.

unsigned int var2 = *(unsigned int*)var_ptr;

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.

unsigned int var3 = (unsigned int)var_ptr;

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.


1

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.


1

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/

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.