เหตุใดไวยากรณ์ Delete[] จึงมีอยู่ใน C ++

ทุกครั้งที่มีคนถามคำถามเกี่ยวกับ delete[] ที่นี่ มันก็มักจะมีคำถามทั่วไปอยู่เสมอว่า C++ ทำอย่างไร ให้ใช้ delete[] การตอบกลับ มาจากพื้นหลังของ vanilla C สิ่งที่ฉันไม่เข้าใจคือเหตุใดจึงต้องมีการร้องขอที่แตกต่างออกไปเลย

ด้วย malloc()/free() ตัวเลือกของคุณคือรับตัวชี้ไปยังบล็อกหน่วยความจำที่อยู่ติดกัน และเพิ่มบล็อกหน่วยความจำที่อยู่ติดกัน มีบางอย่างในการดำเนินการเกิดขึ้นและรู้ว่าบล็อกที่คุณจัดสรรนั้นมีขนาดเท่าใดตามที่อยู่ฐาน สำหรับเวลาที่คุณต้องปลดปล่อยมัน

ไม่มีฟังก์ชัน free_array() ฉันเคยเห็นทฤษฎีบ้าๆ บางอย่างในคำถามอื่นๆ ที่เกี่ยวข้องกับเรื่องนี้ เช่น การเรียก delete ptr จะปล่อยเฉพาะส่วนบนสุดของอาร์เรย์เท่านั้น ไม่ใช่ทั้งอาร์เรย์ หรือที่ถูกต้องกว่านั้นคือไม่ได้ถูกกำหนดโดยการดำเนินการ และแน่นอนว่า... ถ้านี่เป็นเวอร์ชันแรกของ C++ และคุณมีตัวเลือกการออกแบบแปลกๆ ที่สมเหตุสมผล แต่เหตุใดมาตรฐาน C++ ของ $PRESENT_YEAR จึงไม่โอเวอร์โหลด???

ดูเหมือนว่าจะเป็นบิตพิเศษเพียงอย่างเดียวที่ C++ เพิ่มคือการผ่านอาร์เรย์และการเรียกตัวทำลาย และฉันคิดว่านี่อาจเป็นจุดสำคัญของมัน และแท้จริงแล้วมันกำลังใช้ฟังก์ชันแยกต่างหากเพื่อช่วยเราในการค้นหาความยาวรันไทม์ครั้งเดียว หรือ nullptr ในตอนท้ายของรายการเพื่อแลกกับการทรมานโปรแกรมเมอร์หรือโปรแกรมเมอร์ C++ ใหม่ทุกคนที่มีวันคลุมเครือและลืมไปว่ามีคำสงวนที่แตกต่างกัน

ใครช่วยอธิบายให้ชัดเจนสักครั้งได้ไหมหากมีเหตุผลนอกเหนือจากนั่นคือสิ่งที่มาตรฐานบอกและไม่มีใครสงสัยเลย


person awiebe    schedule 17.05.2021    source แหล่งที่มา
comment
หากคุณต้องการทดสอบการจัดสรรหน่วยความจำของคุณและปล่อยให้เป็นอิสระเพื่อดูว่าทฤษฎีบ้าๆ เหล่านั้นถูกต้องหรือไม่ คุณสามารถใช้ Valgrind เพื่อดูว่าเกิดอะไรขึ้นจริง ฉันสงสัยว่าการลบมากเกินไปนั้นมีปัญหามากกว่าที่อธิบายไว้ในคำตอบ แต่ฉันไม่มีความเชี่ยวชาญ   -  person ttbek    schedule 18.05.2021
comment
คำถามที่เกี่ยวข้อง: delete[] รู้ได้อย่างไรว่าเป็นอาร์เรย์ และโดยเฉพาะอย่างยิ่งหมายเหตุ คำตอบนี้   -  person Fred Larson    schedule 18.05.2021


คำตอบ (7)


ออบเจ็กต์ในภาษา C++ มักจะมีตัวทำลายที่ต้องทำงานเมื่อสิ้นสุดอายุการใช้งาน delete[] ทำให้แน่ใจว่ามีการเรียกตัวทำลายของแต่ละองค์ประกอบของอาร์เรย์ แต่การทำเช่นนี้ มีค่าใช้จ่ายที่ไม่ได้ระบุ ในขณะที่ delete ไม่. อันหนึ่งสำหรับอาร์เรย์ซึ่งจ่ายค่าใช้จ่ายและอีกอันสำหรับออบเจ็กต์เดี่ยวที่ไม่จ่าย

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

delete สิ่งที่คุณ new เสมอ และ delete[] สิ่งที่คุณ new[] เสมอ แต่ในภาษา C++ สมัยใหม่ โดยทั่วไปแล้ว new และ new[] จะไม่ถูกใช้อีกต่อไป ใช้ std::make_unique, std::make_shared, std::vector หรือทางเลือกอื่นๆ ที่ชัดเจนและปลอดภัยยิ่งขึ้น

person François Andrieux    schedule 17.05.2021
comment
ว้าว นั่นเป็นการตอบกลับที่รวดเร็ว ขอบคุณสำหรับคำแนะนำเกี่ยวกับฟังก์ชันการจัดสรร เป็นเรื่องที่น่าแปลกใจที่คำตอบใน C ++ ไม่ใช้คำหลักนั้นบ่อยเพียงใด ใช้ std::someWeirdFunctionIntroducedInC++›=11() - person awiebe; 18.05.2021
comment
@awiebe C++ มอบเครื่องมือให้คุณทำงานใกล้เคียงกับฮาร์ดแวร์มากที่สุด แต่เครื่องมือเหล่านั้นมักจะทรงพลังและทื่อ ทำให้เกิดอันตรายและใช้งานอย่างมีประสิทธิภาพได้ยาก ดังนั้นจึงมีเครื่องมือผ่านไลบรารีมาตรฐานที่อยู่ห่างจากฮาร์ดแวร์ เล็กน้อย แต่ปลอดภัยและใช้งานง่ายมาก นี่คือเหตุผลว่าทำไมคุณจึงได้เรียนรู้เกี่ยวกับคุณสมบัติต่างๆ มากมาย แต่กลับถูกสั่งห้ามไม่ให้ใช้คุณสมบัติเหล่านั้น เพราะเว้นแต่ว่าคุณกำลังทำอะไรที่พิเศษหรือแปลกประหลาด เครื่องมือระดับต่ำเหล่านั้นก็ไม่มีประโยชน์ โดยทั่วไปแล้วฟีเจอร์ที่สะดวกกว่านั้นก็ใช้ได้ - person François Andrieux; 18.05.2021
comment
@awiebe คุณถูกต้องว่าโดยส่วนใหญ่หากมีฟีเจอร์ไลบรารี่มาตรฐานที่เรียบร้อยที่จะแทนที่กลไกในตัว มันมาจาก C ++ 11 หรือใหม่กว่า โดยพื้นฐานแล้ว C++11 ได้ปฏิวัติภาษา ทำให้สามารถใช้ฟีเจอร์ไลบรารี่มาตรฐานที่เมื่อก่อนไม่สามารถทำได้ ความแตกต่างระหว่าง C++11 และเวอร์ชันก่อนหน้านั้นสำคัญมากจนสามารถมองได้ว่าเป็นสองภาษาที่แตกต่างกัน เมื่อเรียน C++ ระวัง ให้แยกแยะระหว่างสื่อการศึกษาที่กำหนดเป้าหมาย C++03 และรุ่นก่อนหน้า จากการกำหนดเป้าหมายสื่อ C++11 และใหม่กว่า - person François Andrieux; 18.05.2021
comment
@awiebe โปรดทราบว่าการมีอยู่ของกลไกระดับล่างเช่น new อนุญาตให้ไลบรารีมาตรฐานส่วนใหญ่ (และไลบรารีอื่น ๆ ) เขียนด้วย C++ ล้วนๆ (บางส่วนอาจต้องการการสนับสนุนคอมไพเลอร์) ดังนั้นคำแนะนำอาจใช้เพื่อสร้างนามธรรมในระดับที่สูงขึ้นเท่านั้น - person Carsten S; 18.05.2021
comment
คำตอบนี้ดูเหมือนไม่ถูกต้อง ทั้ง delete และ delete[] เรียกตัวทำลาย - person Barmar; 18.05.2021
comment
@Barmar ฉันใช้คำใหม่เพื่อให้ชัดเจนยิ่งขึ้นว่า delete[] อาจมีค่าใช้จ่ายเพิ่มเติมที่เกี่ยวข้องกับการทำลายแต่ละองค์ประกอบอาร์เรย์ - person François Andrieux; 18.05.2021
comment
การหลีกเลี่ยงการวนซ้ำเล็กๆ น้อยๆ ในกรณีที่มีองค์ประกอบเดียวดูเหมือนจะเป็นการเพิ่มประสิทธิภาพแบบไมโครอย่างน่าสงสัย แต่บางทีมันอาจจะสมเหตุสมผลในช่วงแรกๆ ที่คอมพิวเตอร์ทำงานช้าลงและ C++ ก็เข้าใกล้ C มากขึ้น - person Barmar; 18.05.2021
comment
@Barmar ฉันคิดว่าข้อกังวลนั้นเกี่ยวกับหน่วยความจำมากกว่าค่าใช้จ่ายมากกว่าความเร็ว ปัญหาจะชัดเจนยิ่งขึ้นด้วยการวางตำแหน่งใหม่ โดยที่การวางตำแหน่งอาร์เรย์แบบพกพาใหม่ไม่สามารถทำได้ในปัจจุบันเนื่องจากค่าใช้จ่ายที่อาจเกิดขึ้น ดู สามารถใช้ตำแหน่งใหม่สำหรับอาร์เรย์ใน แบบพกพา. คุณอาจต้องส่งพื้นที่เก็บข้อมูลมากกว่าที่องค์ประกอบต่างๆ ต้องการ แต่เป็นไปไม่ได้ที่จะถามการใช้งานว่าต้องใช้พื้นที่เก็บข้อมูลเพิ่มเติมเท่าใด - person François Andrieux; 18.05.2021
comment
@ FrançoisAndrieux: ตัวเลือกของคำ-nitpick ...เครื่องมือเหล่านั้นมักจะทรงพลังและทื่อ...: จริง ๆ แล้วฉันเห็นว่ามันเป็นเครื่องมือผ่าตัดที่คมกริบ: คุณสามารถได้สิ่งที่คุณต้องการอย่างแน่นอนตามที่คุณต้องการ แต่การเย็บหรือทำความสะอาดขั้นตอนการผ่าตัดต้องใช้ทักษะและวัสดุที่เทียบเท่ากัน ซึ่งพลาสเตอร์ช่วยไม่สามารถทำได้ - person Willem van Rumpt; 18.05.2021
comment
@WillemvanRumpt คุณคงพูดถูก การเปรียบเทียบการผ่าตัดน่าจะดีกว่า ฉันเลือกทื่อเพราะคุณสามารถทำงานด้วยเครื่องมือทื่อได้ แต่คุณอาจต้องใช้ความพยายามมากขึ้นในการทำงาน - person François Andrieux; 18.05.2021
comment
ไม่จำเป็นสำหรับตัวทำลายเท่านั้น แต่ยังสำหรับการเพิ่มหน่วยความจำบนระบบโดยที่ตัวจัดสรรไม่ทราบขนาดของบล็อกหน่วยความจำ และต้องระบุเมื่อปล่อย ตัวอย่างเช่น บน AmigaOS การกระทำ char *p = AllocMem(64); FreeMem(p + 16, 32); เป็นสิ่งที่ถูกกฎหมาย โดยจะทำให้คุณมีการจัดสรรสองครั้ง ครั้งละ 16 ไบต์ ที่ p และ p+48 บน AmigaOS delete o; จะเรียก FreeMem(o, sizeof *o); ดังนั้นการใช้การลบที่ไม่ใช่อาร์เรย์ในอาร์เรย์จะทำให้เฉพาะองค์ประกอบแรกว่างเท่านั้น - person Simon Richter; 18.05.2021
comment
ความจริงที่ว่า delete pointer และ delete [] pointer ใช้ฟังก์ชันการจัดสรรคืนที่แตกต่างกันไม่เกี่ยวอะไรกับครั้งแรกที่ต้องจัดการกับการลบโพลีมอร์ฟิกที่อาจเกิดขึ้น หรือครั้งที่สองต้องกู้คืนจำนวนองค์ประกอบที่จะลบ เมื่อคุณไปถึงฟังก์ชั่นการจัดสรรคืน นั่นก็เสร็จสิ้นแล้ว ฉันไม่ทราบถึงเหตุผลที่สมเหตุสมผลสำหรับพวกเขาโดยใช้ชุดฟังก์ชันการจัดสรรและการจัดสรรคืนแยกต่างหาก - person Deduplicator; 18.05.2021
comment
@Deduplicator ฉันไม่แน่ใจเพียงพอว่าทำไมจึงมีฟังก์ชันการจัดสรรคืนที่แตกต่างกันสำหรับ delete และ delete[] ฉันไม่ชัดเจนสำหรับฉันว่ามันเป็นไปไม่ได้ที่จะมีเวอร์ชันเดียวที่สามารถจัดการทั้งสองกรณีได้โดยไม่มีค่าใช้จ่าย ฉันจะนำคำถามส่วนนั้นกลับออกจากคำตอบของฉัน ส่วนเกี่ยวกับตัวทำลายควรจะเพียงพอที่จะพิสูจน์ความแตกต่างระหว่าง delete และ delete[] แก้ไข : อย่าลืม* - person François Andrieux; 18.05.2021
comment
@Barmar: คุณพูดถูกเกี่ยวกับ "การเพิ่มประสิทธิภาพแบบไมโครที่อยากรู้อยากเห็น" AFAICT มันจะเป็นมาตรฐานที่ถูกต้องโดยสิ้นเชิงสำหรับคอมไพเลอร์ C++ ที่จะใช้ new X เช่นเดียวกับ new X[1] และ delete เช่นเดียวกับ delete[] ดังนั้นจึงให้อภัยการใช้ตัวดำเนินการลบที่ไม่ถูกต้องโดยไม่ตั้งใจ แต่นั่นหมายถึงการ "สิ้นเปลือง" หน่วยความจำที่จัดเก็บความยาวอาเรย์ - person dan04; 18.05.2021
comment
@ dan04 มาตรฐานต้องการให้นิพจน์ใหม่ที่ไม่ใช่อาร์เรย์ที่สร้าง T (new T;) จำเป็นต้องร้องขอ sizeof(T) ไบต์จากฟังก์ชันการจัดสรร เนื่องจากกฎนี้ คุณจึงสามารถใช้ new T; เป็น new T[1]; ได้ก็ต่อเมื่อนิพจน์ใหม่ที่มีประเภทอาร์เรย์ไม่มีค่าใช้จ่ายในการจัดสรรอาร์เรย์บนแพลตฟอร์มนั้น ดู : อาร์กิวเมนต์นั้นจะต้องมีขนาดไม่ต่ำกว่าขนาด ของวัตถุที่ถูกสร้างขึ้น มันอาจจะใหญ่กว่าขนาดของวัตถุที่ถูกสร้างขึ้นก็ต่อเมื่อวัตถุนั้นเป็นอาร์เรย์ - person François Andrieux; 18.05.2021
comment
@Deduplicator ในความคิดที่สอง มีข้อ จำกัด อื่น เนื่องจากเป็นไปได้ที่จะแทนที่ operator new แยกจาก operator new[] แม้ว่าอาจจะเป็นไปไม่ได้ก็ตาม - person François Andrieux; 18.05.2021
comment
@FrançoisAndrieux ใช่แล้ว การมีฟังก์ชั่นทั้งสองแยกกันนั้นไม่สะดวกมาก - person Deduplicator; 18.05.2021
comment
ใช่ เมื่อพวกเขาตัดสินใจออกแบบตั้งแต่แรกเริ่ม มันก็มีการแตกสาขาที่กระเพื่อมผ่านข้อมูลจำเพาะ - person Barmar; 19.05.2021
comment
โปรดทราบว่าบางครั้งคุณต้องจับคู่ new T() กับ delete[] รูปแบบสเกลาร์แบบใหม่มีความสามารถในการจัดสรรอาเรย์ได้อย่างสมบูรณ์แบบ หากประเภทจริงเป็นอาเรย์ - person Ben Voigt; 19.05.2021
comment
คำตอบมักจะใช้ฟังก์ชันแปลก ๆ ที่อาจมาจาก C++11 อย่าง @awiebe เนื่องจากส่วนที่สังเกตได้ชัดเจนของไลบรารีมาตรฐานมีไว้เพื่อจัดเตรียมคุณลักษณะของภาษาหลักเพื่อให้คอมไพเลอร์ทำหน้าที่ยกงานหนักให้กับคุณเมื่อใดก็ตามที่คุณ ไม่จำเป็นต้องเพิ่มประสิทธิภาพบางสิ่งให้ถึงขีดจำกัดที่แน่นอน มันแลกกับความเร็วที่ลดลงเล็กน้อยและ/หรือขนาดปฏิบัติการที่เพิ่มขึ้นสำหรับโค้ดที่อ่านได้ ลดการทำบัญชีในส่วนของคุณและรับประกันความปลอดภัยที่ดีขึ้น ตัวอย่างเช่น std::vector เป็นเพียง wrapper สวยๆ ที่จัดการ new[] และ delete[] ให้กับคุณ พร้อมด้วยส่วนเสริม - person Justin Time - Reinstate Monica; 19.05.2021
comment
@FrançoisAndrieux: C++ ให้เครื่องมือแก่คุณในการทำงานใกล้เคียงกับฮาร์ดแวร์มากที่สุด - ยกเว้นเมื่อคณะกรรมการตัดสินใจว่าจะไม่ทำ โปรดทราบว่า C++ new/delete ไม่มีกลไก C realloc ชนิดใด ๆ (หรือ try_realloc สำหรับการคัดลอกไม่ได้เล็กน้อย) ที่สามารถขยายการจัดสรรที่มีอยู่เพื่อหลีกเลี่ยงการคัดลอกเมื่อเป็นไปได้ ระบบปฏิบัติการจริงส่วนใหญ่สามารถทำได้ และหากไม่ใช่การใช้งานเล็กน้อยก็แค่สร้างใหม่/คัดลอก/ลบ หรือคืนค่าเท็จสำหรับ try_realloc ซึ่งทำให้ std::vector จำนวนมากเสียเวลา แบนด์วิดท์หน่วยความจำ และข้อผิดพลาดของหน้าไปมาก คัดลอกเนื้อหาต่างๆ ไปเรื่อย ๆ เมื่อมันขยายใหญ่ขึ้น หากคุณไม่ .reserve ล่วงหน้า - person Peter Cordes; 19.05.2021
comment
บีจาร์นพูดมานานแล้วว่า [] เป็นความผิดพลาด - person philipxy; 19.05.2021
comment
@BenVoigt มีเสื้อยืด Keep C ++ แปลกไหม? - person Peter - Reinstate Monica; 20.05.2021
comment
@philipxy: จำเป็นต้องมีการอ้างอิง - person Ben Voigt; 20.05.2021

โดยพื้นฐานแล้ว malloc และ free จัดสรรหน่วยความจำ และ new และ delete สร้างและทำลายอ็อบเจ็กต์ ดังนั้นคุณต้องรู้ว่าวัตถุคืออะไร

หากต้องการอธิบายรายละเอียดเกี่ยวกับคำตอบของ François Andrieux ที่ไม่ระบุรายละเอียด คุณสามารถดูคำตอบของฉันสำหรับคำถามนี้ ซึ่งฉันได้ตรวจสอบสิ่งที่ การใช้งานเฉพาะ (Visual C++ 2013, 32 บิต) การใช้งานอื่นๆ อาจทำหรือไม่ทำสิ่งที่คล้ายกัน

ในกรณีที่ new[] ถูกใช้กับอาร์เรย์ของอ็อบเจ็กต์ที่มีตัวทำลายที่ไม่ซับซ้อน สิ่งที่มันทำคือการจัดสรรเพิ่มอีก 4 ไบต์ และการส่งคืนตัวชี้ถูกเลื่อนไปข้างหน้า 4 ไบต์ ดังนั้นเมื่อ delete[] ต้องการทราบว่ามีอ็อบเจ็กต์อยู่กี่อ็อบเจ็กต์ มัน รับตัวชี้ เลื่อนไป 4 ไบต์ก่อนหน้า และรับตัวเลขไปยังที่อยู่นั้นและถือว่ามันเป็นจำนวนอ็อบเจ็กต์ที่เก็บไว้ที่นั่น จากนั้นจะเรียก destructor ในแต่ละวัตถุ (ขนาดของวัตถุนั้นทราบจากประเภทของตัวชี้ที่ส่งผ่าน) จากนั้น เพื่อที่จะเปิดเผยที่อยู่ที่แน่นอน มันจะส่งที่อยู่ที่มีขนาด 4 ไบต์ก่อนที่อยู่ที่ส่งไป

ในการใช้งานนี้ การส่งผ่านอาร์เรย์ที่จัดสรรด้วย new[] ไปยัง delete ปกติจะส่งผลให้มีการเรียกตัวทำลายล้างตัวเดียวขององค์ประกอบแรก ตามด้วยการส่งที่อยู่ผิดไปยังฟังก์ชันการจัดสรรคืน ซึ่งจะทำให้ฮีปเสียหาย อย่าทำ!

person milleniumbug    schedule 18.05.2021

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

พวกมันมีซีแมนทิกส์ C ดั้งเดิมและไม่มีซีแมนทิกส์ C++ ดังนั้นคอมไพเลอร์และรันไทม์จึงรองรับ C++ ซึ่งจะช่วยให้คุณหรือระบบรันไทม์ของคอมไพเลอร์ทำสิ่งที่มีประโยชน์พร้อมกับพอยน์เตอร์ได้

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

หูดในภาษา C++ นี้เกิดขึ้นเนื่องจากมรดกจาก C และยังคงเป็นส่วนหนึ่งของภาษา - แม้ว่าตอนนี้เราจะมี std::array สำหรับอาร์เรย์ที่มีความยาวคงที่และ (มีเสมอ) std::vector สำหรับอาร์เรย์ที่มีความยาวผันแปรได้ - ส่วนใหญ่เพื่อวัตถุประสงค์ของ ความเข้ากันได้: ความสามารถในการเรียกจาก C ++ ไปยัง API ของระบบปฏิบัติการและไปยังไลบรารีที่เขียนในภาษาอื่นโดยใช้การทำงานร่วมกันของภาษา C

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

person davidbak    schedule 18.05.2021
comment
คำตอบนี้ทำการอ้างสิทธิ์ที่ไม่ถูกต้องจำนวนหนึ่งโดยเห็นได้ชัดว่าไม่รู้ว่าทั้ง C และ C ++ รองรับประเภทตัวชี้ถึงอาเรย์ ไม่ใช่การขาดความสามารถในการแสดงตัวชี้ไปยังอาเรย์ แต่เป็นการยกเลิกความสามารถนั้นในทางปฏิบัติ - person Ben Voigt; 19.05.2021
comment
ตัวชี้ไปยังอาร์เรย์จะสลายตัวไปยังตัวชี้ไปยังองค์ประกอบทันที และนั่นคือวิธีการใช้ ไม่ใช่เหรอ? ลายเซ็นฟังก์ชัน/เมธอด C++ (หรือ C) จำนวนเท่าใดที่ใช้ประเภทพอยน์เตอร์ถึงอาเรย์ ไม่มีใครสอนเรื่องนั้น แต่ก็ไม่มีใครสอนเรื่องนั้น และไม่ได้กล่าวถึงวิธีการใช้ด้วย คุณไม่เห็นด้วย? เช่นแสดงให้ฉันเห็นว่าที่ไหนใน Unix/Linux API มีการใช้ตัวชี้ไปยังอาร์เรย์ในลายเซ็นฟังก์ชันบนตัวชี้เปล่าที่เอกสารประกอบถือว่าเป็นอาร์เรย์ @เบ็นวอยท์ - person davidbak; 19.05.2021
comment
@BenVoigt - ทั้ง ภาษาการเขียนโปรแกรม C (Kernighan, Ritchie, 1978) หรือ C: A Reference Manual - ฉบับที่ 3 (Harbison, Steele, 1991) (รวมถึงทั้ง ANSI และ C แบบดั้งเดิม) หารือเกี่ยวกับตัวชี้ต่ออาเรย์ - พวกเขาอภิปรายเพียงว่าอาร์เรย์ของ T และตัวชี้ไปยัง T นั้นใกล้เคียงกันเพียงใด เมื่อตัวระบุอาร์เรย์ปรากฏในนิพจน์เป็นครั้งแรก ประเภทของตัวระบุจะถูกแปลงจากอาร์เรย์ T เป็นตัวชี้เป็น T และค่าของตัวระบุจะถูกแปลงเป็นตัวชี้เป็นองค์ประกอบแรกของอาร์เรย์ (ฮาร์บิสัน หน้า 111) และการอ้างอิงอื่น ๆ ที่คล้ายกันอีกมากมาย - person davidbak; 19.05.2021
comment
การออกแบบและวิวัฒนาการของ C++ (Stroustrup, 1994) ไม่เพียงแต่ไม่ได้กล่าวถึงตัวชี้ถึงอาร์เรย์เท่านั้น แต่ยังสะท้อนถ้อยคำของ Harbison/Steele - person davidbak; 19.05.2021
comment
ทั้ง Effective C++ - 3rd ed (Meyers, 2008) หรือ More Effective C++ (Meyers, 1996) กล่าวถึงประเภทตัวชี้ถึงอาร์เรย์ ฉันสามารถอ่านหนังสือจากห้องสมุดของฉันต่อได้ แต่ ... ฉันไม่สนใจเลย ประเด็นไม่ได้อยู่ที่ว่าในทางเทคนิคแล้วภาษามีความสามารถนี้หรือไม่ ประเด็นก็คือไม่มีใครเคยใช้มัน เคย. การที่ฉันไม่ได้พูดถึงมันในคำตอบไม่ได้หมายความว่าฉันไม่รู้ เพียงแต่ฉันรู้ว่ามันเป็นร่องรอยที่ไร้ประโยชน์ของคลังความรู้ของนักเขียนคอมไพเลอร์ ไม่เคยใช้งานไม่เคยสอน - person davidbak; 19.05.2021
comment
ปัญหาหลักที่นี่คือประเภทตัวชี้ต่ออาเรย์และการอ้างอิงถึงอาเรย์นั้นอ่านยาก จริงๆ ดังนั้นผู้คนจึงมีนิสัยชอบไม่ใช้งานสิ่งเหล่านี้ ซึ่งนำไปสู่ความรู้ที่หลงทาง . วิธีที่ง่ายที่สุดในการทำงานกับเทมเพลตคือการใช้เทมเพลตหรือ decltype และโดยปกติแล้วการใช้งานจะค่อยๆ กลายเป็นความยุ่งเหยิงที่แทบจะอ่านไม่ออก create() ที่นี่แย่พอแล้ว (ในหลายๆ วิธี) ลองนึกภาพฟังก์ชันที่นำพอยน์เตอร์ไปยังอาร์เรย์สองตัวและส่งกลับพอยน์เตอร์ไปยังอาร์เรย์ประเภทอื่น - person Justin Time - Reinstate Monica; 19.05.2021
comment
เนื่องจากการใช้งานทั่วไปของ new[] คือการจัดสรรอาร์เรย์ของขนาดที่ไม่รู้จักในขณะคอมไพล์ ดังนั้นตัวชี้ต่ออาร์เรย์ของ C จึงไม่ช่วยอะไรได้มากนัก - person Jack Aidley; 19.05.2021
comment
@JackAidley: มันใช้สำหรับอาร์เรย์ 2D เนื่องจาก C99 รองรับ VLA ด้วย วิธีอื่นในการ malloc อาร์เรย์ 2 มิติ แสดงให้เห็นว่า int (*p)[2] = malloc(3 * sizeof *p); เป็นวิธี C ในการจัดสรรวัตถุ int [3][2] array-of-arrays แบบไดนามิก โดยมีท้องถิ่นชี้ไป 2 และ 3 อาจเป็นตัวแปรรันไทม์ในภาษา C ซึ่งแตกต่างจาก C++ - person Peter Cordes; 19.05.2021
comment
@davidbak: แน่นอนว่ามันเป็นทางเลือกระหว่างต้องใช้ p[INDEX2D(i,j)] เพื่อจำลองการจัดทำดัชนี 2D ในอาเรย์แบบแบน หรือใช้ p[i][j] และให้คอมไพเลอร์สเกล i ตามมิติอาเรย์อื่น ๆ สำหรับคุณ โดยขึ้นอยู่กับประเภทตัวชี้ถึง VLA อย่างใดอย่างหนึ่งก็ได้ ตราบใดที่คุณหลีกเลี่ยง int **p ด้วยการจัดสรรแยกกันสำหรับแต่ละแถว (nvm ฉันเห็นว่าคุณลบความคิดเห็นที่ฉันตอบกลับไป ฉันสามารถลบสิ่งนี้ได้เช่นกัน) - person Peter Cordes; 19.05.2021
comment
@PeterCordes - w.r.t. คำถามเฉพาะนี้ VLA เป็น C เท่านั้นและเป็นมาตรฐานใน C99 เท่านั้น - การสร้าง C ++ ที่ผ่านมานาน Wikipedia บน VLAs ขาดไปอย่างน่าเศร้าเมื่อ GCC สนับสนุนพวกเขาครั้งแรก - แต่เป็นที่น่าสงสัยว่า Stroustrup จะใช้พวกมันเป็นแบบจำลองเลยโดยไม่ได้มาตรฐานตั้งแต่การใช้งาน C ++ ครั้งแรกของเขา cfront เป็นนักแปลถึง C และเขาอาจจะ มีความคิดที่จะไม่ผูกเข้ากับคอมไพเลอร์ C เฉพาะเจาะจง - person davidbak; 19.05.2021
comment
@PeterCordes - ฉันลบความคิดเห็นของฉันเพราะ ... ประเด็นของคุณคือการตอบกลับที่ถูกต้องต่อความคิดเห็นที่มีอยู่ ... แม้ว่าฉันเองก็มีข้อสงสัยเกี่ยวกับความสามารถในการอ่านของโครงสร้าง - person davidbak; 19.05.2021
comment
ใช่แล้ว แจ็คกำลังตอบกลับ @JustinTime ซึ่งชี้ให้เห็นว่าไวยากรณ์ของตัวชี้ถึงอาเรย์นั้นอ่านยาก ไม่มีการโต้แย้งจากฉัน และมักจะไม่คุ้มที่จะกังวลด้วย การขาดการสนับสนุน ISO C++ สำหรับ VLA อย่างต่อเนื่องหมายความว่าการใช้พอยน์เตอร์ไปยังอาร์เรย์ใน C++ แบบพกพามักจะไม่มีประโยชน์ std::array นั้นทรงพลังและสะอาดกว่าไม่แพ้กัน (GNU C++ รองรับ VLA ใน C++) แต่ C++ แน่นอนที่สุดสามารถมีตัวชี้ไปยังอาร์เรย์ได้ ดังนั้นฉันจึงเห็นด้วยกับความคิดเห็นเริ่มต้นของ Ben อาร์เรย์ชนิดเดียวที่รองรับเลยคือขนาดคงที่ของเวลาคอมไพล์ แต่สามารถชี้ไปที่ได้ - person Peter Cordes; 19.05.2021
comment
@petercordes C99 มาช้าไปหน่อยสำหรับ C ++ - person Jack Aidley; 19.05.2021
comment
@JackAidley: ใช่ การออกแบบเริ่มต้นของ C++ ถูกจำกัดโดยต้องการคอมไพล์เป็น C แบบพกพาในขณะนั้น โดยไม่จำเป็นต้องพึ่งพาคุณสมบัติ VLA ที่คอมไพเลอร์ C บางส่วน แต่ไม่ใช่ทั้งหมดในเวลานั้นรองรับ เพียงอย่างเดียวไม่ได้อธิบายว่าทำไม C ++ สมัยใหม่ถึงยังขาดอยู่ (สาเหตุอาจเกี่ยวข้องกับภาวะแทรกซ้อนของผู้ทำลาย) ฉันคิดว่าเรากำลังพูดถึงว่าทำไมไวยากรณ์ของตัวชี้ถึงอาเรย์จึงไม่ใช้กันอย่างแพร่หลาย (ใน C หรือ C++) และฉันชี้ให้เห็นว่ามีกรณีการใช้งาน C สำหรับมัน ฉันไม่ได้พยายามโต้แย้งอะไรโดยตรงเกี่ยวกับการออกแบบของ C++ (ยกเว้นว่า ไม่ รองรับตัวชี้ต่ออาร์เรย์) - person Peter Cordes; 19.05.2021
comment
@PeterCordes - ฉันเดาว่าเหตุผลที่ C ++ สมัยใหม่ยังขาดอยู่เพราะมันไม่คุ้มกับความพยายามที่จะทำการผ่าตัดใด ๆ เกี่ยวกับภาษา - หลักหรือรอง - ณ วันที่ล่าช้านี้เพื่อทำให้อาร์เรย์สไตล์ C ทำงานได้ดีขึ้น คุ้มยิ่งกว่าคุ้มที่จะให้อันดับ operator[] ตามอำเภอใจแก่เรา จากนั้นใครๆ ก็สามารถใช้คลาสและเทมเพลตที่ได้รับการพิสูจน์แล้วในตัวของ C++ เพื่อสร้างอาร์เรย์ประเภทใดก็ได้ที่พวกเขาต้องการ จากนั้น ตัวดำเนินการ new และ delete ทั้งเวอร์ชันสเกลาร์และอาร์เรย์ จะถูกมองว่าถูกห่อหุ้มและซ่อนไว้ในหนึ่งหรือสองวิธีของคลาสระดับต่ำที่ต้องใช้ฮีปเท่านั้น - person davidbak; 19.05.2021
comment
ไวยากรณ์ VLA นั้นสะอาดกว่า alloca มาก (ซึ่งเป็น IIRC ที่ไม่ได้มาตรฐานด้วย) เมื่อคุณต้องการอาร์เรย์ชั่วคราวในเครื่องขนาดเล็กในพื้นที่จัดเก็บอัตโนมัติ โดยมีขนาดตัวแปรรันไทม์ที่คุณรู้ว่าเล็กพอ (เช่น บนสแต็กในการใช้งาน C++ ปกติ) สำหรับการจัดสรรแบบไดนามิก ตรวจสอบให้แน่ใจว่าคุณสามารถล้อม new[] ไว้ในคลาสที่มีโอเปอเรเตอร์โอเวอร์โหลด[]ได้ รู้สึกเหมือนเสียเวลามากในการเขียน wrapper แบบอาเรย์สำหรับ alloca เมื่อ GNU C++ รองรับ alignas(32) float foo[n]; สำหรับพื้นที่เริ่มต้นเล็กน้อย - person Peter Cordes; 19.05.2021
comment
@PeterCordes - กรณีการใช้งานที่น่าสนใจ ฉันเคยใช้ alloca() ในอดีตอย่างแน่นอน - แต่ฉันสงสัยว่ามันจะเป็นสิ่งที่ดี - หรือแม้แต่สิ่งที่ ยอมรับได้ - ก้าวไปข้างหน้าตอนนี้ที่เรามี coroutines .. (รวมถึง VLA ในนั้นด้วย) - person davidbak; 19.05.2021
comment
จาก en.cppreference.com/w/cpp/ language/coroutines ดูเหมือนว่าการออกแบบจะเข้ากันได้กับการเรียกจากฟังก์ชั่นที่ใช้ VLA / alloca พื้นที่เก็บข้อมูลสำหรับสถานะ Coroutine ได้รับการจัดสรรฮีปด้วย new หรือสามารถปรับให้เหมาะสมได้ (โดยใช้พื้นที่สแต็ก) หากอายุการใช้งานอนุญาต และหากทราบขนาดของเฟรม Coroutine ที่ไซต์การโทร คอมไพเลอร์อาจห้ามการจัดสรร / VLA ภายในฟังก์ชัน coroutine (อาจต้องแก้ไขขนาดสแต็กเฟรมตลอดระยะเวลาของ coroutine) แต่มาตรฐานให้วิธีที่ชัดเจนว่าเมื่อใดฟังก์ชันคือ coroutine - person Peter Cordes; 19.05.2021
comment
มีสาเหตุที่ C++ ไม่นำมาใช้ แต่ไม่เกี่ยวข้องกับการเลือกระหว่างการลบ[]กับการลบ นั่นทำไว้นานแล้ว สำหรับสิ่งที่คุ้มค่า Bjarne ยอมรับว่ามันเป็นความผิดพลาด IIRC - person Jack Aidley; 19.05.2021
comment
@davidbak เห็นได้ชัดว่าคุณไม่รู้เพราะคุณเข้าใจผิดว่าตัวชี้ต่ออาร์เรย์สลายไปเป็นตัวชี้ไปยังองค์ประกอบแรก มันไม่ใช่. อาร์เรย์สลายตัวไปที่ตัวชี้ไปยังองค์ประกอบเริ่มต้น ตัวชี้ต่ออาร์เรย์สามารถแปลงกลับได้ด้วยตัวชี้ไปยังองค์ประกอบแรก แต่ไม่สลายตัว และเป็นเรื่องแย่มากสำหรับตำราเรียนใด ๆ ที่ไม่ครอบคลุมพอยน์เตอร์ถึงอาเรย์ เพราะตรงกันข้ามกับของคุณ มันไม่เคยถูกใช้งาน จริงๆ แล้วมันเป็นเรื่องธรรมดา อาเรย์สองมิติสลายตัวไปเป็นพอยน์เตอร์ถึงอาเรย์ การทำดัชนีในอาร์เรย์สองมิติจะทำการคำนวณทางคณิตศาสตร์ของตัวชี้บนตัวชี้ต่ออาร์เรย์ - person Ben Voigt; 20.05.2021

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

ตัวจัดการหน่วยความจำ C ช่วยให้คุณสามารถเพิ่มบล็อกหน่วยความจำโดยไม่ต้องทราบขนาดของบล็อก แต่ไม่มีวิธีมาตรฐานในการรับขนาดของบล็อกจากรันไทม์ และไม่มีการรับประกันว่าบล็อกที่ได้รับการจัดสรรจริงจะมีขนาดเท่ากับคุณทุกประการ ร้องขอ มันอาจจะใหญ่กว่านี้ก็ได้

ดังนั้นขนาดบล็อกที่จัดเก็บไว้ในตัวจัดการหน่วยความจำ C จึงไม่สามารถนำมาใช้ประโยชน์เพื่อเปิดใช้งานฟังก์ชันระดับที่สูงกว่าได้ หากฟังก์ชันการทำงานระดับสูงกว่าต้องการข้อมูลเกี่ยวกับขนาดของการจัดสรร ฟังก์ชันนั้นจะต้องจัดเก็บไว้เอง (และ C++ delete[] ต้องการสิ่งนี้สำหรับประเภทที่มี destructors เพื่อรันสำหรับทุกองค์ประกอบ)

C++ ยังมีทัศนคติของคุณที่จะจ่ายเฉพาะสิ่งที่คุณใช้ การจัดเก็บฟิลด์ที่มีความยาวพิเศษสำหรับการจัดสรรทุกครั้ง (แยกจากการทำบัญชีของผู้จัดสรรต้นแบบ) จะไม่เหมาะกับทัศนคตินี้

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

person plugwash    schedule 19.05.2021

เรื่องปกคือ delete จำเป็นต้อง เนื่องจากความสัมพันธ์ของ C++ กับ C

ตัวดำเนินการ new สามารถสร้างออบเจ็กต์ที่จัดสรรแบบไดนามิกของออบเจ็กต์เกือบทุกชนิด

แต่เนื่องจากมรดกทางภาษา C ตัวชี้ไปยังประเภทอ็อบเจ็กต์จึงไม่ชัดเจนระหว่างสองนามธรรม:

  • เป็นที่ตั้งของวัตถุชิ้นเดียวและ
  • เป็นฐานของอาร์เรย์แบบไดนามิก

สถานการณ์ delete กับ delete[] ตามมาหลังจากนั้น

อย่างไรก็ตาม นั่นไม่เป็นความจริง เนื่องจากแม้ว่าข้อสังเกตข้างต้นจะเป็นจริง แต่ก็สามารถใช้โอเปอเรเตอร์ delete ตัวเดียวได้ มันไม่ได้เป็นไปตามตรรกะว่าจำเป็นต้องมีตัวดำเนินการสองตัว

นี่คือหลักฐานที่ไม่เป็นทางการ การร้องขอตัวดำเนินการ new T (กรณีวัตถุเดี่ยว) อาจทำงานโดยปริยายราวกับว่าเป็น new T[1] กล่าวคือ ทุก ๆ new สามารถจัดสรรอาร์เรย์ได้เสมอ เมื่อไม่มีการกล่าวถึงไวยากรณ์ของอาร์เรย์ อาจบอกเป็นนัยได้ว่าอาร์เรย์ [1] จะได้รับการจัดสรร จากนั้น ก็จะต้องมี delete ตัวเดียวซึ่งมีพฤติกรรมเหมือน delete[] ของวันนี้

เหตุใดจึงไม่ปฏิบัติตามการออกแบบนั้น

ฉันคิดว่ามันเป็นเรื่องปกติ: มันเป็นแพะที่ถูกบูชายัญต่อเทพเจ้าแห่งประสิทธิภาพ เมื่อคุณจัดสรรอาร์เรย์ด้วย new [] พื้นที่จัดเก็บเพิ่มเติมจะถูกจัดสรรสำหรับข้อมูลเมตาเพื่อติดตามจำนวนองค์ประกอบ เพื่อให้ delete [] สามารถทราบจำนวนองค์ประกอบที่ต้องทำซ้ำเพื่อทำลาย เมื่อคุณจัดสรรออบเจ็กต์เดี่ยวด้วย new ไม่จำเป็นต้องมีข้อมูลเมตาดังกล่าว สามารถสร้างออบเจ็กต์ได้โดยตรงในหน่วยความจำซึ่งมาจากตัวจัดสรรพื้นฐานโดยไม่ต้องมีส่วนหัวเพิ่มเติม

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

person Kaz    schedule 20.05.2021

ตัวอย่างอาจช่วยได้ เมื่อคุณจัดสรรอาร์เรย์ของวัตถุสไตล์ C วัตถุเหล่านั้นอาจมีตัวทำลายของตัวเองที่จำเป็นต้องถูกเรียก ตัวดำเนินการ delete ไม่ได้ทำสิ่งนั้น มันใช้งานได้กับวัตถุคอนเทนเนอร์ แต่ไม่ใช่อาร์เรย์สไตล์ C คุณต้องการ delete[] สำหรับพวกเขา

นี่คือตัวอย่าง:

#include <iostream>
#include <stdlib.h>
#include <string>

using std::cerr;
using std::cout;
using std::endl;

class silly_string : private std::string {
  public:
    silly_string(const char* const s) :
      std::string(s) {}
    ~silly_string() {
      cout.flush();
      cerr << "Deleting \"" << *this << "\"."
           << endl;
      // The destructor of the base class is now implicitly invoked.
    }

  friend std::ostream& operator<< ( std::ostream&, const silly_string& );
};

std::ostream& operator<< ( std::ostream& out, const silly_string& s )
{
  return out << static_cast<const std::string>(s);
}

int main()
{
  constexpr size_t nwords = 2;
  silly_string *const words = new silly_string[nwords]{
    "hello,",
    "world!" };

  cout << words[0] << ' '
       << words[1] << '\n';

  delete[] words;

  return EXIT_SUCCESS;
}

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

คอมไพเลอร์บางตัว เช่น clang++ นั้นฉลาดพอที่จะเตือนคุณหากคุณละเว้น [] ใน delete[] words; แต่ถ้าคุณบังคับให้คอมไพล์โค้ด buggy คุณจะได้รับความเสียหายแบบฮีป

person Davislor    schedule 19.05.2021

Delete เป็นตัวดำเนินการที่ทำลายวัตถุอาร์เรย์และไม่ใช่อาร์เรย์ (ตัวชี้) ซึ่งสร้างขึ้นโดยนิพจน์ใหม่

สามารถใช้งานได้โดยใช้ตัวดำเนินการ Delete หรือตัวดำเนินการ Delete [ ] ตัวดำเนินการใหม่จะใช้สำหรับการจัดสรรหน่วยความจำแบบไดนามิกซึ่งวางตัวแปรไว้ในหน่วยความจำฮีป ซึ่งหมายความว่าตัวดำเนินการลบจะจัดสรรหน่วยความจำจากฮีป ตัวชี้ไปยังวัตถุไม่ถูกทำลาย ค่าหรือบล็อกหน่วยความจำที่ตัวชี้ชี้จะถูกทำลาย ตัวดำเนินการลบมีประเภทการส่งคืนเป็นโมฆะที่ไม่ส่งคืนค่า

person Aditya-ai    schedule 14.06.2021