Apa yang dimaksud dengan "dereferencing" pointer?

541

Harap sertakan contoh dengan penjelasan.

asir
sumber
ini dapat membantu Anda: stackoverflow.com/questions/2795575/…
Harry Joy
24
int *p;akan mendefinisikan sebuah pointer ke integer, dan *pakan melakukan dereference pada pointer itu, yang berarti bahwa itu akan benar-benar mengambil data yang ditunjukkan oleh p.
Peyman
4
Binky's Pointer Fun ( cslibrary.stanford.edu/104 ) adalah video HEBAT tentang pointer yang dapat mengklarifikasi hal-hal. @ Erik- Anda senang memasang tautan Perpustakaan Stanford CS. Ada begitu banyak barang di sana ...
templatetypedef
6
Tanggapan Harry adalah kebalikan dari membantu di sini.
Jim Balter

Jawaban:

731

Meninjau terminologi dasar

Ini biasanya cukup baik - kecuali Anda pemrograman perakitan - untuk membayangkan sebuah pointer yang berisi alamat memori numerik, dengan 1 mengacu pada byte kedua dalam memori proses ini, 2 yang ketiga, 3 keempat dan seterusnya ....

  • Apa yang terjadi pada 0 dan byte pertama? Baiklah, kita akan membahasnya nanti - lihat null pointer di bawah ini.
  • Untuk definisi yang lebih akurat tentang apa yang disimpan pointer, dan bagaimana memori dan alamat berhubungan, lihat "Lebih banyak tentang alamat memori, dan mengapa Anda mungkin tidak perlu tahu" di akhir jawaban ini.

Ketika Anda ingin mengakses data / nilai dalam memori yang ditunjuk oleh pointer - isi alamat dengan indeks numerik tersebut - maka Anda melakukan referensi pada pointer tersebut.

Bahasa komputer yang berbeda memiliki notasi yang berbeda untuk memberi tahu kompiler atau juru bahasa bahwa Anda sekarang tertarik pada nilai (saat ini) yang diarahkan ke objek - Saya fokus di bawah pada C dan C ++.

Skenario penunjuk

Pertimbangkan dalam C, diberi pointer seperti di pbawah ini ...

const char* p = "abc";

... empat byte dengan nilai numerik yang digunakan untuk menyandikan huruf 'a', 'b', 'c', dan 0 byte untuk menunjukkan akhir data tekstual, disimpan di suatu tempat dalam memori dan alamat numeriknya data disimpan di p. Dengan cara ini C menyandikan teks dalam memori dikenal sebagai ASCIIZ .

Misalnya, jika string literal berada di alamat 0x1000 dan ppointer 32-bit pada 0x2000, konten memori akan menjadi:

Memory Address (hex)    Variable name    Contents
1000                                     'a' == 97 (ASCII)
1001                                     'b' == 98
1002                                     'c' == 99
1003                                     0
...
2000-2003               p                1000 hex

Perhatikan bahwa tidak ada variabel nama / identifier untuk alamat 0x1000, tapi kami tidak langsung dapat merujuk ke string literal menggunakan pointer menyimpan alamat: p.

Mendereferensi pointer

Untuk merujuk pada ppoin karakter , kami pmenggunakan salah satu dari notasi ini (sekali lagi, untuk 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]

Anda juga dapat memindahkan pointer melalui data yang diarahkan ke, merujuknya saat Anda pergi:

++p;  // Increment p so it's now 0x1001
assert(*p == 'b');  // p == 0x1001 which is where the 'b' is...

Jika Anda memiliki beberapa data yang dapat ditulis, maka Anda dapat melakukan hal-hal seperti ini:

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

Di atas, Anda harus tahu pada waktu kompilasi bahwa Anda akan memerlukan variabel yang dipanggil x, dan kode meminta kompiler untuk mengatur di mana ia harus disimpan, memastikan alamat akan tersedia melalui &x.

Mendereferensi dan mengakses anggota struktur data

Dalam C, jika Anda memiliki variabel yang merupakan penunjuk ke struktur dengan anggota data, Anda dapat mengakses anggota tersebut menggunakan ->operator dereferencing:

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_

Tipe data multi-byte

Untuk menggunakan sebuah pointer, sebuah program komputer juga memerlukan beberapa wawasan tentang tipe data yang ditunjukkan - jika tipe data tersebut membutuhkan lebih dari satu byte untuk diwakili, maka pointer biasanya menunjuk ke byte bernomor terendah dalam data.

Jadi, perhatikan contoh yang sedikit lebih rumit:

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]

Pointer ke memori yang dialokasikan secara dinamis

Kadang-kadang Anda tidak tahu berapa banyak memori yang Anda butuhkan sampai program Anda berjalan dan melihat data apa yang dilemparkan ke sana ... maka Anda dapat mengalokasikan memori secara dinamis menggunakan malloc. Ini adalah praktik umum untuk menyimpan alamat dalam sebuah pointer ...

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

Dalam C ++, alokasi memori biasanya dilakukan dengan newoperator, dan deallokasi dengan 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;

Lihat juga C ++ smart pointer di bawah ini.

Kehilangan dan bocornya alamat

Seringkali pointer mungkin satu-satunya indikasi di mana beberapa data atau buffer ada dalam memori. Jika penggunaan data / buffer yang sedang berlangsung dibutuhkan, atau kemampuan untuk memanggil free()atau deletemenghindari kebocoran memori, maka programmer harus beroperasi pada salinan pointer ...

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

... atau dengan hati-hati mengatur pembalikan setiap perubahan ...

const size_t n = ...;
p += n;
...
p -= n;  // Restore earlier value...
free(p);

C ++ pointer cerdas

Dalam C ++, praktik terbaik untuk menggunakan objek penunjuk pintar untuk menyimpan dan mengelola pointer, secara otomatis mendelokasi mereka ketika destruktor pointer pintar berjalan. Sejak C ++ 11, Perpustakaan Standar menyediakan dua, unique_ptrketika ada satu pemilik untuk objek yang dialokasikan ...

{
    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

... dan shared_ptruntuk kepemilikan saham (menggunakan penghitungan referensi ) ...

{
    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

Pointer kosong

Dalam C, NULLdan 0- dan juga dalam C ++ nullptr- dapat digunakan untuk menunjukkan bahwa pointer saat ini tidak memegang alamat memori variabel, dan tidak boleh dereferenced atau digunakan dalam aritmatika pointer. Sebagai contoh:

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

Dalam C dan C ++, sama seperti tipe numerik bawaan tidak selalu default untuk 0, atau boolsuntuk false, pointer tidak selalu diatur ke NULL. Semua ini diatur ke 0 / false / NULL ketika mereka staticvariabel atau (hanya C ++) variabel anggota langsung atau tidak langsung dari objek statis atau basis mereka, atau menjalani nol inisialisasi (misalnya new T();dan new T(x, y, z);melakukan inisialisasi nol pada anggota T termasuk pointer, sedangkan new T;tidak).

Lebih jauh, ketika Anda menetapkan 0, NULLdan nullptrke sebuah penunjuk, bit-bit di dalam penunjuk itu tidak harus semuanya disetel ulang: penunjuk itu mungkin tidak mengandung "0" pada tingkat perangkat keras, atau merujuk ke alamat 0 dalam ruang alamat virtual Anda. Compiler diperbolehkan untuk menyimpan sesuatu yang lain di sana jika ia memiliki alasan untuk, tapi apa pun yang dilakukannya - jika Anda datang dan membandingkan pointer ke 0, NULL, nullptratau pointer lain yang ditugaskan salah satu dari mereka, pekerjaan perbandingan keharusan seperti yang diharapkan. Jadi, di bawah kode sumber di tingkat kompiler, "NULL" berpotensi sedikit "ajaib" dalam bahasa C dan C ++ ...

Lebih banyak tentang alamat memori, dan mengapa Anda mungkin tidak perlu tahu

Lebih tepatnya, pointer yang diinisialisasi menyimpan pola-bit yang mengidentifikasi baik NULLatau alamat memori (sering virtual ).

Kasus sederhana adalah di mana ini adalah offset numerik ke seluruh ruang alamat virtual proses; dalam kasus yang lebih kompleks, pointer mungkin relatif terhadap beberapa area memori tertentu, yang dapat dipilih CPU berdasarkan register "segmen" CPU atau beberapa jenis id segmen yang dikodekan dalam pola bit, dan / atau mencari di tempat yang berbeda tergantung pada instruksi kode mesin menggunakan alamat.

Sebagai contoh, sebuah int*diinisialisasi dengan benar untuk menunjuk ke suatu intvariabel mungkin - setelah melakukan casting ke float*- akses memori dalam memori "GPU" cukup berbeda dari memori di mana intvariabel itu, kemudian setelah dilemparkan ke dan digunakan sebagai penunjuk fungsi itu mungkin menunjuk ke lebih lanjut opcode mesin penahan memori yang berbeda untuk program (dengan nilai numerik dari int*penunjuk yang acak dan tidak valid secara efektif dalam wilayah memori lainnya).

Bahasa pemrograman 3GL seperti C dan C ++ cenderung menyembunyikan kompleksitas ini, sehingga:

  • Jika kompiler memberi Anda pointer ke variabel atau fungsi, Anda dapat melakukan dereferensi secara bebas (selama variabel tidak dirusak / tidak dialokasikan sementara itu) dan itu adalah masalah kompiler apakah mis. Register segmen CPU tertentu perlu dipulihkan sebelumnya, atau instruksi kode mesin yang berbeda digunakan

  • Jika Anda mendapatkan pointer ke elemen dalam array, Anda bisa menggunakan pointer aritmatika untuk pindah ke tempat lain di array, atau bahkan untuk membentuk alamat satu-melewati-akhir array yang legal untuk dibandingkan dengan pointer ke elemen lainnya. dalam array (atau yang juga telah dipindahkan oleh pointer aritmatika ke nilai one-past-the-end yang sama); lagi di C dan C ++, terserah kompiler untuk memastikan ini "hanya berfungsi"

  • Fungsi OS tertentu, misalnya pemetaan memori bersama, dapat memberi Anda petunjuk, dan mereka akan "hanya bekerja" dalam kisaran alamat yang masuk akal bagi mereka

  • Upaya untuk memindahkan penunjuk hukum di luar batas ini, atau untuk melemparkan angka sewenang-wenang ke penunjuk, atau menggunakan penunjuk yang dilemparkan ke jenis yang tidak terkait, biasanya memiliki perilaku yang tidak ditentukan , sehingga harus dihindari di perpustakaan dan aplikasi tingkat tinggi, tetapi kode untuk OS, driver perangkat, dll Mungkin perlu mengandalkan perilaku yang tidak terdefinisi oleh Standar C atau C ++, yang tetap didefinisikan dengan baik oleh implementasi atau perangkat keras spesifik mereka.

Tony Delroy
sumber
adalah p[1] dan *(p + 1) identik ? Yaitu, Apakah p[1] dan *(p + 1)menghasilkan instruksi yang sama?
Pacerier
2
@Pacerier: dari 6.5.2.1/2 dalam N1570 draft C Standard (pertama kali saya temukan online) "Definisi operator subskrip [] adalah bahwa E1 [E2] identik dengan (* (E1) + (E2)) ). " - Saya tidak dapat membayangkan alasan mengapa kompiler tidak akan segera mengkonversikannya ke representasi yang identik pada tahap awal kompilasi, menerapkan optimisasi yang sama setelah itu, tetapi saya tidak melihat bagaimana orang dapat benar-benar membuktikan bahwa kode akan identik tanpa mensurvei setiap kompiler yang pernah ditulis.
Tony Delroy
3
@ Madu: nilai 1000 hex terlalu besar untuk dikodekan dalam satu byte (8 bit) memori: Anda hanya dapat menyimpan angka yang tidak ditandatangani dari 0 hingga 255 dalam satu byte. Jadi, Anda tidak dapat menyimpan 1000 hex pada "hanya" alamat 2000. Sebaliknya, sistem 32-bit akan menggunakan 32 bit - yang merupakan empat byte - dengan alamat dari 2000 hingga 2003. Sistem 64-bit akan menggunakan 64 bits - 8 byte - dari 2000 hingga 2007. Either way, alamat dasar phanya 2000: jika Anda memiliki pointer lain untuk pitu harus menyimpan 2000 dalam empat atau delapan byte. Semoga itu bisa membantu! Bersulang.
Tony Delroy
1
@TonyDelroy: Jika sebuah serikat uberisi array arr, baik gcc dan dentang akan mengenali bahwa nilai u.arr[i]mungkin mengakses penyimpanan yang sama dengan anggota serikat lainnya, tetapi tidak akan mengenali bahwa nilai *(u.arr+i)mungkin melakukannya. Saya tidak yakin apakah penulis dari kompiler tersebut berpikir bahwa yang terakhir memanggil UB, atau yang sebelumnya memanggil UB tetapi mereka harus memprosesnya dengan bermanfaat, tetapi mereka jelas melihat dua ekspresi sebagai berbeda.
supercat
3
Saya jarang melihat pointer dan penggunaannya dalam C / C ++ begitu ringkas dan sederhana dijelaskan.
kayleeFrye_onDeck
102

Mendereferensi pointer berarti mendapatkan nilai yang disimpan di lokasi memori yang ditunjuk oleh pointer. Operator * digunakan untuk melakukan ini, dan disebut operator dereferencing.

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.
Mahesh
sumber
15
Pointer tidak menunjuk ke suatu nilai , itu menunjuk ke suatu objek .
Keith Thompson
52
@KeithThompson Sebuah pointer tidak menunjuk ke suatu objek, itu menunjuk ke alamat memori, di mana sebuah objek (mungkin primitif) berada.
mg30rg
4
@ mg30rg: Saya tidak yakin perbedaan apa yang Anda buat. Nilai penunjuk adalah alamat. Objek, menurut definisi, adalah "wilayah penyimpanan data dalam lingkungan eksekusi, konten yang dapat mewakili nilai". Dan apa yang Anda maksud dengan "primitif"? Standar C tidak menggunakan istilah itu.
Keith Thompson
6
@KeithThompson Saya hampir tidak menunjukkan, bahwa Anda tidak benar-benar menambah nilai pada jawabannya, Anda hanya mengolok-olok terminologi (dan melakukan itu juga salah). Nilai penunjuk pasti adalah alamat, itulah yang "menunjuk" ke alamat memori. Kata "objek" di dunia OOPdriven kita dapat menyesatkan, karena dapat ditafsirkan sebagai "instance kelas" (ya, saya tidak tahu bahwa pertanyaannya berlabel [C] dan bukan [C ++]), dan saya menggunakan kata itu "primitif" seperti kebalikan dari "copmlex" (struktur data seperti struct atau kelas).
mg30rg
3
Biarkan saya tambahkan ke jawaban ini bahwa operator subskrip array []juga menunjuk sebuah pointer ( a[b]didefinisikan sebagai mean *(a + b)).
cmaster - mengembalikan monica
20

Pointer adalah "referensi" ke nilai .. seperti halnya nomor panggilan perpustakaan adalah referensi ke sebuah buku. "Dereferencing" nomor panggilan secara fisik melewati dan mengambil buku itu.

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

Jika buku itu tidak ada, pustakawan mulai berteriak, menutup perpustakaan, dan beberapa orang ditetapkan untuk menyelidiki penyebab seseorang akan menemukan buku yang tidak ada di sana.

bobobobo
sumber
17

Dengan kata sederhana, dereferencing berarti mengakses nilai dari lokasi memori tertentu yang ditunjuk oleh pointer itu.

Fahad Naeem
sumber
7

Kode dan penjelasan dari Pointer Basics :

Operasi dereferensi dimulai pada penunjuk dan mengikuti panahnya untuk mengakses penunjuknya. Tujuannya mungkin untuk melihat status pointee atau mengubah status pointee. Operasi dereference pada pointer hanya berfungsi jika pointer memiliki pointee - pointee harus dialokasikan dan pointer harus diatur untuk menunjuk ke sana. Kesalahan paling umum dalam kode pointer lupa mengatur pointee. Kecelakaan runtime paling umum karena kesalahan dalam kode adalah operasi dereference gagal. Di Jawa dereference yang salah akan ditandai dengan sopan oleh sistem runtime. Dalam bahasa yang dikompilasi seperti C, C ++, dan Pascal, dereference yang salah terkadang akan crash, dan di lain waktu merusak memori dalam beberapa cara yang halus dan acak.

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   
}
atp
sumber
Anda benar-benar harus mengalokasikan memori untuk tempat x seharusnya menunjuk. Contoh Anda memiliki perilaku yang tidak terdefinisi.
Peyman
3

Saya pikir semua jawaban sebelumnya salah, karena menyatakan bahwa dereferencing berarti mengakses nilai aktual. Wikipedia memberikan definisi yang benar sebagai gantinya: https://en.wikipedia.org/wiki/Dereference_operator

Ini beroperasi pada variabel pointer, dan mengembalikan nilai-l yang setara dengan nilai pada alamat pointer. Ini disebut "dereferencing" pointer.

Yang mengatakan, kita dapat melakukan dereferensi pointer tanpa pernah mengakses nilai yang ditunjukkannya. Sebagai contoh:

char *p = NULL;
*p;

Kami mendereferensi pointer NULL tanpa mengakses nilainya. Atau kita bisa melakukan:

p1 = &(*p);
sz = sizeof(*p);

Sekali lagi, dereferencing, tetapi tidak pernah mengakses nilainya. Kode seperti itu TIDAK akan macet: Kecelakaan terjadi ketika Anda benar-benar mengakses data dengan pointer yang tidak valid. Namun, sayangnya, menurut standar, penereferensian pointer yang tidak valid adalah perilaku yang tidak terdefinisi (dengan beberapa pengecualian), bahkan jika Anda tidak mencoba menyentuh data aktual.

Jadi singkatnya: mendereferensi pointer berarti menerapkan operator dereferensi untuk itu. Operator itu hanya mengembalikan nilai-l untuk penggunaan Anda di masa depan.

stsp
sumber
baik, Anda men-decereferensi pointer NULL, yang akan menyebabkan kesalahan segmentasi.
arjun gaur
di atas itu Anda mencari 'operator dereferencing' dan bukan 'dereferencing pointer' yang sebenarnya berarti mendapatkan nilai / mengakses nilai di lokasi memori yang ditunjuk oleh pointer.
arjun gaur
Sudahkah Anda mencoba? Aku melakukannya. Berikut ini tidak crash: `#include <stdlib.h> int main () {char * p = NULL; * p; return 0; } `
stsp
1
@stsp Apakah karena kode tidak crash sekarang tidak berarti bahwa itu tidak akan terjadi lagi, atau pada sistem lain.
1
*p;menyebabkan perilaku yang tidak terdefinisi. Meskipun Anda benar bahwa dereferencing tidak mengakses nilai per se , kode *p; tersebut mengakses nilai.
MM