Mengapa Pointer Tambahan?

25

Saya baru-baru ini mulai belajar C ++, dan karena kebanyakan orang (sesuai dengan apa yang saya baca) saya berjuang dengan pointer.

Tidak dalam pengertian tradisional, saya mengerti apa itu, dan mengapa mereka digunakan, dan bagaimana mereka bisa berguna, namun saya tidak bisa mengerti bagaimana penambahan pointer akan berguna, adakah yang bisa memberikan penjelasan tentang bagaimana penambahan pointer adalah konsep yang berguna dan C ++ idiomatik?

Pertanyaan ini muncul setelah saya mulai membaca buku A Tour of C ++ oleh Bjarne Stroustrup, saya direkomendasikan buku ini, karena saya cukup akrab dengan Java, dan orang-orang di Reddit mengatakan kepada saya bahwa itu akan menjadi buku 'peralihan' yang baik .

INdek
sumber
11
Pointer hanyalah sebuah iterator
Charles Salvia
1
Ini adalah salah satu alat favorit untuk menulis virus komputer yang membaca apa yang seharusnya tidak mereka baca. Ini juga salah satu kasus kerentanan aplikasi yang paling umum (ketika seseorang menambah pointer melewati area yang seharusnya, kemudian membaca atau menulisnya)> Lihat bug HeartBleed.
Sam
1
@vasile Inilah yang buruk tentang pointer.
Cruncher
4
Yang menyenangkan / buruk tentang C ++ adalah memungkinkan Anda melakukan lebih banyak sebelum memanggil segfault. Biasanya Anda mendapatkan segfault ketika mencoba mengakses memori proses lain, memori sistem atau memori aplikasi yang dilindungi. Setiap akses di dalam halaman aplikasi yang biasa diizinkan oleh sistem, dan itu tergantung pada programmer / kompiler / bahasa untuk menegakkan batasan yang wajar. C ++ cukup banyak memungkinkan Anda untuk melakukan apa pun yang Anda inginkan. Adapun openssl memiliki manajer memori sendiri - itu tidak benar. Itu hanya memiliki mekanisme akses memori C ++ default.
Sam
1
@Indek: Anda hanya akan mendapatkan segfault jika memori yang Anda coba akses dilindungi. Sebagian besar sistem operasi memberikan perlindungan di tingkat halaman, sehingga Anda biasanya dapat mengakses apa pun yang ada di halaman tempat pointer Anda dimulai. Jika OS menggunakan ukuran halaman 4K, itu adalah jumlah data yang wajar. Jika pointer Anda mulai di suatu tempat di heap, siapa pun dapat menebak berapa banyak data yang dapat Anda akses.
TMN

Jawaban:

46

Ketika Anda memiliki sebuah array, Anda bisa mengatur pointer untuk menunjuk ke elemen array:

int a[10];
int *p = &a[0];

Di sini pmenunjuk ke elemen pertama a, yaitu a[0]. Sekarang Anda dapat menambahkan pointer untuk menunjuk ke elemen berikutnya:

p++;

Sekarang pmenunjuk ke elemen kedua a[1],. Anda dapat mengakses elemen di sini menggunakan *p. Ini berbeda dari Java di mana Anda harus menggunakan variabel indeks integer untuk mengakses elemen array.

Menambah pointer di C ++ di mana pointer itu tidak menunjuk ke elemen array adalah perilaku yang tidak terdefinisi .

Greg Hewgill
sumber
23
Yap, dengan C ++ Anda bertanggung jawab untuk menghindari kesalahan pemrograman seperti akses di luar batas array.
Greg Hewgill
9
Tidak, menambah pointer yang menunjuk ke apa pun kecuali elemen array adalah perilaku yang tidak terdefinisi. Namun, jika Anda melakukan sesuatu tingkat rendah dan tidak portabel, maka peningkatan pointer biasanya tidak lebih dari mengakses hal berikutnya dalam memori, apa pun itu.
Greg Hewgill
4
Ada beberapa hal yang, atau dapat diperlakukan sebagai, array; string teks sebenarnya adalah array karakter. Dalam beberapa kasus, int panjang diperlakukan sebagai array byte, meskipun ini dapat dengan mudah membuat Anda kesulitan.
AMADANON Inc.
6
Itu memberi tahu Anda jenisnya , tetapi perilaku tersebut dijelaskan dalam 5.7 operator aditif [expr.add]. Secara khusus, 5.7 / 5 mengatakan bahwa pergi ke mana pun di luar array kecuali one-past-the-end adalah UB.
berguna
4
Paragraf terakhir adalah: Jika operan penunjuk dan titik hasil ke elemen objek array yang sama, evaluasi tidak akan menghasilkan luapan; jika tidak maka perilaku tidak akan ditentukan . Jadi, jika hasilnya bukan dalam array atau satu-melewati akhir, Anda mendapatkan UB.
berguna
37

Pointer yang bertambah adalah idiomatik C ++, karena pointer semantik mencerminkan aspek mendasar dari filosofi desain di belakang pustaka standar C ++ (berdasarkan STL Alexander Stepanov )

Konsep penting di sini, adalah bahwa STL dirancang di sekitar wadah, algoritma, dan iterator. Pointer hanyalah iterator .

Tentu saja, kemampuan untuk menambah (atau menambah / mengurangi) pointer kembali ke C. Banyak algoritma manipulasi C-string dapat ditulis hanya dengan menggunakan aritmatika pointer. Pertimbangkan kode berikut:

char string1[4] = "abc";
char string2[4];
char* src = string1;
char* dest = string2;
while ((*dest++ = *src++));

Kode ini menggunakan pointer aritmatika untuk menyalin string-null yang diakhiri. Loop secara otomatis berakhir ketika bertemu dengan nol.

Dengan C ++, pointer semantik digeneralisasikan ke konsep iterators . Paling standar C ++ wadah memberikan iterator, yang dapat diakses melalui begindan endanggota fungsi. Iterator berperilaku seperti pointer, dalam arti mereka dapat ditingkatkan, dikurangi, dan kadang-kadang dikurangi atau maju.

Untuk beralih lebih dari satu std::string, kita akan mengatakan:

std::string s = "abcdef";
std::string::iterator it = s.begin();
for (; it != s.end(); ++it) std::cout << *it;

Kami menambah iterator seperti halnya kami akan menambah pointer ke string C-plain. Alasan konsep ini sangat kuat adalah karena Anda dapat menggunakan templat untuk menulis fungsi yang akan bekerja untuk semua jenis iterator yang memenuhi persyaratan konsep yang diperlukan. Dan ini adalah kekuatan STL:

std::string s1 = "abcdef";
std::vector<char> buf;
std::copy(s1.begin(), s1.end(), std::back_inserter(buf));

Kode ini menyalin string ke dalam vektor. The copyfungsi template yang akan bekerja dengan setiap iterator yang mendukung incrementing (termasuk pointer polos). Kita bisa menggunakan copyfungsi yang sama pada string C-string:

   const char* s1 = "abcdef";
   std::vector<char> buf;
   std::copy(s1, s1 + std::strlen(s1), std::back_inserter(buf));

Kita dapat menggunakan copypada std::mapatau std::setatau wadah kustom apa pun yang mendukung iterator.

Perhatikan bahwa pointer jenis tertentu dari iterator: random akses iterator , yang berarti mereka mendukung incrementing, decrementing, dan maju dengan +dan -operator. Jenis iterator lain hanya mendukung subset dari semantik pointer: iterator dua arah mendukung setidaknya menambah dan mengurangi; a maju iterator mendukung setidaknya incrementing. (Semua jenis iterator mendukung dereferencing.) copyFungsi ini membutuhkan iterator yang setidaknya mendukung penambahan.

Anda dapat membaca tentang berbagai konsep iterator di sini .

Jadi, incrementing pointer adalah cara C ++ idiomatis untuk beralih pada C-array, atau mengakses elemen / offset dalam C-array.

Charles Salvia
sumber
3
Meskipun saya menggunakan pointer seperti pada contoh pertama, saya tidak pernah memikirkannya sebagai iterator, itu sangat masuk akal sekarang.
dyesdyes
1
"Loop secara otomatis berakhir ketika menemukan nol." Ini adalah ungkapan yang menakutkan.
Charles Wood
9
@CharlesWood, maka saya kira Anda harus menemukan C yang cukup menakutkan
Siler
7
@CharlesWood: Alternatifnya adalah menggunakan panjang string sebagai variabel kontrol loop, yang berarti melintasi string dua kali (satu kali untuk menentukan panjangnya, dan satu kali untuk menyalin karakter). Saat Anda menjalankan PDP-7 1MHz, itu bisa benar-benar mulai bertambah.
TMN
3
@ Indek: pertama-tama, C dan C ++ mencoba untuk menghindari di semua biaya untuk memperkenalkan perubahan yang melanggar - dan saya akan mengatakan bahwa mengubah perilaku default string literal akan sangat modifikasi. Tetapi yang paling penting, string tanpa-penghentian nol hanyalah sebuah konvensi (dibuat mudah diikuti oleh fakta bahwa string literal adalah tanpa-penghentian secara default dan bahwa fungsi perpustakaan mengharapkannya), tidak ada yang menghentikan Anda dari menggunakan string yang dihitung dalam C - sebenarnya, beberapa C library menggunakannya (lihat misalnya OLE's BSTR).
Matteo Italia
16

Pointer aritmatika berada di C ++ karena berada di C. Aritmatika pointer ada di C karena itu adalah idiom normal dalam assembler .

Ada banyak sistem di mana "register kenaikan" lebih cepat dari "memuat nilai konstan 1 dan menambah mendaftar". Selain itu, beberapa sistem memungkinkan Anda melakukan "muat DWORD ke A dari alamat yang ditentukan dalam register B, lalu tambahkan sizeof (DWORD) ke B" dalam satu instruksi. Saat ini Anda mungkin mengharapkan kompiler pengoptimal untuk menyelesaikan masalah ini untuk Anda, tetapi ini bukan pilihan pada tahun 1973.

Ini pada dasarnya adalah alasan yang sama bahwa array C tidak diperiksa batas dan string C tidak memiliki ukuran yang tertanam di dalamnya: bahasa dikembangkan pada sistem di mana setiap byte dan setiap instruksi dihitung.

pjc50
sumber