ทำไมถึงต้องเพิ่มพอยน์เตอร์?


25

ฉันเพิ่งเริ่มเรียนรู้ C ++ และเป็นคนส่วนใหญ่ (ตามสิ่งที่ฉันได้อ่าน) ฉันกำลังดิ้นรนกับพอยน์เตอร์

ไม่ใช่ในความหมายดั้งเดิมฉันเข้าใจว่าพวกเขาคืออะไรและทำไมจึงใช้และวิธีที่มีประโยชน์อย่างไร แต่ฉันไม่เข้าใจว่าตัวชี้การเพิ่มขึ้นจะมีประโยชน์ใคร ๆ สามารถให้คำอธิบายว่าการเพิ่มพอยน์เตอร์เป็นอย่างไร แนวคิดที่มีประโยชน์และสำนวน C ++?

คำถามนี้เกิดขึ้นหลังจากที่ฉันเริ่มอ่านหนังสือA Tour of C ++โดย Bjarne Stroustrup ฉันแนะนำหนังสือเล่มนี้เพราะฉันค่อนข้างคุ้นเคยกับ Java และคนที่ Reddit บอกว่ามันจะเป็นหนังสือที่ 'ดีมาก' .


11
ตัวชี้เป็นเพียงตัววนซ้ำ
Charles Salvia

1
เป็นหนึ่งในเครื่องมือที่ชื่นชอบในการเขียนไวรัสคอมพิวเตอร์ที่อ่านสิ่งที่ไม่ควรอ่าน นอกจากนี้ยังเป็นหนึ่งในกรณีที่พบบ่อยที่สุดของความเสี่ยงในแอพ (เมื่อตัวชี้เพิ่มขึ้นหนึ่งครั้งผ่านบริเวณที่ควรจะทำการอ่านหรือเขียน)> ดูข้อบกพร่อง HeartBleed
Sam

1
@vasile นี่คือสิ่งที่ไม่ดีเกี่ยวกับพอยน์เตอร์
Cruncher

4
สิ่งที่ดี / ไม่ดีเกี่ยวกับ C ++ คือมันช่วยให้คุณทำอะไรได้มากขึ้นก่อนที่จะเรียก segfault โดยปกติแล้วคุณจะได้รับ segfault เมื่อพยายามเข้าถึงหน่วยความจำของกระบวนการหน่วยความจำระบบหรือหน่วยความจำแอพที่ได้รับการป้องกัน การเข้าถึงใด ๆ ภายในหน้าแอปพลิเคชันปกติได้รับอนุญาตจากระบบและมันอยู่ที่โปรแกรมเมอร์ / คอมไพเลอร์ / ภาษาเพื่อบังคับใช้ข้อ จำกัด ที่สมเหตุสมผล C ++ ช่วยให้คุณทำอะไรก็ได้ที่คุณต้องการ สำหรับ openssl มีตัวจัดการหน่วยความจำของตัวเอง - มันไม่เป็นความจริง มันเพิ่งมีกลไกการเข้าถึงหน่วยความจำ C ++ เริ่มต้น
Sam

1
@Indek: คุณจะได้รับ segfault เท่านั้นหากหน่วยความจำที่คุณพยายามเข้าถึงได้รับการป้องกัน ระบบปฏิบัติการส่วนใหญ่กำหนดการป้องกันในระดับหน้าดังนั้นโดยปกติคุณสามารถเข้าถึงสิ่งที่อยู่บนเพจที่ตัวชี้ของคุณเริ่มต้น หากระบบปฏิบัติการใช้ขนาดหน้า 4K นั่นเป็นจำนวนข้อมูลที่พอใช้ หากตัวชี้ของคุณเริ่มต้นที่ใดที่หนึ่งในกองใคร ๆ ก็เดาได้ว่าคุณสามารถเข้าถึงข้อมูลได้มากแค่ไหน
TMN

คำตอบ:


46

เมื่อคุณมีอาร์เรย์คุณสามารถตั้งค่าตัวชี้ให้ชี้ไปที่องค์ประกอบของอาร์เรย์ได้:

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

นี่pชี้ไปที่องค์ประกอบแรกของซึ่งเป็นa a[0]ตอนนี้คุณสามารถเพิ่มตัวชี้ให้ชี้ไปที่องค์ประกอบถัดไป:

p++;

ตอนนี้pชี้ไปที่องค์ประกอบที่สอง, a[1]. *pคุณสามารถเข้าถึงองค์ประกอบที่นี่ใช้ สิ่งนี้แตกต่างจาก Java ที่คุณจะต้องใช้ตัวแปรดัชนีจำนวนเต็มเพื่อเข้าถึงองค์ประกอบของอาร์เรย์

การเพิ่มตัวชี้ใน C ++ ที่ตัวชี้ที่ไม่ได้ชี้ไปที่องค์ประกอบของอาร์เรย์เป็นพฤติกรรมที่ไม่ได้กำหนด


23
ใช่ด้วย C ++ คุณมีหน้าที่รับผิดชอบในการหลีกเลี่ยงข้อผิดพลาดในการเขียนโปรแกรมเช่นการเข้าถึงนอกขอบเขตของอาร์เรย์
Greg Hewgill

9
ไม่การเพิ่มพอยน์เตอร์ที่ชี้ไปยังสิ่งใด ๆยกเว้นองค์ประกอบอาเรย์นั้นเป็นพฤติกรรมที่ไม่ได้กำหนด อย่างไรก็ตามหากคุณกำลังทำบางสิ่งในระดับต่ำและไม่ใช่แบบพกพาการเพิ่มตัวชี้มักจะไม่มีอะไรมากไปกว่าการเข้าถึงสิ่งต่อไปในหน่วยความจำไม่ว่าอะไรจะเกิดขึ้น
Greg Hewgill

4
มีบางสิ่งที่เป็นหรือสามารถถือว่าเป็นอาเรย์ อันที่จริงแล้วสตริงของข้อความคืออาร์เรย์ของอักขระ ในบางกรณี int ที่มีความยาวจะถือว่าเป็นอาร์เรย์ของไบต์แม้ว่าจะทำให้คุณมีปัญหาได้อย่างง่ายดาย
AMADANON Inc.

6
ที่บอกประเภทของคุณแต่พฤติกรรมอธิบายไว้ใน 5.7 ตัวดำเนินการเสริม [expr.add] โดยเฉพาะ 5.7 / 5 กล่าวว่าการไปที่ใดก็ได้นอกแถวยกเว้นหนึ่งในอดีตคือ UB
ไร้ประโยชน์

4
ย่อหน้าสุดท้ายคือ: ถ้าทั้งตัวถูกดำเนินการตัวชี้และจุดผลลัพธ์ไปยังองค์ประกอบของวัตถุอาร์เรย์เดียวกันการประเมินจะไม่ทำให้เกิดการล้น มิฉะนั้นพฤติกรรมจะไม่ได้กำหนด ดังนั้นหากผลลัพธ์ไม่ได้อยู่ในอาร์เรย์หรือเมื่อจบหนึ่งครั้งคุณจะได้รับ UB
ไร้ประโยชน์

37

การเพิ่มพอยน์เตอร์เป็นสำนวน C ++, เพราะซีแมนทิกส์ตัวชี้สะท้อนลักษณะพื้นฐานของปรัชญาการออกแบบที่อยู่เบื้องหลังไลบรารีมาตรฐาน C ++ (อิงจากSTLของ Alexander Stepanov )

แนวคิดที่สำคัญในที่นี้คือ STL ได้รับการออกแบบรอบคอนเทนเนอร์อัลกอริทึมและตัววนซ้ำ ชี้เป็นเพียงiterators

แน่นอนความสามารถในการเพิ่ม (หรือเพิ่ม / ลบ) ตัวชี้กลับไปที่ C อัลกอริธึมการจัดการสตริง C จำนวนมากสามารถเขียนได้อย่างง่ายดายโดยใช้ตัวชี้ทางคณิตศาสตร์ พิจารณารหัสต่อไปนี้:

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

รหัสนี้ใช้เลขคณิตของตัวชี้เพื่อคัดลอก C-string ที่มีค่า Null การวนซ้ำจะสิ้นสุดลงโดยอัตโนมัติเมื่อพบโมฆะ

ด้วย C ++ ความหมายชี้กำลังทั่วไปกับแนวคิดของiterators คอนเทนเนอร์ C ++ มาตรฐานส่วนใหญ่จะมีตัววนซ้ำซึ่งสามารถเข้าถึงได้ผ่านฟังก์ชั่นbeginและendสมาชิก ตัวทำซ้ำทำตัวเหมือนพอยน์เตอร์ซึ่งสามารถเพิ่มขึ้น dereferenced และบางครั้งมีค่าลดลงหรือขั้นสูง

เพื่อย้ำมากกว่าstd::stringเราจะพูดว่า:

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

เราเพิ่มตัววนซ้ำเหมือนเราจะเพิ่มตัวชี้ไปยัง C-string ธรรมดา เหตุผลที่แนวคิดนี้มีประสิทธิภาพเพราะคุณสามารถใช้เทมเพลตเพื่อเขียนฟังก์ชั่นที่จะทำงานกับตัววนซ้ำชนิดใดก็ได้ที่ตรงตามข้อกำหนดของแนวคิดที่จำเป็น และนี่คือพลังของ STL:

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

รหัสนี้คัดลอกสตริงลงในเวกเตอร์ copyฟังก์ชั่นเป็นแม่แบบที่จะทำงานร่วมกับใด ๆ iterator ที่สนับสนุนการเพิ่ม (ซึ่งรวมถึงตัวชี้ธรรมดา) เราสามารถใช้copyฟังก์ชั่นเดียวกันกับ C-string ธรรมดา:

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

เราสามารถใช้copyบนstd::mapหรือstd::setหรือใด ๆภาชนะที่กำหนดเองที่ iterators สนับสนุน

โปรดทราบว่าพอยน์เตอร์เป็นประเภทเฉพาะของ iterator: random access iteratorซึ่งหมายความว่ารองรับการเพิ่มขึ้นลดลงและล้ำหน้าด้วยตัวดำเนินการ+และ -ชนิดตัววนซ้ำอื่น ๆ สนับสนุนชุดย่อยของซีแมนติกพอยน์เตอร์เท่านั้น: ตัววนซ้ำสองทิศทางสนับสนุนการเพิ่มและการลดลงอย่างน้อย; ไปข้างหน้า iteratorสนับสนุนที่ incrementing น้อย (ทุกชนิดตัววนซ้ำสนับสนุนการยกเลิกการลงทะเบียน) copyฟังก์ชันต้องการตัววนซ้ำที่อย่างน้อยรองรับการเพิ่มขึ้น

คุณสามารถอ่านเกี่ยวกับแนวคิด iterator ที่แตกต่างกันที่นี่

ดังนั้นการเพิ่มพอยน์เตอร์จึงเป็นวิธี C ++ ที่ใช้ในการวนซ้ำใน C-array หรือเข้าถึงองค์ประกอบ / ออฟเซ็ตใน C-array


3
แม้ว่าฉันจะใช้พอยน์เตอร์เหมือนในตัวอย่างแรก แต่ฉันไม่เคยคิดเลยว่ามันจะเป็นตัววนซ้ำ แต่มันก็สมเหตุสมผลดีในตอนนี้
สีย้อม

1
"การวนซ้ำอัตโนมัติเมื่อพบโมฆะ" นี่เป็นสำนวนที่น่ากลัว
Charles Wood

9
@CharlesWood จากนั้นฉันเดาว่าคุณต้องหา C ที่น่ากลัวมาก
Siler

7
@CharlesWood: ทางเลือกคือการใช้ความยาวของสตริงเป็นตัวแปรควบคุมการวนรอบซึ่งหมายถึงการสำรวจสตริงสองครั้ง (หนึ่งครั้งเพื่อกำหนดความยาวและอีกครั้งเพื่อคัดลอกอักขระ) เมื่อคุณใช้งานด้วย 1MHz PDP-7 มันสามารถเริ่มเพิ่มได้จริงๆ
TMN

3
@Indek: อย่างแรกเลย, C และ C ++ พยายามหลีกเลี่ยงค่าใช้จ่ายทั้งหมดในการแนะนำการเปลี่ยนแปลงที่มีข้อผิดพลาด - และฉันจะบอกว่าการเปลี่ยนพฤติกรรมเริ่มต้นของตัวอักษรสตริงจะเป็นการปรับเปลี่ยน แต่ที่สำคัญที่สุดสตริง zero-terminated เป็นเพียงการประชุม (ทำให้ง่ายต่อการตามความจริงที่ว่าสตริงตัวอักษรจะถูกยกเลิกโดยศูนย์และฟังก์ชันไลบรารีคาดหวังว่าจะไม่มีใครหยุดคุณจากการใช้สตริงที่นับใน C - จริง ๆ แล้ว ห้องสมุด C หลายแห่งใช้มัน (ดูเช่น BSTR ของ OLE)
Matteo Italia

16

เลขคณิตตัวชี้อยู่ใน C ++ เพราะอยู่ใน C. เลขคณิตตัวชี้อยู่ใน C เพราะมันเป็นสำนวนปกติในแอสเซมเบลอร์

มีระบบมากมายที่ "การเพิ่มค่าการลงทะเบียน" เร็วกว่า "ค่าคงที่โหลด 1 และเพิ่มเพื่อลงทะเบียน" ยิ่งไปกว่านั้นระบบบางระบบอนุญาตให้คุณ "โหลด DWORD ลงใน A จากที่อยู่ที่ระบุใน register B จากนั้นเพิ่ม sizeof (DWORD) ไปยัง B" ในคำสั่งเดียว วันนี้คุณอาจคาดหวังว่าคอมไพเลอร์ที่ได้รับการปรับให้เหมาะสมเพื่อแยกแยะสิ่งนี้สำหรับคุณ แต่นี่ไม่ใช่ตัวเลือกในปี 1973

นี่เป็นเหตุผลเดียวกันกับที่อาร์เรย์ C ไม่ได้ตรวจสอบขอบเขตและสตริง C ไม่มีขนาดที่ฝังอยู่ในนั้น: ภาษานั้นพัฒนาบนระบบที่มีการนับจำนวนไบต์และคำสั่งทุกคำสั่ง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.