Klarifikasi pointer ke pointer

142

Saya mengikuti tutorial ini tentang bagaimana cara kerja pointer ke pointer .

Izinkan saya mengutip bagian yang relevan:


    int i = 5, j = 6, k = 7;
    int *ip1 = &i, *ip2 = &j;

Sekarang kita bisa atur

    int **ipp = &ip1;

dan ippmenunjuk ke ip1poin mana i. *ippadalah ip1, dan **ippsekarang i, atau 5. Kita dapat menggambarkan situasinya, dengan notasi kotak-dan-panah yang kita kenal, seperti ini:

masukkan deskripsi gambar di sini

Kalau begitu kita katakan

    *ipp = ip2;

kami telah mengubah pointer yang ditunjuk oleh ipp(yaitu, ip1) untuk berisi salinan ip2, sehingga ( ip1) sekarang menunjuk ke j:

masukkan deskripsi gambar di sini


Pertanyaan saya adalah: Mengapa pada gambar kedua, ippmasih menunjuk ip1tetapi tidak ip2?

Blake
sumber
6
tolong, tambahkan jenis ippketika mendefinisikannya, jadi pertanyaan Anda selesai ;-)
zmo
1
Karena ippmenunjuk ke ip1hal *ipp = ip2yang sama sepertiip1 = ip2
Grijesh Chauhan
1
Bisakah kita berhenti menulis asterik yang jauh dari tipe runcing? int **ippjauh lebih tidak intuitif daripada int** ipp, terutama ketika **ippberarti hal yang sama sekali berbeda di luar deklarasi tipe.
Darkhogg
4
@Darkhogg int **ipptampaknya cukup intuitif bagi saya. Artinya, saya membuat **ippsebuah int. Yang mana yang benar.
ithisa
2
@ user54609 makna dan intuitif pribadi terpisah, pertimbangkan ini: Jenis ippini int**, jadi hanya menulis int**bukan magis " impdereference adalah int" pemahaman .
Manu343726

Jawaban:

143

Lupakan sejenak tentang analogi yang menunjuk. Apa yang benar-benar berisi pointer adalah alamat memori. The &adalah "alamat" operator - yakni mengembalikan alamat di memori obyek. The *Operator memberikan objek pointer mengacu, yaitu diberi pointer yang berisi alamat, ia mengembalikan objek pada alamat memori. Jadi ketika Anda melakukannya *ipp = ip2, apa yang Anda lakukan adalah *ippmendapatkan objek di alamat yang disimpan di ippmana ip1dan kemudian menetapkan ip1nilai yang disimpan ip2, yang merupakan alamat j.

Cukup
& -> Alamat
*-> Nilai di

Robert S. Barnes
sumber
14
& dan * tidak pernah semudah itu
Ray
7
Saya percaya sumber utama kebingungan adalah karena ambiguitas operator *, yang selama deklarasi variabel digunakan untuk menunjukkan bahwa variabel, pada kenyataannya, adalah penunjuk ke tipe data tertentu. Tetapi, di sisi lain, ini juga digunakan dalam pernyataan untuk mengakses isi variabel yang ditunjuk oleh pointer (operator dereferencing).
Lucas A.
43

Karena Anda mengubah nilai yang ditunjukkan oleh ippbukan nilai ipp. Jadi, ippmasih menunjuk ke ip1(nilai ipp), ip1nilai sekarang sama dengan ip2nilai, jadi keduanya menunjuk ke j.

Ini:

*ipp = ip2;

sama dengan:

ip1 = ip2;
Mendesis
sumber
11
Mungkin patut menunjukkan perbedaan antara int *ip1 = &idan *ipp = ip2;, yaitu jika Anda menghapus intdari pernyataan pertama maka tugasnya terlihat sangat mirip, tetapi *melakukan sesuatu yang sangat berbeda dalam dua kasus.
Crowman
22

Seperti kebanyakan pertanyaan pemula dalam tag C, pertanyaan ini dapat dijawab dengan kembali ke prinsip pertama:

  • Pointer adalah sejenis nilai.
  • Variabel berisi nilai.
  • The &Operator ternyata variabel menjadi pointer.
  • The *Operator ternyata pointer ke variabel.

(Secara teknis saya harus mengatakan "lvalue" daripada "variabel", tapi saya merasa lebih jelas untuk menggambarkan lokasi penyimpanan yang bisa berubah sebagai "variabel".)

Jadi kita punya variabel:

int i = 5, j = 6;
int *ip1 = &i, *ip2 = &j;

Variabel ip1 berisi pointer. The &Operator berubah imenjadi pointer dan bahwa nilai pointer ditugaskan untuk ip1. Jadi ip1 mengandung pointer ke i.

Variabel ip2 berisi pointer. The &Operator berubah jmenjadi pointer dan pointer yang ditugaskan untuk ip2. Jadi ip2 mengandung pointer ke j.

int **ipp = &ip1;

Variabel ippberisi pointer. The &Operator ternyata variabel ip1ke pointer dan bahwa nilai pointer ditugaskan untuk ipp. Jadi ippmengandung pointer ke ip1.

Mari kita simpulkan ceritanya sejauh ini:

  • i berisi 5
  • j berisi 6
  • ip1berisi "penunjuk ke i"
  • ip2berisi "penunjuk ke j"
  • ippberisi "penunjuk ke ip1"

Sekarang kita katakan

*ipp = ip2;

The *Operator ternyata kembali pointer ke variabel. Kami mengambil nilai ipp, yang merupakan "penunjuk ke ip1dan mengubahnya menjadi variabel. Variabel apa? ip1Tentu saja!

Karena itu, ini hanyalah cara lain untuk mengatakan

ip1 = ip2;

Jadi kami mengambil nilai ip2. Apa itu? "arahkan ke j". Kami menetapkan nilai penunjuk itu ip1, jadi ip1sekarang "penunjuk ke j"

Kami hanya mengubah satu hal: nilai ip1:

  • i berisi 5
  • j berisi 6
  • ip1berisi "penunjuk ke j"
  • ip2berisi "penunjuk ke j"
  • ippberisi "penunjuk ke ip1"

Kenapa ippmasih menunjuk ip1dan tidak ip2?

Variabel berubah saat Anda menetapkannya. Hitung tugas; tidak mungkin ada lebih banyak perubahan pada variabel daripada ada tugas! Anda mulai dengan menetapkan untuk i, j, ip1, ip2dan ipp. Anda kemudian menugaskan ke *ipp, yang seperti telah kita lihat artinya sama dengan "menetapkan untuk ip1". Karena Anda tidak menetapkan untuk ippkedua kalinya, itu tidak berubah!

Jika Anda ingin berubah ippmaka Anda harus benar-benar menetapkan untuk ipp:

ipp = &ip2;

misalnya.

Eric Lippert
sumber
21

Semoga kode ini dapat membantu.

#include <iostream>
#include <stdio.h>
using namespace std;

int main()
{
    int i = 5, j = 6, k = 7;
    int *ip1 = &i, *ip2 = &j;
    int** ipp = &ip1;
    printf("address of value i: %p\n", &i);
    printf("address of value j: %p\n", &j);
    printf("value ip1: %p\n", ip1);
    printf("value ip2: %p\n", ip2);
    printf("value ipp: %p\n", ipp);
    printf("address value of ipp: %p\n", *ipp);
    printf("value of address value of ipp: %d\n", **ipp);
    *ipp = ip2;
    printf("value ipp: %p\n", ipp);
    printf("address value of ipp: %p\n", *ipp);
    printf("value of address value of ipp: %d\n", **ipp);
}

itu output:

masukkan deskripsi gambar di sini

michaeltang
sumber
12

Pendapat saya yang sangat pribadi adalah bahwa gambar dengan panah mengarah ke sini atau itu membuat pointer lebih sulit untuk dipahami. Itu memang membuat mereka tampak seperti entitas abstrak dan misterius. Mereka tidak.

Seperti semua hal lain di komputer Anda, pointer adalah angka . Nama "pointer" hanyalah cara biasa untuk mengatakan "variabel yang berisi alamat".

Oleh karena itu, izinkan saya mengaduk berbagai hal dengan menjelaskan bagaimana komputer sebenarnya bekerja.

Kami memiliki int, ia memiliki nama idan nilai 5. Ini disimpan dalam memori. Seperti semua yang tersimpan di memori, ia perlu alamat, atau kami tidak akan dapat menemukannya. Katakanlah iberakhir di alamat 0x12345678 dan temannya jdengan nilai 6 berakhir tepat setelah itu. Dengan asumsi CPU 32-bit di mana int adalah 4 byte dan pointer 4 byte, maka variabel disimpan dalam memori fisik seperti ini:

Address     Data           Meaning
0x12345678  00 00 00 05    // The variable i
0x1234567C  00 00 00 06    // The variable j

Sekarang kita ingin menunjukkan variabel-variabel ini. Kami membuat satu pointer ke int int* ip1,, dan satu int* ip2. Seperti semua yang ada di komputer, variabel pointer ini dialokasikan di suatu tempat di memori juga. Mari kita asumsikan mereka berakhir di alamat yang berdekatan berikutnya dalam memori, segera setelah itu j. Kami menetapkan pointer berisi alamat variabel yang sebelumnya dialokasikan: ip1=&i;("salin alamat i ke ip1") dan ip2=&j. Apa yang terjadi di antara keduanya adalah:

Address     Data           Meaning
0x12345680  12 34 56 78    // The variable ip1(equal to address of i)
0x12345684  12 34 56 7C    // The variable ip2(equal to address of j)

Jadi yang kami dapatkan hanyalah beberapa byte memori yang berisi angka. Tidak ada panah mistis atau magis di mana pun terlihat.

Bahkan, hanya dengan melihat dump memori, kami tidak dapat memastikan apakah alamat 0x12345680 berisi intatau int*. Perbedaannya adalah bagaimana program kami memilih untuk menggunakan konten yang disimpan di alamat ini. (Tugas program kami sebenarnya hanya memberi tahu CPU apa yang harus dilakukan dengan angka-angka ini.)

Kemudian kami menambahkan satu lagi tipuan dengan int** ipp = &ip1;. Sekali lagi, kami hanya mendapatkan sepotong memori:

Address     Data           Meaning
0x12345688  12 34 56 80    // The variable ipp

Polanya memang tidak asing lagi. Namun sepotong 4 byte lainnya berisi angka.

Sekarang, jika kita memiliki memori dump RAM fiksi kecil di atas, kita bisa secara manual memeriksa di mana titik petunjuk ini. Kami mengintip apa yang disimpan di alamat ippvariabel dan menemukan konten 0x12345680. Yang tentu saja alamat tempat ip1penyimpanannya. Kita dapat pergi ke alamat itu, memeriksa isinya, dan menemukan alamatnya i, dan akhirnya kita bisa pergi ke alamat itu dan menemukan nomor 5.

Jadi jika kita mengambil konten ipp *ipp,, kita akan mendapatkan alamat variabel pointer ip1. Dengan menulis *ipp=ip2kita menyalin IP2 ke IP1, itu setara dengan ip1=ip2. Dalam kedua kasus kita akan mendapatkan

Address     Data           Meaning
0x12345680  12 34 56 7C    // The variable ip1
0x12345684  12 34 56 7C    // The variable ip2

(Contoh-contoh ini diberikan untuk CPU big endian)

Lundin
sumber
5
Meskipun saya mengambil poin Anda, ada nilai dalam berpikir pointer sebagai abstrak, entitas misterius. Setiap tertentu pelaksanaan pointer hanya nomor, tetapi strategi implementasi Anda sketsa bukan persyaratan dari sebuah implementasi, itu hanya strategi bersama. Pointer tidak perlu ukuran yang sama dengan int, pointer tidak perlu alamat dalam model memori virtual datar, dan sebagainya; ini hanyalah detail implementasi.
Eric Lippert
@ EricLippert Saya pikir kita bisa membuat contoh ini lebih abstrak dengan tidak menggunakan alamat memori aktual atau blok data. Jika itu adalah tabel yang menyatakan sesuatu seperti di location, value, variablemana lokasi 1,2,3,4,5dan nilainya berada A,1,B,C,3, ide pointer yang sesuai dapat dijelaskan dengan mudah tanpa menggunakan panah, yang secara inheren membingungkan. Dengan implementasi apa pun yang dipilih, nilai ada di beberapa lokasi, dan ini adalah bagian dari teka-teki yang menjadi dikaburkan saat memodelkan dengan panah.
MirroredFate
@ EricLippert Dalam pengalaman saya, sebagian besar calon programmer C yang memiliki masalah memahami pointer, adalah mereka yang diberi makan model abstrak, buatan. Abstraksi tidak membantu, karena seluruh tujuan bahasa C saat ini, adalah bahwa ia dekat dengan perangkat keras. Jika Anda belajar C tetapi tidak bermaksud menulis kode dekat dengan perangkat keras, Anda membuang-buang waktu . Java dll adalah pilihan yang jauh lebih baik jika Anda tidak ingin tahu cara kerja komputer, tetapi lakukan pemrograman tingkat tinggi.
Lundin
@ EricLippert Dan ya, berbagai implementasi yang tidak jelas dari pointer mungkin ada, di mana pointer tidak selalu sesuai dengan alamat. Tetapi menggambar panah tidak akan membantu Anda memahami cara kerjanya. Pada titik tertentu Anda harus meninggalkan pemikiran abstrak dan turun ke tingkat perangkat keras, jika tidak, Anda tidak boleh menggunakan C. Ada banyak bahasa modern yang jauh lebih cocok, yang dimaksudkan untuk pemrograman tingkat tinggi murni abstrak.
Lundin
@Lundin: Saya juga bukan penggemar diagram panah; Gagasan panah sebagai data adalah rumit. Saya lebih suka memikirkannya secara abstrak tetapi tanpa panah. The &operator pada variabel memberikan Anda koin yang mewakili variabel itu. The *operator pada koin yang memberikan Anda kembali variabel. Tidak perlu panah!
Eric Lippert
8

Perhatikan tugasnya:

ipp = &ip1;

hasil ippuntuk menunjuk ke ip1.

jadi untuk ippmenunjuk ke ip2, kita harus berubah dengan cara yang sama,

ipp = &ip2;

yang jelas tidak kita lakukan. Sebaliknya, kami mengubah nilai pada alamat yang ditunjuk oleh ipp.
Dengan melakukan mengikuti

*ipp = ip2;

kami hanya mengganti nilai yang disimpan di ip1.

ipp = &ip1, berarti *ipp = ip1 = &i,
Sekarang *ipp = ip2 = &j,.
Jadi, *ipp = ip2pada dasarnya sama dengan ip1 = ip2.

Dipto
sumber
5
ipp = &ip1;

Tidak ada tugas selanjutnya yang mengubah nilai ipp. Ini sebabnya masih menunjuk ke ip1.

Apa yang Anda lakukan dengan *ipp, yaitu, dengan ip1, tidak mengubah fakta yang ippmenunjuk ip1.

Daniel Daranas
sumber
5

Pertanyaan saya adalah: Mengapa pada gambar kedua, ipp masih menunjuk ke ip1 tetapi tidak ip2?

Anda menempatkan gambar yang bagus, saya akan mencoba membuat seni ascii yang bagus:

Seperti @ Robert-S-Barnes katakan dalam jawabannya: lupakan pointer , dan apa menunjuk ke apa, tetapi pikirkan dalam hal memori. Pada dasarnya, int*artinya berisi alamat variabel dan int**alamat variabel yang berisi alamat variabel. Kemudian Anda bisa menggunakan aljabar pointer untuk mengakses nilai atau alamat: &foomeans address of foo, and *foomeans value of the address contained in foo.

Jadi, karena pointer adalah tentang berurusan dengan memori, cara terbaik untuk benar-benar membuat "nyata" adalah untuk menunjukkan apa yang dilakukan aljabar pointer ke memori.

Jadi, inilah memori program Anda (disederhanakan untuk tujuan contoh):

name:    i   j ip1 ip2 ipp
addr:    0   1   2   3   4
mem : [   |   |   |   |   ]

ketika Anda melakukan kode awal Anda:

int i = 5, j = 6;
int *ip1 = &i, *ip2 = &j;

seperti inilah memori Anda:

name:    i   j ip1 ip2
addr:    0   1   2   3
mem : [  5|  6|  0|  1]

sana Anda dapat melihat ip1dan ip2mendapatkan alamat idan jdan ippmasih tidak ada. Jangan lupa bahwa alamat hanyalah bilangan bulat yang disimpan dengan tipe khusus.

Kemudian Anda mendeklarasikan dan mendefinisikan ippseperti:

int **ipp = &ip1;

jadi inilah ingatanmu:

name:    i   j ip1 ip2 ipp
addr:    0   1   2   3   4
mem : [  5|  6|  0|  1|  2]

dan kemudian, Anda mengubah nilai yang ditunjukkan oleh alamat yang disimpan di ippdalamnya, yaitu alamat yang disimpan di ip1:

*ipp = ip2;

memori program adalah

name:    i   j ip1 ip2 ipp
addr:    0   1   2   3   4
mem : [  5|  6|  1|  1|  2]

NB: sebagai int*adalah tipe khusus, saya lebih memilih untuk selalu menghindari menyatakan beberapa pointer pada baris yang sama, karena saya pikir int *x;atau int *x, *y;notasi dapat menyesatkan. Saya lebih suka menulisint* x; int* y;

HTH

zmo
sumber
dengan contoh Anda, nilai awal ip2seharusnya 3tidak 4.
Dipto
1
oh, saya baru saja mengubah memori sehingga cocok dengan urutan deklarasi. Saya kira saya sudah memperbaikinya?
zmo
5

Karena ketika Anda mengatakannya

*ipp = ip2

Anda mengatakan 'objek yang ditunjuk ipp' untuk menunjukkan arah memori yang ip2menunjuk.

Anda tidak mengatakan ippuntuk menunjukkan ip2.

Diego R. Alcantara
sumber
4

Jika Anda menambahkan operator dereference *ke pointer, Anda mengarahkan dari pointer ke objek menunjuk-ke.

Contoh:

int i = 0;
int *p = &i; // <-- N.B. the pointer declaration also uses the `*`
             //     it's not the dereference operator in this context
*p;          // <-- this expression uses the pointed-to object, that is `i`
p;           // <-- this expression uses the pointer object itself, that is `p`

Karena itu:

*ipp = ip2; // <-- you change the pointer `ipp` points to, not `ipp` itself
            //     therefore, `ipp` still points to `ip1` afterwards.
moooeeeep
sumber
3

Jika Anda ingin ippmenunjuk ip2, Anda harus mengatakannya ipp = &ip2;. Namun, ini akan ip1tetap menunjuk i.

Andrejovich
sumber
3

Sangat awal Anda atur,

ipp = &ip1;

Sekarang ganti sebagai,

*ipp = *&ip1 // Here *& becomes 1  
*ipp = ip1   // Hence proved 
Sunil Bojanapally
sumber
3

Pertimbangkan setiap variabel yang diwakili seperti ini:

type  : (name, adress, value)

jadi variabel Anda harus direpresentasikan seperti ini

int   : ( i ,  &i , 5 ); ( j ,  &j ,  6); ( k ,  &k , 5 )

int*  : (ip1, &ip1, &i); (ip1, &ip1, &j)

int** : (ipp, &ipp, &ip1)

Karena nilainya ippadalah &ip1begitu inktruksi:

*ipp = ip2;

mengubah nilai pada addess &ip1ke nilai ip2, yang artinya ip1diubah:

(ip1, &ip1, &i) -> (ip1, &ip1, &j)

Tapi ipptetap saja:

(ipp, &ipp, &ip1)

Jadi nilai ippstill &ip1yang artinya masih menunjuk ke ip1.

rullof
sumber
1

Karena Anda mengubah pointer *ipp. Itu berarti

  1. ipp (nama yang dapat diganti-ganti) ---- masuk ke dalam.
  2. di dalamnya ippadalah alamat ip1.
  3. sekarang *ippjadi pergi ke (alamat dalam) ip1.

Sekarang kita berada di ip1. *ipp(ie ip1) = ip2.
ip2mengandung alamat j.so ip1konten akan diganti dengan berisi ip2 (yaitu alamat j), KAMI TIDAK MENGUBAH ippKONTEN. ITU DIA.

user3286725
sumber
1

*ipp = ip2; menyiratkan:

Tetapkan ip2ke variabel yang ditunjuk oleh ipp. Jadi ini setara dengan:

ip1 = ip2;

Jika Anda ingin alamat ip2untuk disimpan ipp, cukup lakukan:

ipp = &ip2;

Sekarang ippmenunjuk ke ip2.

Rikayan Bandyopadhyay
sumber
0

ippdapat menyimpan nilai (yaitu menunjuk ke) pointer ke objek tipe pointer . Saat kamu melakukan

ipp = &ip2;  

kemudian ippberisi alamat variabel (pointer)ip2 , yaitu ( &ip2) dari tipe pointer ke pointer . Sekarang panah ippdi dalam gambar kedua akan menunjuk ke ip2.

Wiki mengatakan:
The *operator adalah operator dereference beroperasi pada variabel pointer, dan mengembalikan sebuah l-nilai (variabel) setara dengan nilai di alamat pointer. Ini disebut dereferencing pointer.

Menerapkan *operator pada ippderefrence ke nilai l pointer untukint mengetik. Nilai l yang didereferensi *ippadalah tipe pointerint , ia dapat menampung alamat inttipe data. Setelah pernyataan itu

ipp = &ip1;

ippmemegang alamat ip1dan *ippmemegang alamat (menunjuk ke) i. Anda dapat mengatakan bahwa itu *ippadalah alias ip1. Keduanya **ippdan *ip1alias untuk i.
Dengan melakukan

 *ipp = ip2;  

*ippdan ip2keduanya menunjuk ke lokasi yang sama tetapi ippmasih menunjuk ke ip1.

Apa yang *ipp = ip2;sebenarnya adalah bahwa itu menyalin isi ip2(alamat j) ke ip1(seperti *ippalias untuk ip1), pada dasarnya membuat pointer ip1dan ip2menunjuk ke objek yang sama ( j).
Jadi, pada gambar kedua, panahip1 dan ip2menunjuk ke jsementara ippmasih menunjuk ke ip1karena tidak ada modifikasi yang dilakukan untuk mengubah nilaiipp .

haccks
sumber