Apa saja hambatan untuk memahami petunjuk dan apa yang bisa dilakukan untuk mengatasinya? [Tutup]

449

Mengapa petunjuk merupakan faktor utama kebingungan bagi banyak mahasiswa baru, dan bahkan lama, di C atau C ++? Apakah ada alat atau proses pemikiran yang membantu Anda memahami bagaimana pointer bekerja di variabel, fungsi, dan di luar level?

Apa saja hal-hal praktik yang baik yang dapat dilakukan untuk membawa seseorang ke level, "Ah-hah, saya mengerti," tanpa membuat mereka terjebak dalam konsep keseluruhan? Pada dasarnya, bor skenario seperti.

David McGraw
sumber
20
Tesis dari pertanyaan ini adalah bahwa pointer sulit dimengerti. Pertanyaan itu tidak menawarkan bukti bahwa petunjuk lebih sulit untuk dipahami daripada yang lainnya.
bmargulies
14
Mungkin saya kehilangan sesuatu (karena saya kode dalam bahasa GCC) tetapi saya selalu berpikir jika pointer dalam memori sebagai struktur Key-> Value. Karena mahal untuk mengedarkan sejumlah besar data dalam suatu program, Anda membuat struktur (nilai) dan mengopernya dengan pointer / referensi (kunci) karena kuncinya adalah representasi yang jauh lebih kecil dari struktur yang lebih besar. Bagian yang sulit adalah ketika Anda perlu membandingkan dua pointer / referensi (apakah Anda membandingkan kunci atau nilai-nilai) yang membutuhkan lebih banyak pekerjaan untuk membobol data yang terkandung dalam struktur (nilai).
Evan Plaice
2
@ Wolfpack'08 "Sepertinya saya memori dalam alamat akan selalu menjadi int." - Maka seharusnya bagi Anda bahwa tidak ada yang memiliki tipe, karena mereka semua hanya bit dalam memori. "Sebenarnya, tipe pointer adalah tipe var yang menunjuk pointer ke" - Tidak, tipe pointer adalah pointer ke tipe var yang menunjuk pointer - yang alami dan harus jelas.
Jim Balter
2
Saya selalu bertanya-tanya apa yang begitu sulit untuk dipahami dalam fakta bahwa variabel (dan fungsi) hanya blok memori dan pointer adalah variabel yang menyimpan alamat memori. Model pemikiran yang mungkin terlalu praktis ini mungkin tidak mengesankan semua penggemar konsep abstrak, tetapi sangat membantu untuk memahami bagaimana pointer bekerja.
Christian Rau
8
Singkatnya, siswa mungkin tidak mengerti karena mereka tidak mengerti dengan benar, atau sama sekali, bagaimana memori komputer secara umum, dan khususnya "model memori" C bekerja. Buku ini Programming from the Ground Up memberikan pelajaran yang sangat bagus tentang topik-topik ini.
Abbafei

Jawaban:

745

Pointer adalah konsep yang bagi banyak orang dapat membingungkan pada awalnya, khususnya ketika harus menyalin nilai pointer di sekitar dan masih merujuk pada blok memori yang sama.

Saya telah menemukan bahwa analogi terbaik adalah dengan menganggap pointer sebagai selembar kertas dengan alamat rumah di atasnya, dan memori memblokir referensi sebagai rumah yang sebenarnya. Segala macam operasi dengan demikian dapat dengan mudah dijelaskan.

Saya telah menambahkan beberapa kode Delphi di bawah ini, dan beberapa komentar yang sesuai. Saya memilih Delphi karena bahasa pemrograman utama saya yang lain, C #, tidak menunjukkan hal-hal seperti kebocoran memori dengan cara yang sama.

Jika Anda hanya ingin mempelajari konsep pointer tingkat tinggi, maka Anda harus mengabaikan bagian yang berlabel "Tata letak memori" dalam penjelasan di bawah ini. Mereka dimaksudkan untuk memberikan contoh seperti apa ingatan itu setelah operasi, tetapi sifatnya lebih rendah. Namun, untuk menjelaskan secara akurat bagaimana buffer overruns benar-benar bekerja, penting agar saya menambahkan diagram ini.

Penafian: Untuk semua maksud dan tujuan, penjelasan ini dan contoh tata letak memori sangat disederhanakan. Ada lebih banyak overhead dan lebih banyak detail yang perlu Anda ketahui jika Anda perlu berurusan dengan memori pada tingkat rendah. Namun, untuk maksud menjelaskan memori dan pointer, itu cukup akurat.


Mari kita asumsikan kelas THouse yang digunakan di bawah terlihat seperti ini:

type
    THouse = class
    private
        FName : array[0..9] of Char;
    public
        constructor Create(name: PChar);
    end;

Ketika Anda menginisialisasi objek rumah, nama yang diberikan kepada konstruktor disalin ke bidang pribadi FName. Ada alasannya itu didefinisikan sebagai array ukuran tetap.

Dalam memori, akan ada beberapa overhead yang terkait dengan alokasi rumah, saya akan menggambarkan ini di bawah ini seperti ini:

--- [ttttNNNNNNNNNNNN] ---
     ^ ^
     | |
     | + - array FName
     |
     + - overhead

Area "tttt" adalah overhead, biasanya akan ada lebih dari ini untuk berbagai jenis runtime dan bahasa, seperti 8 atau 12 byte. Sangat penting bahwa nilai apa pun yang disimpan di area ini tidak akan pernah diubah oleh apa pun selain pengalokasi memori atau rutinitas sistem inti, atau Anda berisiko menabrak program.


Alokasikan memori

Dapatkan seorang wirausahawan untuk membangun rumah Anda, dan memberi Anda alamatnya ke rumah itu. Berbeda dengan dunia nyata, alokasi memori tidak dapat diberitahu di mana harus mengalokasikan, tetapi akan menemukan tempat yang cocok dengan ruang yang cukup, dan melaporkan kembali alamat ke memori yang dialokasikan.

Dengan kata lain, wirausahawan akan memilih tempat.

THouse.Create('My house');

Tata letak memori:

--- [ttttNNNNNNNNNNNN] ---
    1234Rumahku

Simpan variabel dengan alamat

Tuliskan alamatnya ke rumah baru Anda di atas selembar kertas. Makalah ini akan berfungsi sebagai referensi Anda ke rumah Anda. Tanpa selembar kertas ini, Anda tersesat, dan tidak dapat menemukan rumah, kecuali Anda sudah ada di dalamnya.

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...

Tata letak memori:

    h
    v
--- [ttttNNNNNNNNNNNN] ---
    1234Rumahku

Salin nilai penunjuk

Tulis saja alamatnya di selembar kertas baru. Anda sekarang memiliki dua lembar kertas yang akan membawa Anda ke rumah yang sama, bukan dua rumah terpisah. Setiap upaya untuk mengikuti alamat dari satu kertas dan mengatur ulang furnitur di rumah itu akan membuatnya tampak bahwa rumah lain telah dimodifikasi dengan cara yang sama, kecuali Anda dapat secara eksplisit mendeteksi bahwa itu sebenarnya hanya satu rumah.

Catatan Ini biasanya konsep yang saya punya masalah paling menjelaskan kepada orang, dua pointer tidak berarti dua objek atau blok memori.

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := h1; // copies the address, not the house
    ...
    h1
    v
--- [ttttNNNNNNNNNNNN] ---
    1234Rumahku
    ^
    h2

Membebaskan memori

Hancurkan rumah. Anda kemudian dapat menggunakan kembali kertas untuk alamat baru jika Anda inginkan, atau menghapusnya untuk melupakan alamat ke rumah yang sudah tidak ada.

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...
    h.Free;
    h := nil;

Di sini saya pertama kali membangun rumah, dan mendapatkan alamatnya. Lalu saya melakukan sesuatu ke rumah (gunakan, ... kode, dibiarkan sebagai latihan untuk pembaca), dan kemudian saya membebaskannya. Terakhir saya menghapus alamat dari variabel saya.

Tata letak memori:

    h <- +
    v + - sebelum gratis
--- [ttttNNNNNNNNNNN] --- |
    1234Rumah saya <- +

    h (sekarang tidak menunjukkan apa-apa) <- +
                                + - setelah gratis
---------------------- | (catatan, memori mungkin masih
    rumah xx34My <- + mengandung beberapa data)

Pointer menggantung

Anda memberi tahu pengusaha Anda untuk menghancurkan rumah, tetapi Anda lupa untuk menghapus alamat dari selembar kertas Anda. Ketika nanti Anda melihat selembar kertas, Anda lupa bahwa rumah itu tidak ada lagi, dan pergi mengunjunginya, dengan hasil yang gagal (lihat juga bagian tentang referensi yang tidak valid di bawah).

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...
    h.Free;
    ... // forgot to clear h here
    h.OpenFrontDoor; // will most likely fail

Menggunakan hsetelah panggilan ke .Free mungkin berhasil, tapi itu hanya keberuntungan belaka. Kemungkinan besar itu akan gagal, di tempat pelanggan, di tengah operasi kritis.

    h <- +
    v + - sebelum gratis
--- [ttttNNNNNNNNNNN] --- |
    1234Rumah saya <- +

    h <- +
    v + - setelah gratis
---------------------- |
    xx34My House <- +

Seperti yang Anda lihat, ia masih menunjuk ke sisa-sisa data dalam memori, tetapi karena itu mungkin tidak lengkap, menggunakannya seperti sebelumnya mungkin gagal.


Kebocoran memori

Anda kehilangan selembar kertas dan tidak dapat menemukan rumah. Rumah itu masih berdiri di suatu tempat, dan ketika nanti Anda ingin membangun rumah baru, Anda tidak dapat menggunakan kembali tempat itu.

var
    h: THouse;
begin
    h := THouse.Create('My house');
    h := THouse.Create('My house'); // uh-oh, what happened to our first house?
    ...
    h.Free;
    h := nil;

Di sini kita menimpa isi hvariabel dengan alamat rumah baru, tetapi yang lama masih berdiri ... di suatu tempat. Setelah kode ini, tidak ada cara untuk mencapai rumah itu, dan itu akan tetap berdiri. Dengan kata lain, memori yang dialokasikan akan tetap dialokasikan hingga aplikasi ditutup, pada titik mana sistem operasi akan merobohkannya.

Tata letak memori setelah alokasi pertama:

    h
    v
--- [ttttNNNNNNNNNNNN] ---
    1234Rumahku

Tata letak memori setelah alokasi kedua:

                       h
                       v
--- [ttttNNNNNNNNNNN] --- [ttttNNNNNNNNNNNN]
    1234Rumahku 5678Rumahku

Cara yang lebih umum untuk mendapatkan metode ini adalah lupa untuk membebaskan sesuatu, alih-alih menimpa seperti di atas. Dalam istilah Delphi, ini akan terjadi dengan metode berikut:

procedure OpenTheFrontDoorOfANewHouse;
var
    h: THouse;
begin
    h := THouse.Create('My house');
    h.OpenFrontDoor;
    // uh-oh, no .Free here, where does the address go?
end;

Setelah metode ini dieksekusi, tidak ada tempat dalam variabel kami bahwa alamat ke rumah ada, tetapi rumah itu masih ada di luar sana.

Tata letak memori:

    h <- +
    v + - sebelum kehilangan pointer
--- [ttttNNNNNNNNNNN] --- |
    1234Rumah saya <- +

    h (sekarang tidak menunjukkan apa-apa) <- +
                                + - setelah kehilangan pointer
--- [ttttNNNNNNNNNNN] --- |
    1234Rumah saya <- +

Seperti yang Anda lihat, data lama dibiarkan utuh dalam memori, dan tidak akan digunakan kembali oleh pengalokasi memori. Pengalokasi melacak area memori mana yang telah digunakan, dan tidak akan menggunakannya kembali kecuali Anda membebaskannya.


Membebaskan memori tetapi menyimpan referensi (sekarang tidak valid)

Hancurkan rumah, hapus salah satu dari selembar kertas tetapi Anda juga memiliki selembar kertas dengan alamat lama, ketika Anda pergi ke alamat, Anda tidak akan menemukan rumah, tetapi Anda mungkin menemukan sesuatu yang menyerupai reruntuhan dari satu.

Mungkin Anda bahkan akan menemukan rumah, tetapi bukan rumah yang semula Anda berikan alamatnya, dan dengan demikian segala upaya untuk menggunakannya seolah milik Anda mungkin gagal total.

Kadang-kadang Anda bahkan mungkin menemukan bahwa alamat tetangga memiliki rumah yang agak besar di atasnya yang menempati tiga alamat (Main Street 1-3), dan alamat Anda pergi ke tengah rumah. Setiap upaya untuk memperlakukan bagian dari rumah 3-alamat yang besar itu sebagai satu rumah kecil mungkin juga gagal total.

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := h1; // copies the address, not the house
    ...
    h1.Free;
    h1 := nil;
    h2.OpenFrontDoor; // uh-oh, what happened to our house?

Di sini rumah itu dirobohkan, melalui referensi di h1, dan sementara h1dibersihkan juga, h2masih memiliki alamat lama, ketinggalan zaman. Akses ke rumah yang tidak lagi berdiri mungkin atau mungkin tidak berfungsi.

Ini adalah variasi dari penunjuk menggantung di atas. Lihat tata letak memorinya.


Buffer dibanjiri

Anda memindahkan lebih banyak barang ke rumah daripada yang bisa Anda muat, tumpah ke rumah atau halaman tetangga. Ketika pemilik rumah tetangga nanti pulang, dia akan menemukan segala hal yang akan dia anggap miliknya.

Ini adalah alasan saya memilih array ukuran tetap. Untuk mengatur panggung, asumsikan bahwa rumah kedua yang kita alokasikan akan, untuk beberapa alasan, ditempatkan sebelum yang pertama di memori. Dengan kata lain, rumah kedua akan memiliki alamat yang lebih rendah daripada yang pertama. Juga, mereka dialokasikan tepat di sebelah satu sama lain.

Jadi, kode ini:

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := THouse.Create('My other house somewhere');
                         ^-----------------------^
                          longer than 10 characters
                         0123456789 <-- 10 characters

Tata letak memori setelah alokasi pertama:

                        h1
                        v
----------------------- [ttttNNNNNNNNNNN]
                        5678 rumah saya

Tata letak memori setelah alokasi kedua:

    h2 h1
    ay
--- [ttttNNNNNNNNNNN] ---- [ttttNNNNNNNNNNNN]
    1234 Rumahku di suatu tempat
                        ^ --- + - ^
                            |
                            + - ditimpa

Bagian yang paling sering menyebabkan kerusakan adalah ketika Anda menimpa bagian penting dari data yang Anda simpan yang benar-benar tidak boleh diubah secara acak. Misalnya itu mungkin tidak menjadi masalah bahwa bagian dari nama h1-house telah diubah, dalam hal menabrak program, tetapi menimpa overhead objek kemungkinan besar akan crash ketika Anda mencoba menggunakan objek yang rusak, seperti yang akan menimpa tautan yang disimpan ke objek lain di objek.


Daftar tertaut

Ketika Anda mengikuti alamat pada selembar kertas, Anda sampai di sebuah rumah, dan di rumah itu ada selembar kertas lain dengan alamat baru di atasnya, untuk rumah berikutnya dalam rantai, dan seterusnya.

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('Home');
    h2 := THouse.Create('Cabin');
    h1.NextHouse := h2;

Di sini kami membuat tautan dari rumah kami ke kabin kami. Kita dapat mengikuti rantai sampai sebuah rumah tidak memiliki NextHousereferensi, yang berarti itu adalah yang terakhir. Untuk mengunjungi semua rumah kami, kami dapat menggunakan kode berikut:

var
    h1, h2: THouse;
    h: THouse;
begin
    h1 := THouse.Create('Home');
    h2 := THouse.Create('Cabin');
    h1.NextHouse := h2;
    ...
    h := h1;
    while h <> nil do
    begin
        h.LockAllDoors;
        h.CloseAllWindows;
        h := h.NextHouse;
    end;

Tata letak memori (menambahkan NextHouse sebagai tautan dalam objek, dicatat dengan empat LLLL dalam diagram di bawah ini):

    h1 h2
    ay
--- [ttttNNNNNNNNNNLLLL] ---- [ttttNNNNNNNNNNNNLLLL]
    1234Rumah + 5678 Kabin +
                   | ^ |
                   + -------- + * (tanpa tautan)

Dalam istilah dasar, apa itu alamat memori?

Alamat memori pada dasarnya hanya berupa angka. Jika Anda menganggap memori sebagai array besar byte, byte pertama memiliki alamat 0, yang berikutnya alamat 1 dan seterusnya ke atas. Ini disederhanakan, tetapi cukup baik.

Jadi tata letak memori ini:

    h1 h2
    ay
--- [ttttNNNNNNNNNNN] --- [ttttNNNNNNNNNNNN]
    1234Rumahku 5678Rumahku

Mungkin memiliki dua alamat ini (paling kiri - adalah alamat 0):

  • h1 = 4
  • h2 = 23

Yang artinya daftar tertaut kami di atas mungkin benar-benar terlihat seperti ini:

    h1 (= 4) h2 (= 28)
    ay
--- [ttttNNNNNNNNNNLLLL] ---- [ttttNNNNNNNNNNNNLLLL]
    1234Rumah 0028 5678Kabin 0000
                   | ^ |
                   + -------- + * (tanpa tautan)

Biasanya menyimpan alamat yang "tidak menunjuk ke mana-mana" sebagai alamat-nol.


Dalam istilah dasar, apa itu pointer?

Pointer hanyalah variabel yang menyimpan alamat memori. Anda biasanya dapat meminta bahasa pemrograman untuk memberikan nomornya, tetapi sebagian besar bahasa pemrograman dan runtime mencoba menyembunyikan fakta bahwa ada angka di bawahnya, hanya karena angka itu sendiri tidak benar-benar memiliki arti bagi Anda. Yang terbaik adalah menganggap pointer sebagai kotak hitam, yaitu. Anda tidak benar-benar tahu atau peduli tentang bagaimana itu sebenarnya dilaksanakan, asalkan berfungsi.

Lasse V. Karlsen
sumber
59
Buffer overrun sangat lucu. "Tetangga pulang, memecahkan tengkoraknya yang tergelincir pada sampahmu, dan menuntutmu untuk dilupakan."
gtd
12
Ini penjelasan yang bagus tentang konsep itu, tentu saja. Konsep BUKAN hal yang saya anggap membingungkan tentang pointer, jadi seluruh esai ini agak sia-sia.
Breton
10
Tetapi hanya untuk bertanya, apa yang menurut Anda membingungkan tentang petunjuk?
Lasse V. Karlsen
11
Saya telah meninjau kembali posting ini beberapa kali sejak Anda menulis jawabannya. Klarifikasi Anda dengan kode sangat bagus, dan saya menghargai Anda mengunjungi kembali untuk menambah / menyaring lebih banyak pemikiran. Bravo Lasse!
David McGraw
3
Tidak ada cara satu halaman teks (tidak peduli berapa lama itu) dapat meringkas setiap nuansa manajemen memori, referensi, pointer, dll. Karena 465 orang telah memilih ini, saya akan mengatakan itu berfungsi sebagai cukup baik halaman awal pada informasi. Apakah ada lagi yang harus dipelajari? Tentu, kapan bukan?
Lasse V. Karlsen
153

Di kelas Comp Sci pertama saya, kami melakukan latihan berikut. Memang, ini adalah ruang kuliah dengan sekitar 200 siswa di dalamnya ...

Profesor menulis di papan tulis: int john;

John berdiri

Profesor menulis: int *sally = &john;

Sally berdiri, menunjuk ke john

Profesor: int *bill = sally;

Bill berdiri, menunjuk John

Profesor: int sam;

Sam berdiri

Profesor: bill = &sam;

Bill sekarang menunjuk ke Sam.

Saya pikir Anda mendapatkan idenya. Saya pikir kami menghabiskan sekitar satu jam untuk melakukan ini, sampai kami membahas dasar-dasar penugasan pointer.

Tryke
sumber
5
Saya tidak berpikir saya salah. Niat saya adalah untuk mengubah nilai variabel menunjuk-ke ​​dari John ke Sam. Agak sulit untuk diwakili oleh orang-orang, karena sepertinya Anda mengubah nilai kedua petunjuk.
Tryke
24
Tapi alasan mengapa ini membingungkan adalah karena tidak seperti John bangkit dari tempat duduknya dan kemudian sam duduk, seperti yang bisa kita bayangkan. Ini lebih seperti sam datang dan memasukkan tangannya ke john dan mengkloning pemrograman sam ke tubuh john, seperti tenun hugo dalam matriks yang dimuat ulang.
Breton
59
Lebih seperti Sam mengambil tempat duduk John, dan John melayang di sekitar ruangan sampai ia menabrak sesuatu yang kritis dan menyebabkan segfault.
just_wes
2
Saya pribadi menemukan contoh ini tidak perlu rumit. Prof saya menyuruh saya untuk menunjuk lampu dan berkata "tangan Anda adalah penunjuk ke objek cahaya".
Celeritas
Masalah dengan jenis contoh ini adalah pointer ke X dan X tidak sama. Dan ini tidak digambarkan dengan orang-orang.
Isaac Nequittepas
124

Sebuah analogi yang saya temukan bermanfaat untuk menjelaskan pointer adalah hyperlink. Sebagian besar orang dapat memahami bahwa tautan pada halaman web 'menunjuk' ke halaman lain di internet, dan jika Anda dapat menyalin & menempelkan hyperlink itu, maka keduanya akan menunjuk ke halaman web asli yang sama. Jika Anda pergi dan mengedit halaman asli itu, maka ikuti salah satu dari tautan tersebut (petunjuk) Anda akan mendapatkan halaman baru yang diperbarui.

Wilka
sumber
15
Saya sangat menyukai ini. Tidak sulit untuk melihat bahwa menulis hyperlink dua kali tidak membuat dua situs web muncul (sama seperti int *a = btidak membuat dua salinan *b).
detly
4
Ini sebenarnya sangat intuitif dan sesuatu yang setiap orang harus dapat hubungkan. Meskipun ada banyak skenario di mana analogi ini berantakan. Sangat bagus untuk pengenalan singkat. +1
Brian Wigginton
Tautan ke halaman yang dibuka dua kali biasanya membuat dua contoh halaman web yang hampir sepenuhnya independen. Saya pikir hyperlink bisa menjadi analogi yang baik untuk seorang konstruktor, tetapi tidak untuk sebuah pointer.
Utkan Gezer
@ThoAppelsin Tidak selalu benar, jika Anda mengakses halaman web html statis misalnya, Anda sedang mengakses satu file di server.
dramzy
5
Anda terlalu memikirkannya. Hyperlink menunjuk ke file di server, itulah tingkat analoginya.
dramzy
48

Pointer alasan tampaknya membingungkan begitu banyak orang adalah bahwa mereka kebanyakan datang dengan sedikit atau tanpa latar belakang dalam arsitektur komputer. Karena banyak yang tampaknya tidak memiliki gagasan tentang bagaimana komputer (mesin) sebenarnya diimplementasikan - bekerja di C / C ++ tampaknya asing.

Latihan adalah meminta mereka untuk mengimplementasikan mesin virtual sederhana berbasis bytecode (dalam bahasa apa pun yang mereka pilih, python berfungsi dengan baik untuk ini) dengan set instruksi yang difokuskan pada operasi pointer (memuat, menyimpan, menangani langsung / tidak langsung). Kemudian minta mereka untuk menulis program sederhana untuk set instruksi itu.

Apa pun yang membutuhkan sedikit lebih dari penambahan sederhana akan melibatkan pointer dan mereka pasti akan mendapatkannya.

JSN
sumber
2
Menarik. Tidak tahu bagaimana memulai tentang ini. Ada sumber daya untuk dibagikan?
Karolis
1
Saya setuju. Sebagai contoh, saya belajar memprogram dalam perakitan sebelum C dan mengetahui bagaimana register bekerja, belajar pointer itu mudah. Faktanya, tidak banyak yang dipelajari, semuanya menjadi sangat alami.
Milan Babuškov
Ambil CPU dasar, ucapkan sesuatu yang menjalankan mesin pemotong rumput atau mesin cuci piring dan implementasikan. Atau subset ARM atau MIPS yang sangat sangat mendasar. Keduanya memiliki ISA yang sangat sederhana.
Daniel Goldberg
1
Mungkin perlu menunjukkan bahwa pendekatan pendidikan ini telah dianjurkan / dipraktikkan oleh Donald Knuth sendiri. Seni Pemrograman Komputer Knuth menggambarkan arsitektur hipotetis sederhana dan meminta siswa untuk mengimplementasikan solusi untuk mempraktikkan masalah dalam bahasa majelis hipotetis untuk arsitektur itu. Setelah menjadi layak secara praktis, beberapa siswa yang membaca buku-buku Knuth sebenarnya mengimplementasikan arsitekturnya sebagai VM (atau menggunakan implementasi yang ada), dan benar-benar menjalankan solusi mereka. IMO ini adalah cara yang bagus untuk belajar, jika Anda punya waktu.
WeirdlyCheezy
4
@ Lukas Saya tidak berpikir itu mudah untuk memahami orang-orang yang tidak bisa memahami petunjuk (atau, lebih tepatnya, tipuan secara umum). Anda pada dasarnya berasumsi bahwa orang-orang yang tidak mengerti petunjuk dalam C akan dapat mulai belajar perakitan, memahami arsitektur komputer yang mendasarinya dan kembali ke C dengan pemahaman tentang petunjuk. Ini mungkin benar bagi banyak orang, tetapi menurut beberapa penelitian, tampaknya beberapa orang secara inheren tidak dapat memahami tipuan, bahkan pada prinsipnya (saya masih menemukan itu sangat sulit untuk dipercaya, tetapi mungkin saya hanya beruntung dengan "murid-murid saya" ").
Luaan
27

Mengapa petunjuk merupakan faktor utama kebingungan bagi banyak mahasiswa tingkat baru, dan bahkan yang lama, dalam bahasa C / C ++?

Konsep placeholder untuk nilai - variabel - peta ke sesuatu yang kita ajarkan di sekolah - aljabar. Tidak ada paralel yang ada yang dapat Anda gambar tanpa memahami bagaimana memori diletakkan secara fisik di dalam komputer, dan tidak ada yang berpikir tentang hal semacam ini sampai mereka berurusan dengan hal-hal tingkat rendah - pada tingkat komunikasi C / C ++ / byte .

Apakah ada alat atau proses pemikiran yang membantu Anda memahami bagaimana pointer bekerja di variabel, fungsi, dan di luar level?

Kotak alamat. Saya ingat ketika saya belajar memprogram BASIC menjadi mikrokomputer, ada buku-buku cantik dengan permainan di dalamnya, dan kadang-kadang Anda harus menyodok nilai ke alamat tertentu. Mereka memiliki gambar sekelompok kotak, secara bertahap diberi label 0, 1, 2 ... dan dijelaskan bahwa hanya satu hal kecil (satu byte) yang dapat masuk ke dalam kotak-kotak ini, dan ada banyak dari mereka - beberapa komputer punya sebanyak 65535! Mereka bersebelahan, dan mereka semua memiliki alamat.

Apa saja hal-hal praktik yang baik yang dapat dilakukan untuk membawa seseorang ke level, "Ah-hah, saya mengerti," tanpa membuat mereka terjebak dalam konsep keseluruhan? Pada dasarnya, bor skenario seperti.

Untuk latihan? Buat struct:

struct {
char a;
char b;
char c;
char d;
} mystruct;
mystruct.a = 'r';
mystruct.b = 's';
mystruct.c = 't';
mystruct.d = 'u';

char* my_pointer;
my_pointer = &mystruct.b;
cout << 'Start: my_pointer = ' << *my_pointer << endl;
my_pointer++;
cout << 'After: my_pointer = ' << *my_pointer << endl;
my_pointer = &mystruct.a;
cout << 'Then: my_pointer = ' << *my_pointer << endl;
my_pointer = my_pointer + 3;
cout << 'End: my_pointer = ' << *my_pointer << endl;

Contoh yang sama seperti di atas, kecuali dalam C:

// Same example as above, except in C:
struct {
    char a;
    char b;
    char c;
    char d;
} mystruct;

mystruct.a = 'r';
mystruct.b = 's';
mystruct.c = 't';
mystruct.d = 'u';

char* my_pointer;
my_pointer = &mystruct.b;

printf("Start: my_pointer = %c\n", *my_pointer);
my_pointer++;
printf("After: my_pointer = %c\n", *my_pointer);
my_pointer = &mystruct.a;
printf("Then: my_pointer = %c\n", *my_pointer);
my_pointer = my_pointer + 3;
printf("End: my_pointer = %c\n", *my_pointer);

Keluaran:

Start: my_pointer = s
After: my_pointer = t
Then: my_pointer = r
End: my_pointer = u

Mungkin itu menjelaskan beberapa dasar melalui contoh?

Josh
sumber
+1 untuk "tanpa memahami bagaimana memori diletakkan secara fisik". Saya datang ke C dari latar belakang bahasa assembly dan konsep pointer sangat alami dan mudah; dan saya telah melihat orang-orang dengan hanya latar belakang bahasa tingkat tinggi berjuang untuk mencari tahu. Untuk membuatnya lebih buruk, sintaksnya membingungkan (function pointer!), Jadi mempelajari konsep dan sintaksis pada saat yang sama adalah resep untuk masalah.
Brendan
4
Saya tahu ini posting lama, tetapi akan lebih bagus jika output dari kode yang diberikan ditambahkan ke posting.
Josh
Ya, ini mirip dengan aljabar (meskipun aljabar memiliki titik pemahaman tambahan dalam membuat "variabel" mereka tidak berubah). Tetapi sekitar setengah orang yang saya kenal tidak memiliki pemahaman tentang aljabar dalam praktik. Itu tidak menghitung untuk mereka. Mereka tahu semua "persamaan" dan resep untuk mencapai hasil, tetapi mereka menerapkannya agak secara acak, dan dengan canggung. Dan mereka tidak dapat memperluasnya untuk tujuan mereka sendiri - itu hanya beberapa kotak hitam yang tidak dapat diubah dan tidak dapat disusun bagi mereka. Jika Anda mengerti aljabar, dan dapat menggunakannya secara efektif, Anda sudah jauh di depan paket - bahkan di antara programmer.
Luaan
24

Alasan saya mengalami kesulitan memahami pointer, pada awalnya, adalah bahwa banyak penjelasan termasuk banyak omong kosong tentang melewati referensi. Semua ini dilakukan membingungkan masalah. Saat Anda menggunakan parameter pointer, Anda masih memberikan nilai; tetapi nilai kebetulan merupakan alamat alih-alih, katakanlah, int.

Orang lain telah ditautkan dengan tutorial ini, tetapi saya dapat menyoroti saat ketika saya mulai memahami petunjuk:

Tutorial tentang Pointer dan Array di C: Bab 3 - Pointer dan String

int puts(const char *s);

Untuk saat ini, abaikan const.Parameter yang diteruskan ke puts()adalah penunjuk, yaitu nilai penunjuk (karena semua parameter dalam C diteruskan oleh nilai), dan nilai penunjuk adalah alamat yang ditunjuknya, atau, cukup , sebuah alamat. Jadi ketika kita menulis puts(strA);seperti yang kita lihat, kita memberikan alamat strA [0].

Saat saya membaca kata-kata ini, awan membelah dan seberkas sinar matahari menyelimuti saya dengan pemahaman pointer.

Bahkan jika Anda seorang pengembang VB .NET atau C # (seperti saya) dan tidak pernah menggunakan kode yang tidak aman, masih layak untuk memahami cara kerja pointer, atau Anda tidak akan mengerti cara kerja referensi objek. Kemudian Anda akan memiliki gagasan umum tapi keliru bahwa meneruskan referensi objek ke metode akan menyalin objek.

Kyralessa
sumber
Meninggalkan saya bertanya-tanya apa gunanya memiliki pointer. Di sebagian besar blok kode yang saya temui, sebuah pointer hanya terlihat dalam deklarasi.
Wolfpack'08
@ Wolfpack'08 ... apa ?? Kode apa yang kamu lihat ??
Kyle Strand
@KyleStrand Biarkan aku melihatnya.
Wolfpack'08
19

Saya menemukan "Tutorial tentang Pointer dan Array dalam C" Ted Jensen sumber yang bagus untuk belajar tentang pointer. Ini dibagi menjadi 10 pelajaran, dimulai dengan penjelasan tentang apa pointer (dan apa gunanya) dan diakhiri dengan pointer fungsi. http://home.netcom.com/~tjensen/ptr/cpoint.htm

Beranjak dari sana, Beej's Guide to Network Programming mengajarkan Unix sockets API, dari mana Anda dapat mulai melakukan hal-hal yang sangat menyenangkan. http://beej.us/guide/bgnet/

Ted Percival
sumber
1
Saya tutorial kedua Ted Jensen. Ini memecah pointer ke tingkat detail, itu tidak terlalu rinci, tidak ada buku yang saya baca. Sangat membantu! :)
Dave Gallagher
12

Kompleksitas petunjuk melampaui apa yang dapat dengan mudah kita ajarkan. Mintalah siswa menunjuk satu sama lain dan menggunakan selembar kertas dengan alamat rumah adalah alat pembelajaran yang hebat. Mereka melakukan pekerjaan yang bagus untuk memperkenalkan konsep-konsep dasar. Memang, mempelajari konsep dasar sangat penting untuk berhasil menggunakan petunjuk. Namun, dalam kode produksi, adalah umum untuk masuk ke skenario yang jauh lebih kompleks daripada demonstrasi sederhana ini dapat merangkum.

Saya telah terlibat dengan sistem di mana kami memiliki struktur yang menunjuk ke struktur lain yang menunjuk ke struktur lain. Beberapa dari struktur itu juga berisi struktur yang disematkan (bukan petunjuk ke struktur tambahan). Di sinilah pointer menjadi sangat membingungkan. Jika Anda memiliki beberapa tingkat tipuan, dan Anda mulai berakhir dengan kode seperti ini:

widget->wazzle.fizzle = fazzle.foozle->wazzle;

itu bisa membingungkan sangat cepat (bayangkan lebih banyak baris, dan berpotensi lebih banyak level). Lemparkan array pointer, dan pointer node ke node (pohon, daftar terkait) dan masih lebih buruk lagi. Saya telah melihat beberapa pengembang yang sangat baik tersesat begitu mereka mulai bekerja pada sistem seperti itu, bahkan pengembang yang memahami dasar-dasarnya dengan sangat baik.

Struktur kompleks pointer tidak selalu menunjukkan pengkodean yang buruk, (meskipun mereka bisa). Komposisi adalah bagian penting dari pemrograman berorientasi objek yang baik, dan dalam bahasa dengan pointer mentah, itu pasti akan mengarah pada tipuan multi-layer. Lebih jauh, sistem sering perlu menggunakan perpustakaan pihak ketiga dengan struktur yang tidak cocok satu sama lain dalam gaya atau teknik. Dalam situasi seperti itu, kompleksitas secara alami akan muncul (walaupun tentu saja, kita harus berjuang semaksimal mungkin).

Saya pikir hal terbaik yang dapat dilakukan perguruan tinggi untuk membantu siswa mempelajari petunjuk adalah menggunakan demonstrasi yang baik, dikombinasikan dengan proyek yang membutuhkan penggunaan pointer. Satu proyek yang sulit akan melakukan lebih banyak untuk pemahaman pointer daripada seribu demonstrasi. Demonstrasi dapat membuat Anda memiliki pemahaman yang dangkal, tetapi untuk memahami secara mendalam, Anda harus benar-benar menggunakannya.

Taman Derek
sumber
10

Saya pikir saya akan menambahkan analogi ke daftar ini yang saya temukan sangat membantu ketika menjelaskan petunjuk (kembali pada hari itu) sebagai Tutor Ilmu Komputer; pertama, mari:


Atur panggung :

Pertimbangkan tempat parkir dengan 3 ruang, ruang-ruang ini diberi nomor:

-------------------
|     |     |     |
|  1  |  2  |  3  |
|     |     |     |

Di satu sisi, ini seperti lokasi memori, mereka berurutan dan berdekatan .. semacam array. Saat ini tidak ada mobil di dalamnya sehingga seperti array kosong ( parking_lot[3] = {0}).


Tambahkan data

Tempat parkir tidak pernah kosong untuk waktu yang lama ... jika tidak ada gunanya dan tidak akan ada yang membangun. Jadi katakanlah ketika hari bergerak di tempat parkir dipenuhi dengan 3 mobil, mobil biru, mobil merah, dan mobil hijau:

   1     2     3
-------------------
| o=o | o=o | o=o |
| |B| | |R| | |G| |
| o-o | o-o | o-o |

Mobil-mobil ini adalah semua jenis yang sama (mobil) jadi salah satu cara untuk memikirkan ini adalah bahwa mobil kami adalah semacam data (mengatakan sebuah int) tetapi mereka memiliki nilai yang berbeda ( blue, red, green; yang bisa menjadi warna enum)


Masukkan pointer

Sekarang jika saya membawa Anda ke tempat parkir ini, dan meminta Anda untuk menemukan saya mobil biru, Anda mengulurkan satu jari dan menggunakannya untuk menunjuk ke mobil biru di tempat 1. Ini seperti mengambil pointer dan menugaskannya ke alamat memori ( int *finger = parking_lot)

Jari Anda (penunjuk) bukan jawaban untuk pertanyaan saya. Melihat di jari Anda memberitahu saya apa-apa, tapi kalau saya melihat di mana Anda jari Anda adalah menunjuk ke (dereferencing pointer), saya dapat menemukan mobil (data) yang saya cari.


Menugaskan kembali pointer

Sekarang saya dapat meminta Anda untuk mencari mobil merah dan Anda dapat mengarahkan ulang jari Anda ke mobil baru. Sekarang pointer Anda (yang sama seperti sebelumnya) menunjukkan kepada saya data baru (tempat parkir di mana mobil merah dapat ditemukan) dari jenis yang sama (mobil).

Pointer tidak berubah secara fisik, itu masih jari Anda , hanya data yang saya tunjukkan berubah. (alamat "tempat parkir")


Pointer ganda (atau pointer ke pointer)

Ini bekerja dengan lebih dari satu pointer juga. Saya bisa bertanya di mana penunjuknya, yang menunjuk ke mobil merah dan Anda bisa menggunakan tangan Anda yang lain dan menunjuk dengan satu jari ke jari pertama. (ini seperti int **finger_two = &finger)

Sekarang jika saya ingin tahu di mana mobil biru itu, saya bisa mengikuti arah jari pertama ke jari kedua, ke mobil (data).


Pointer yang menggantung

Sekarang katakanlah Anda merasa sangat seperti patung, dan Anda ingin memegang tangan Anda ke arah mobil merah tanpa batas. Bagaimana jika mobil merah itu pergi?

   1     2     3
-------------------
| o=o |     | o=o |
| |B| |     | |G| |
| o-o |     | o-o |

Pointer Anda masih menunjuk ke tempat mobil merah itu tetapi sekarang tidak lagi. Katakanlah mobil baru masuk ke sana ... mobil oranye. Sekarang jika saya bertanya lagi, "di mana mobil merah", Anda masih menunjuk ke sana, tapi sekarang Anda salah. Itu bukan mobil merah, itu oranye.


Aritmatika petunjuk

Ok, jadi Anda masih menunjuk ke tempat parkir kedua (sekarang ditempati oleh mobil Orange)

   1     2     3
-------------------
| o=o | o=o | o=o |
| |B| | |O| | |G| |
| o-o | o-o | o-o |

Yah saya punya pertanyaan baru sekarang ... Saya ingin tahu warna mobil di tempat parkir berikutnya . Anda dapat melihat Anda menunjuk ke titik 2, jadi Anda hanya menambahkan 1 dan menunjuk titik berikutnya. ( finger+1), sekarang karena saya ingin tahu data apa yang ada di sana, Anda harus memeriksa tempat itu (bukan hanya jari) sehingga Anda dapat menghormati pointer ( *(finger+1)) untuk melihat ada mobil hijau yang ada di sana (data di lokasi itu) )

Mike
sumber
Hanya saja, jangan gunakan kata "penunjuk ganda". Pointer dapat menunjuk ke apa pun, jadi jelas Anda dapat memiliki pointer menunjuk ke pointer lain. Mereka bukan pointer ganda.
gnasher729
Saya pikir ini melewatkan titik bahwa "jari" sendiri, untuk melanjutkan analogi Anda, masing-masing "menempati tempat parkir". Saya tidak yakin bahwa orang-orang memiliki kesulitan memahami pointer pada abstraksi tingkat tinggi dari analogi Anda, itu memahami bahwa pointer adalah hal-hal yang bisa berubah yang menempati lokasi memori, dan bagaimana ini berguna, yang tampaknya menghindari orang.
Emmet
1
@Emmet - Saya tidak setuju bahwa ada banyak lagi yang bisa masuk ke petunjuk WRT, tapi saya membaca pertanyaan: "without getting them bogged down in the overall concept"sebagai pemahaman tingkat tinggi. Dan untuk titik Anda: "I'm not sure that people have any difficulty understanding pointers at the high level of abstraction"- Anda akan sangat terkejut betapa banyak orang tidak mengerti pointer bahkan ke level ini
Mike
Apakah ada gunanya memperluas analogi jari-mobil ke seseorang (dengan satu jari atau lebih - dan kelainan genetik yang memungkinkan masing-masing menunjuk ke arah mana pun!) Duduk di salah satu mobil yang menunjuk ke mobil lain (atau membungkuk menunjuk ke tanah kosong di samping tanah kosong sebagai "penunjuk yang tidak diinisialisasi"; atau seluruh tangan terentang menunjuk pada deretan ruang sebagai "ukuran tetap [5] array pointer" atau melengkung ke telapak "null pointer" yang menunjuk ke suatu tempat di mana diketahui bahwa TIDAK PERNAH mobil) ... 8-)
SlySven
Penjelasan Anda sangat berharga dan bagus untuk pemula.
Yatendra Rathore
9

Saya tidak berpikir pointer sebagai konsep yang sangat rumit - kebanyakan model mental siswa memetakan sesuatu seperti ini dan beberapa sketsa kotak cepat dapat membantu.

Kesulitannya, setidaknya yang pernah saya alami di masa lalu dan melihat orang lain berurusan, adalah bahwa manajemen pointer di C / C ++ dapat berbelit-belit secara tidak perlu.

Matt Mitchell
sumber
8

Masalah dengan pointer bukanlah konsepnya. Ini adalah eksekusi dan bahasa yang terlibat. Kebingungan tambahan terjadi ketika guru berasumsi bahwa itu adalah KONSEP dari pointer yang sulit, dan bukan jargon, atau kekacauan C dan C ++ yang membuat konsep. Begitu banyak upaya yang dilakukan untuk menjelaskan konsep ini (seperti dalam jawaban yang diterima untuk pertanyaan ini) dan itu cukup sia-sia untuk seseorang seperti saya, karena saya sudah mengerti semua itu. Itu hanya menjelaskan bagian yang salah dari masalah.

Untuk memberi Anda ide dari mana saya berasal, saya seseorang yang mengerti pointer dengan sangat baik, dan saya bisa menggunakannya dengan kompeten dalam bahasa assembler. Karena dalam bahasa assembler mereka tidak disebut sebagai pointer. Mereka disebut sebagai alamat. Ketika datang ke pemrograman dan menggunakan pointer di C, saya membuat banyak kesalahan dan benar-benar bingung. Saya masih belum menyelesaikan ini. Biarkan saya memberi Anda sebuah contoh.

Ketika sebuah api berkata:

int doIt(char *buffer )
//*buffer is a pointer to the buffer

apa yang dia inginkan?

itu bisa ingin:

nomor yang mewakili alamat ke buffer

(Untuk memberikan itu, apakah saya katakan doIt(mybuffer), atau doIt(*myBuffer)?)

nomor yang mewakili alamat ke alamat ke buffer

(apakah itu doIt(&mybuffer)atau doIt(mybuffer)atau doIt(*mybuffer)?)

nomor yang mewakili alamat ke alamat ke alamat ke buffer

(mungkin itu doIt(&mybuffer). atau apakah itu doIt(&&mybuffer)? atau bahkan doIt(&&&mybuffer))

dan seterusnya, dan bahasa yang terlibat tidak menjelaskannya karena melibatkan kata "pointer" dan "referensi" yang tidak memiliki banyak makna dan kejelasan bagi saya seperti "x menyimpan alamat untuk y" dan " fungsi ini membutuhkan alamat ke y ". Jawabannya juga tergantung pada apa "mybuffer" itu, dan apa yang ingin dilakukan dengan itu. Bahasa ini tidak mendukung level sarang yang dijumpai dalam praktik. Seperti ketika saya harus menyerahkan "pointer" ke fungsi yang membuat buffer baru, dan memodifikasi pointer ke titik di lokasi baru buffer. Apakah itu benar-benar ingin pointer, atau pointer ke pointer, jadi ia tahu ke mana harus pergi untuk mengubah isi dari pointer. Sebagian besar waktu saya hanya perlu menebak apa yang dimaksud dengan "

"Pointer" terlalu kelebihan. Apakah penunjuk alamat ke nilai? atau apakah itu variabel yang menyimpan alamat ke suatu nilai. Ketika suatu fungsi menginginkan sebuah pointer, apakah ia menginginkan alamat yang dipegang oleh variabel pointer, atau apakah ia menginginkan alamat tersebut ke variabel pointer? Saya bingung.

Breton
sumber
Saya telah melihatnya menjelaskan seperti ini: jika Anda melihat deklarasi pointer seperti double *(*(*fn)(int))(char), maka hasil evaluasi *(*(*fn)(42))('x')akan menjadi double. Anda dapat menghapus lapisan evaluasi untuk memahami apa yang harus menjadi tipe perantara.
Bernd Jendrissek
@BerndJendrissek Tidak yakin saya mengikuti. Apa hasil dari evaluasi (*(*fn)(42))('x') itu?
Breton
Anda mendapatkan sesuatu (sebut saja x) di mana, jika Anda mengevaluasi *x, Anda mendapatkan dua kali lipat.
Bernd Jendrissek
@BerndJendrissek Apakah ini seharusnya menjelaskan sesuatu tentang petunjuk? Saya tidak mengerti. Apa maksudmu Saya telah menanggalkan satu lapisan, dan tidak mendapatkan informasi baru tentang jenis perantara apa pun. Apa yang dijelaskan tentang fungsi apa yang akan diterima? Apa hubungannya dengan apa pun?
Breton
Mungkin pesan dalam penjelasan ini (dan bukan milik saya, saya berharap bisa menemukan di mana saya pertama kali melihatnya) adalah untuk menganggapnya kurang dalam hal apa yang fn ada dan lebih dalam hal apa yang dapat Anda lakukan denganfn
Bernd Jendrissek
8

Saya pikir hambatan utama untuk memahami petunjuk adalah guru yang buruk.

Hampir semua orang diajarkan kebohongan tentang petunjuk: Bahwa mereka tidak lebih dari alamat memori , atau bahwa mereka memungkinkan Anda untuk menunjuk ke lokasi yang sewenang - wenang .

Dan tentu saja mereka sulit dimengerti, berbahaya dan semi-magis.

Tidak ada yang benar. Pointer sebenarnya adalah konsep yang cukup sederhana, selama Anda tetap berpegang pada apa yang dikatakan bahasa C ++ tentang mereka dan tidak menanamkannya dengan atribut yang "biasanya" ternyata bekerja dalam praktik, tetapi tetap tidak dijamin oleh bahasa , dan jadi bukan bagian dari konsep aktual pointer.

Saya mencoba menulis penjelasan tentang ini beberapa bulan yang lalu di posting blog ini - semoga akan membantu seseorang.

(Catatan, sebelum ada orang yang menyinggung saya, ya, standar C ++ mengatakan bahwa pointer mewakili alamat memori. Tetapi tidak mengatakan bahwa "pointer adalah alamat memori, dan tidak ada yang lain selain alamat memori dan dapat digunakan atau dipikirkan secara bergantian dengan memori). alamat ". Perbedaannya penting)

jalf
sumber
Lagi pula, pointer nol tidak menunjuk ke alamat-nol di memori, meskipun itu "nilai" C adalah nol. Ini adalah konsep yang sepenuhnya terpisah, dan jika Anda salah mengatasinya, Anda mungkin berakhir dengan menangani (dan meremehkan) sesuatu yang tidak Anda harapkan. Dalam beberapa kasus, itu mungkin bahkan nol-alamat dalam memori (terutama sekarang karena ruang alamat biasanya datar), tetapi dalam kasus lain, itu mungkin dihilangkan sebagai perilaku tidak terdefinisi oleh kompiler yang mengoptimalkan, atau mengakses beberapa bagian lain dari memori yang terkait dengan "nol" untuk jenis penunjuk yang diberikan. Hilaritas terjadi.
Luaan
Belum tentu. Anda harus dapat memodelkan komputer di kepala Anda agar pointer masuk akal (dan juga untuk debug program lain). Tidak semua orang bisa melakukan ini.
Thorbjørn Ravn Andersen
5

Saya pikir apa yang membuat pointer sulit untuk dipelajari adalah bahwa sampai pointer Anda merasa nyaman dengan gagasan bahwa "di lokasi memori ini adalah sekumpulan bit yang mewakili int, double, karakter, apa pun".

Ketika Anda pertama kali melihat pointer, Anda tidak benar-benar mendapatkan apa yang ada di lokasi memori itu. "Apa maksudmu, itu menyimpan alamat ?"

Saya tidak setuju dengan anggapan bahwa "Anda mendapatkannya atau tidak".

Mereka menjadi lebih mudah dipahami ketika Anda mulai menemukan kegunaan nyata untuk mereka (seperti tidak melewatkan struktur besar ke dalam fungsi).

Baltimark
sumber
5

Alasannya sangat sulit untuk dipahami bukan karena itu konsep yang sulit tetapi karena sintaksinya tidak konsisten .

   int *mypointer;

Anda pertama kali mengetahui bahwa bagian paling kiri dari suatu kreasi variabel menentukan jenis variabel. Deklarasi pointer tidak berfungsi seperti ini di C dan C ++. Sebaliknya mereka mengatakan bahwa variabel menunjuk pada tipe di sebelah kiri. Dalam hal ini: *mypointer menunjuk ke int.

Saya tidak sepenuhnya memahami pointer sampai saya mencoba menggunakannya dalam C # (dengan tidak aman), mereka bekerja dengan cara yang sama persis tetapi dengan sintaksis yang logis dan konsisten. Pointer adalah tipe itu sendiri. Di sini mypointer adalah penunjuk ke int.

  int* mypointer;

Bahkan jangan mulai dengan fungsi pointer ...

Burrrr
sumber
2
Sebenarnya, kedua fragmen Anda valid C. Ini adalah masalah gaya C selama bertahun-tahun sehingga yang pertama lebih umum. Yang kedua agak lebih umum di C ++, misalnya.
RBerteig
1
Fragmen kedua tidak benar-benar berfungsi dengan baik dengan deklarasi yang lebih kompleks. Dan sintaksnya tidak "tidak konsisten" setelah Anda menyadari bahwa bagian kanan dari deklarasi pointer menunjukkan kepada Anda apa yang harus Anda lakukan pada pointer untuk mendapatkan sesuatu yang tipenya adalah specifier tipe atom di sebelah kiri.
Bernd Jendrissek
2
int *p;memiliki makna yang sederhana: *padalah bilangan bulat. int *p, **ppberarti: *pdan **ppbilangan bulat.
Miles Rout
@MilesRout: Tapi itulah masalahnya. *pdan **ppyang tidak bilangan bulat, karena Anda tidak pernah dijalankan patau ppatau *ppke titik apa pun. Saya mengerti mengapa beberapa orang lebih suka bertahan dengan tata bahasa yang satu ini, terutama karena beberapa kasus tepi dan kasus kompleks mengharuskan Anda untuk melakukannya (meskipun, masih, Anda dapat dengan mudah mengatasi hal itu dalam semua kasus yang saya sadari) ... tetapi saya tidak berpikir kasus-kasus itu lebih penting daripada fakta bahwa mengajarkan keselarasan benar menyesatkan pemula. Belum lagi jenis jelek! :)
Lightness Races in Orbit
@LightnessRacesinOrbit Mengajarkan perataan kanan jauh dari menyesatkan. Itu adalah satu-satunya cara yang benar untuk mengajarkannya. BUKAN mengajarkan itu menyesatkan.
Miles Rout
5

Saya bisa bekerja dengan pointer ketika saya hanya tahu C ++. Saya agak tahu apa yang harus dilakukan dalam beberapa kasus dan apa yang tidak boleh dilakukan dari percobaan / kesalahan. Tetapi hal yang memberi saya pemahaman penuh adalah bahasa assembly. Jika Anda melakukan debug tingkat instruksi serius dengan program bahasa assembly yang Anda tulis, Anda harus dapat memahami banyak hal.

toto
sumber
4

Saya suka analogi alamat rumah, tetapi saya selalu berpikir alamatnya adalah kotak surat itu sendiri. Dengan cara ini Anda dapat memvisualisasikan konsep dereferencing pointer (membuka kotak surat).

Misalnya mengikuti daftar tertaut: 1) mulai dengan kertas Anda dengan alamat 2) Buka alamat di atas kertas 3) Buka kotak surat untuk menemukan selembar kertas baru dengan alamat berikutnya di atasnya

Dalam daftar tertaut linier, kotak pesan terakhir tidak ada di dalamnya (akhir daftar). Dalam daftar tertaut melingkar, kotak surat terakhir memiliki alamat kotak surat pertama di dalamnya.

Perhatikan bahwa langkah 3 adalah tempat dereference terjadi dan di mana Anda akan crash atau salah ketika alamat tidak valid. Dengan asumsi Anda bisa berjalan ke kotak surat dari alamat yang tidak valid, bayangkan bahwa ada lubang hitam atau sesuatu di sana yang mengubah dunia luar :)

Christopher Scott
sumber
Komplikasi buruk dengan analogi angka kotak surat adalah bahwa sementara bahasa yang ditemukan oleh Dennis Ritchie mendefinisikan perilaku dalam hal alamat byte dan nilai yang disimpan dalam byte tersebut, bahasa yang didefinisikan oleh Standar C mengundang implementasi "optimalisasi" untuk menggunakan perilaku. model yang lebih rumit tetapi mendefinisikan berbagai aspek model dengan cara yang ambigu, kontradiktif, dan tidak lengkap.
supercat
3

Saya pikir alasan utama orang mengalami masalah adalah karena umumnya tidak diajarkan secara menarik dan menarik. Saya ingin melihat seorang dosen mendapatkan 10 sukarelawan dari kerumunan dan memberi mereka masing-masing 1 meter penggaris, membuat mereka berdiri di sekitar konfigurasi tertentu dan menggunakan para penguasa untuk saling menunjuk. Kemudian tunjukkan aritmetika penunjuk dengan menggerakkan orang di sekitar (dan di mana mereka mengarahkan penggaris mereka). Ini akan menjadi cara yang sederhana namun efektif (dan yang paling berkesan) untuk menunjukkan konsep tanpa terlalu macet di mekanik.

Setelah Anda mendapatkan C dan C ++ sepertinya semakin sulit bagi sebagian orang. Saya tidak yakin apakah ini karena mereka akhirnya menempatkan teori bahwa mereka tidak memahami praktik dengan benar atau karena manipulasi pointer secara inheren lebih sulit dalam bahasa tersebut. Saya tidak dapat mengingat transisi saya sendiri dengan baik, tetapi saya tahu petunjuk dalam Pascal dan kemudian pindah ke C dan benar-benar tersesat.

Wolfbyte
sumber
2

Saya tidak berpikir bahwa pointer itu sendiri membingungkan. Kebanyakan orang dapat memahami konsep tersebut. Sekarang, berapa banyak petunjuk yang dapat Anda pikirkan atau berapa tingkat tipuan yang nyaman bagi Anda. Tidak perlu terlalu banyak untuk menempatkan orang pada batas. Fakta bahwa mereka dapat diubah secara tidak sengaja oleh bug di program Anda juga dapat membuat mereka sangat sulit untuk di-debug ketika ada kesalahan dalam kode Anda.

bruceatk
sumber
2

Saya pikir itu mungkin sebenarnya masalah sintaksis. Sintaks C / C ++ untuk pointer tampaknya tidak konsisten dan lebih kompleks dari yang seharusnya.

Ironisnya, hal yang benar-benar membantu saya untuk memahami pointer menemukan konsep iterator di c + + Standard Template Library . Ini ironis karena saya hanya bisa berasumsi bahwa iterator dikandung sebagai generalisasi dari pointer.

Terkadang Anda tidak bisa melihat hutan sampai Anda belajar mengabaikan pohon.

Waylon Flinn
sumber
Masalahnya sebagian besar dalam sintaks deklarasi C. Tetapi penggunaan pointer tentu akan lebih mudah jika (*p)dilakukan (p->), dan dengan demikian kita akan memiliki p->->xalih - alih ambigu*p->x
MSalters
@Malters Oh my god kamu bercanda, kan? Tidak ada ketidakkonsistenan di sana. a->bhanya berarti (*a).b.
Rute Mil
@ Miles: Memang, dan dengan logika itu * p->xberarti * ((*a).b)sedangkan *p -> xberarti (*(*p)) -> x. Campuran operator prefix dan postfix menyebabkan penguraian ambigu.
MSalters
@Mengganti tidak, karena spasi tidak relevan. Itu seperti mengatakan bahwa itu 1+2 * 3seharusnya 9.
Miles Rout
2

Kebingungan datang dari beberapa lapisan abstraksi yang digabungkan menjadi satu dalam konsep "pointer". Programmer tidak menjadi bingung oleh referensi biasa dalam Java / Python, tetapi pointer berbeda karena mereka mengekspos karakteristik arsitektur memori yang mendasarinya.

Merupakan prinsip yang baik untuk memisahkan lapisan abstraksi dengan bersih, dan petunjuk tidak melakukannya.

Joshua Fox
sumber
1
Hal yang menarik adalah, bahwa pointer C sebenarnya tidak memaparkan karakteristik arsitektur memori yang mendasarinya. Satu-satunya perbedaan antara referensi Java dan pointer C adalah bahwa Anda dapat memiliki tipe kompleks yang melibatkan pointer (mis. Int *** atau char * ( ) (void * )), ada aritmatika pointer untuk array dan pointer ke anggota struct, keberadaan dari kekosongan *, dan dualitas array / pointer. Selain itu, mereka bekerja sama saja.
jpalecek
Poin yang bagus. Ini adalah aritmatika penunjuk dan kemungkinan buffer overflows - keluar dari abstraksi dengan keluar dari area memori yang saat ini relevan - yang melakukannya.
Joshua Fox
@ jpalecek: Cukup mudah untuk memahami bagaimana pointer bekerja pada implementasi yang mendokumentasikan perilaku mereka dalam hal arsitektur yang mendasarinya. Berkata foo[i]berarti pergi ke tempat tertentu, bergerak maju jarak tertentu, dan melihat apa yang ada di sana. Yang menyulitkan adalah lapisan abstraksi ekstra yang jauh lebih rumit yang ditambahkan oleh Standar semata-mata untuk kepentingan kompiler, tetapi memodelkan berbagai hal dengan cara yang tidak sesuai untuk kebutuhan programmer dan kebutuhan kompiler sama.
supercat
2

Cara saya suka menjelaskannya adalah dalam hal array dan indeks - orang mungkin tidak terbiasa dengan pointer, tetapi mereka umumnya tahu apa itu indeks.

Jadi saya katakan bayangkan RAM adalah sebuah array (dan Anda hanya memiliki 10-byte RAM):

unsigned char RAM[10] = { 10, 14, 4, 3, 2, 1, 20, 19, 50, 9 };

Kemudian pointer ke suatu variabel benar-benar hanya indeks (byte pertama) dari variabel dalam RAM.

Jadi jika Anda memiliki pointer / indeks unsigned char index = 2, maka nilainya jelas elemen ketiga, atau angka 4. Sebuah pointer ke pointer adalah tempat Anda mengambil nomor itu dan menggunakannya sebagai indeks itu sendiri, seperti RAM[RAM[index]].

Saya akan menggambar array pada daftar kertas, dan hanya menggunakannya untuk menunjukkan hal-hal seperti banyak pointer yang menunjuk ke memori yang sama, aritmatika pointer, pointer ke pointer, dan sebagainya.

sashoalm
sumber
1

Nomor kotak pos.

Itu adalah sepotong informasi yang memungkinkan Anda mengakses sesuatu yang lain.

(Dan jika Anda melakukan hitung pada nomor kotak pos, Anda mungkin memiliki masalah, karena hurufnya berada di kotak yang salah. Dan jika seseorang pindah ke negara bagian lain - tanpa alamat penerusan - maka Anda memiliki penunjuk yang menggantung. sisi lain - jika kantor pos meneruskan surat, maka Anda memiliki pointer ke pointer.)

joel.neely
sumber
1

Bukan cara yang buruk untuk menangkapnya, melalui iterator .. tetapi terus mencari Anda akan melihat Alexandrescu mulai mengeluh tentang mereka.

Banyak ex-C ++ devs (yang tidak pernah mengerti bahwa iterator adalah pointer modern sebelum membuang bahasa) melompat ke C # dan masih percaya mereka memiliki iterator yang layak.

Hmm, masalahnya adalah bahwa semua iterator berada dalam peluang penuh pada apa yang ingin dicapai oleh platform runtime (Java / CLR): penggunaan yang baru, sederhana, semua orang adalah dev. Yang bisa bagus, tetapi mereka mengatakannya sekali dalam buku ungu dan mereka mengatakannya bahkan sebelum dan sebelum C:

Tipuan.

Konsep yang sangat kuat tetapi tidak pernah demikian jika Anda melakukannya sepenuhnya .. Iterator berguna karena mereka membantu dengan abstraksi algoritma, contoh lain. Dan waktu kompilasi adalah tempat untuk suatu algoritma, sangat sederhana. Anda tahu kode + data, atau dalam bahasa lain C #:

IEnumerable + LINQ + Massive Framework = 300MB runtime penalti tipu daya buruk, menyeret aplikasi melalui banyak contoh tipe referensi ..

"Le Pointer murah."

rama-jka toti
sumber
4
Apa hubungannya ini dengan apa pun?
Neil Williams
... apa yang ingin Anda katakan, selain dari "penghubung statis adalah hal terbaik yang pernah ada" dan "Saya tidak mengerti bagaimana sesuatu yang berbeda dari apa yang saya pelajari sebelumnya bekerja"?
Luaan
Luaan, Anda tidak mungkin tahu apa yang bisa dipelajari dengan membongkar JIT pada tahun 2000, bukan? Bahwa itu berakhir di tabel lompat, dari tabel pointer, seperti yang ditunjukkan pada tahun 2000 online di ASM, jadi tidak memahami apa pun yang berbeda dapat mengambil arti lain: membaca dengan cermat adalah keterampilan yang penting, coba lagi.
rama-jka toti
1

Beberapa jawaban di atas telah menyatakan bahwa "pointer tidak terlalu sulit", tetapi belum langsung ditujukan ke mana "pointer sulit!" datang dari. Beberapa tahun yang lalu saya mengajari siswa CS tahun pertama (hanya satu tahun, karena saya jelas mengisapnya) dan jelas bagi saya bahwa ide pointer tidak sulit. Yang sulit adalah memahami mengapa dan kapan Anda menginginkan pointer .

Saya tidak berpikir Anda bisa menceraikan pertanyaan itu - mengapa dan kapan menggunakan pointer - dari menjelaskan masalah rekayasa perangkat lunak yang lebih luas. Mengapa setiap variabel tidak boleh menjadi variabel global, dan mengapa seseorang harus memfaktorkan kode yang sama ke dalam fungsi (bahwa, dapatkan ini, gunakan pointer untuk mengkhususkan perilaku mereka ke situs panggilan mereka).

Bernd Jendrissek
sumber
0

Saya tidak melihat apa yang begitu membingungkan tentang pointer. Mereka menunjuk ke lokasi di memori, yaitu menyimpan alamat memori. Dalam C / C ++ Anda dapat menentukan jenis titik penunjuk. Sebagai contoh:

int* my_int_pointer;

Mengatakan bahwa my_int_pointer berisi alamat ke lokasi yang berisi int.

Masalah dengan pointer adalah bahwa mereka menunjuk ke suatu lokasi dalam memori, sehingga mudah untuk dilacak ke beberapa lokasi Anda tidak boleh masuk. Sebagai bukti lihat banyak lubang keamanan dalam aplikasi C / C ++ dari buffer overflow (incrementing pointer melewati batas yang dialokasikan).

grom
sumber
0

Hanya untuk membingungkan hal-hal sedikit lebih, kadang-kadang Anda harus bekerja dengan pegangan, bukan pointer. Pegangan adalah pointer ke pointer, sehingga ujung belakang dapat memindahkan benda-benda dalam memori untuk mendefrag tumpukan. Jika pointer berubah di pertengahan rutin, hasilnya tidak dapat diprediksi, jadi pertama-tama Anda harus mengunci pegangan untuk memastikan tidak ada yang terjadi di mana pun.

http://arjay.bc.ca/Modula-2/Text/Ch15/Ch15.8.html#15.8.5 membicarakannya sedikit lebih masuk akal daripada saya. :-)

SarekOfVulcan
sumber
-1: Pegangan bukan pointer ke pointer; mereka bukan petunjuk dalam arti apa pun. Jangan membingungkan mereka.
Ian Goldby
"Mereka bukan petunjuk dalam arti apa pun" - um, saya mohon berbeda.
SarekOfVulcan
Pointer adalah lokasi memori. Pegangan adalah pengidentifikasi unik. Mungkin saja sebuah pointer, tetapi mungkin juga merupakan indeks dalam sebuah array, atau apa pun yang lainnya. Tautan yang Anda berikan hanyalah kasus khusus di mana pegangannya adalah sebuah pointer, tetapi tidak harus seperti itu. Lihat juga parashift.com/c++-faq-lite/references.html#faq-8.8
Ian Goldby
Tautan itu tidak mendukung pernyataan Anda bahwa mereka bukan penunjuk dalam arti apa pun, juga - "Misalnya, pegangannya mungkin Fred **, di mana penunjuk rujukan Fred * ..." Saya tidak berpikir -1 adalah adil.
SarekOfVulcan
0

Setiap pemula C / C ++ memiliki masalah yang sama dan masalah itu terjadi bukan karena "pointer sulit dipelajari" tetapi "siapa dan bagaimana hal itu dijelaskan". Beberapa peserta didik mengumpulkannya secara lisan beberapa secara visual dan cara terbaik untuk menjelaskannya adalah dengan menggunakan contoh "melatih" (cocok untuk contoh verbal dan visual).

Di mana "lokomotif" adalah sebuah penunjuk yang tidak dapat menahan apa pun dan "gerobak" adalah apa yang "ditarik oleh lokomotif" (atau arahkan ke). Setelah itu, Anda dapat mengklasifikasikan "gerobak" itu sendiri, dapatkah binatang, tumbuhan atau manusia (atau campurannya).

Praveen Kumar
sumber