Podaj przykład z wyjaśnieniem.
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.
Podaj przykład z wyjaśnieniem.
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:
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 ++.
Rozważ w C, biorąc pod uwagę wskaźnik taki jak p
poniżej ...
const char* p = "abc";
... 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:
Memory Address (hex) Variable name Contents
1000 'a' == 97 (ASCII)
1001 'b' == 98
1002 'c' == 99
1003 0
...
2000-2003 p 1000 hex
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
.
Aby odnieść się do znaków, p
do których odwołujemy się , odrzucamy p
jedną z tych notacji (ponownie dla C):
assert(*p == 'a'); // The first character at address p will be 'a'
assert(p[1] == 'b'); // p[1] actually dereferences a pointer created by adding
// p and 1 times the size of the things to which p points:
// In this case they're char which are 1 byte in C...
assert(*(p + 1) == 'b'); // Another notation for p[1]
Możesz także przenosić wskaźniki przez wskazane dane, odsuwając je w trakcie:
++p; // Increment p so it's now 0x1001
assert(*p == 'b'); // p == 0x1001 which is where the 'b' is...
Jeśli masz jakieś dane, które można zapisać, możesz wykonać następujące czynności:
int x = 2;
int* p_x = &x; // Put the address of the x variable into the pointer p_x
*p_x = 4; // Change the memory at the address in p_x to be 4
assert(x == 4); // Check x is now 4
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
.
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:
typedef struct X { int i_; double d_; } X;
X x;
X* p = &x;
p->d_ = 3.14159; // Dereference and access data member x.d_
(*p).d_ *= -1; // Another equivalent notation for accessing x.d_
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:
double sizes[] = { 10.3, 13.4, 11.2, 19.4 };
double* p = sizes;
assert(p[0] == 10.3); // Knows to look at all the bytes in the first double value
assert(p[1] == 13.4); // Actually looks at bytes from address p + 1 * sizeof(double)
// (sizeof(double) is almost always eight bytes)
++p; // Advance p by sizeof(double)
assert(*p == 13.4); // The double at memory beginning at address p has value 13.4
*(p + 2) = 29.8; // Change sizes[3] from 19.4 to 29.8
// Note earlier ++p and + 2 here => sizes[3]
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 ...
int* p = (int*)malloc(sizeof(int)); // Get some memory somewhere...
*p = 10; // Dereference the pointer to the memory, then write a value in
fn(*p); // Call a function, passing it the value at address p
(*p) += 3; // Change the value, adding 3 to it
free(p); // Release the memory back to the heap allocation library
W C ++ alokacja pamięci jest zwykle wykonywana przez new
operatora, a zwalnianie za pomocą delete
:
int* p = new int(10); // Memory for one int with initial value 10
delete p;
p = new int[10]; // Memory for ten ints with unspecified initial value
delete[] p;
p = new int[10](); // Memory for ten ints that are value initialised (to 0)
delete[] p;
Zobacz także inteligentne wskaźniki C ++ poniżej.
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()
lub delete
uniknięcia wycieku pamięci, programista musi działać na kopii wskaźnika ...
const char* p = asprintf("name: %s", name); // Common but non-Standard printf-on-heap
// Replace non-printable characters with underscores....
for (const char* q = p; *q; ++q)
if (!isprint(*q))
*q = '_';
printf("%s\n", p); // Only q was modified
free(p);
... lub starannie zaplanuj cofnięcie wszelkich zmian ...
const size_t n = ...;
p += n;
...
p -= n; // Restore earlier value...
free(p);
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 ...
{
std::unique_ptr<T> p{new T(42, "meaning")};
call_a_function(p);
// The function above might throw, so delete here is unreliable, but...
} // p's destructor's guaranteed to run "here", calling delete
... i shared_ptr
do własności udziałów (z wykorzystaniem liczenia referencji ) ...
{
auto p = std::make_shared<T>(3.14, "pi");
number_storage1.may_add(p); // Might copy p into its container
number_storage2.may_add(p); // Might copy p into its container } // p's destructor will only delete the T if neither may_add copied it
W języku C NULL
i 0
- 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:
const char* p_filename = NULL; // Or "= 0", or "= nullptr" in C++
int c;
while ((c = getopt(argc, argv, "f:")) != -1)
switch (c) {
case f: p_filename = optarg; break;
}
if (p_filename) // Only NULL converts to false
... // Only get here if -f flag specified
W C i C ++, podobnie jak wbudowane typy liczbowe niekoniecznie są domyślnie ustawione na 0
, ani bools
na false
, wskaźniki nie zawsze są ustawione na NULL
. Wszystkie te są ustawione na 0 / false / NULL, gdy są to static
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();
I new T(x, y, z);
przeprowadzają zerową inicjalizację elementów T, w tym wskaźników, podczas gdy new T;
nie).
Ponadto, gdy można przypisać 0
, NULL
a nullptr
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 do 0
, 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 ++ ...
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 do float*
- dostępu do pamięci w pamięci „GPU” zupełnie różnić się od pamięci, w której int
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.
p[1]
i *(p + 1)
identyczny ? To znaczy: Czy p[1]
i *(p + 1)
generuje te same instrukcje?
p
to tylko 2000: gdybyś miał inny wskaźnik p
, 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.
int a = 10;
int* ptr = &a;
printf("%d", *ptr); // With *ptr I'm dereferencing the pointer.
// Which means, I am asking the value pointed at by the pointer.
// ptr is pointing to the location in memory of the variable a.
// In a's location, we have 10. So, dereferencing gives this value.
// Since we have indirect control over a's location, we can modify its content using the pointer. This is an indirect way to access a.
*ptr = 20; // Now a's content is no longer 10, and has been modified to 20.
[]
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ę.
int a=4 ;
int *pA = &a ;
printf( "The REFERENCE/call number for the variable `a` is %p\n", pA ) ;
// The * causes pA to DEREFERENCE... `a` via "callnumber" `pA`.
printf( "%d\n", *pA ) ; // prints 4..
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.
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.
Kod i objaśnienia z Podstawy wskaźnika :
Operacja dereferencji rozpoczyna się od wskaźnika i podąża za strzałką, aby uzyskać dostęp do punktu. Celem może być sprawdzenie stanu pointee lub zmiana stanu pointee. Operacja dereferencji na wskaźniku działa tylko wtedy, gdy wskaźnik ma pointee - pointee musi zostać przydzielony, a wskaźnik musi być ustawiony tak, aby wskazywał na niego. Najczęstszym błędem w kodzie wskaźnika jest zapomnienie o konfiguracji pointee. Najczęstszą awarią środowiska wykonawczego z powodu tego błędu w kodzie jest nieudana operacja dereferencji. W Javie niepoprawne dereferencje zostaną oznaczone przez system wykonawczy grzecznie. W skompilowanych językach, takich jak C, C ++ i Pascal, nieprawidłowe dereferencje czasami się zawieszają, a innym razem psują pamięć w subtelny, losowy sposób.
void main() {
int* x; // Allocate the pointer x
x = malloc(sizeof(int)); // Allocate an int pointee,
// and set x to point to it
*x = 42; // Dereference x to store 42 in its pointee
}
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
Działa na zmiennej wskaźnika i zwraca wartość l równoważną wartości pod adresem wskaźnika. Nazywa się to „dereferencją” wskaźnika.
To powiedziawszy, możemy wyrejestrować wskaźnik bez dostępu do wartości, na którą wskazuje. Na przykład:
char *p = NULL;
*p;
Wyrejestrowaliśmy wskaźnik NULL bez dostępu do jego wartości. Lub możemy zrobić:
p1 = &(*p);
sz = sizeof(*p);
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.
*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.