std :: vector (ab) ใช้ที่เก็บข้อมูลอัตโนมัติ


46

พิจารณาตัวอย่างต่อไปนี้:

#include <array>
int main() {
  using huge_type = std::array<char, 20*1024*1024>;
  huge_type t;
}

เห็นได้ชัดว่ามันจะผิดพลาดในแพลตฟอร์มส่วนใหญ่เนื่องจากขนาดสแต็คเริ่มต้นมักจะน้อยกว่า 20MB

พิจารณารหัสต่อไปนี้:

#include <array>
#include <vector>

int main() {
  using huge_type = std::array<char, 20*1024*1024>;
  std::vector<huge_type> v(1);
}

น่าแปลกที่มันเกิดปัญหาเช่นกัน! การย้อนกลับ (ด้วยหนึ่งในเวอร์ชันล่าสุดของ libstdc ++) นำไปสู่include/bits/stl_uninitialized.hไฟล์ซึ่งเราสามารถเห็นบรรทัดต่อไปนี้:

typedef typename iterator_traits<_ForwardIterator>::value_type _ValueType;
std::fill(__first, __last, _ValueType());

ตัวvectorสร้างปรับขนาดต้องเริ่มต้นองค์ประกอบและนี่คือวิธีการใช้งาน เห็นได้ชัดว่า_ValueType()ชั่วคราวเกิดปัญหาสแต็ก

คำถามคือการใช้งานที่สอดคล้องหรือไม่ ถ้าใช่จริง ๆ แล้วหมายความว่าการใช้เวกเตอร์ขนาดใหญ่นั้นค่อนข้าง จำกัด ใช่ไหม?


หนึ่งไม่ควรเก็บวัตถุขนาดใหญ่ในประเภทอาร์เรย์ การทำเช่นนั้นอาจต้องใช้พื้นที่ขนาดใหญ่ของความทรงจำที่ขัดแย้งซึ่งอาจไม่มี ให้ใช้เวกเตอร์พอยน์เตอร์ (std :: unique_ptr โดยทั่วไป) แทนเพื่อให้คุณไม่ต้องใช้หน่วยความจำที่มีความต้องการสูง
NathanOliver

2
แค่ความทรงจำ มีการใช้งาน C ++ ที่ไม่ได้ใช้หน่วยความจำเสมือน
NathanOliver

3
คอมไพเลอร์ btw ไหน? ฉันไม่สามารถทำซ้ำกับ VS 2019 (16.4.2)
ChrisMM

3
จากการดูที่ libstdc รหัส ++ การดำเนินการนี้จะใช้เฉพาะในกรณีที่ประเภทองค์ประกอบเป็นเล็กน้อยและคัดลอกมอบหมายและถ้าเริ่มต้นstd::allocatorถูกนำมาใช้
วอลนัท

1
@Damon ตามที่ฉันได้กล่าวไปแล้วดูเหมือนว่าจะใช้สำหรับประเภทที่ไม่สำคัญกับตัวจัดสรรเริ่มต้นเท่านั้นดังนั้นจึงไม่ควรมีความแตกต่างที่สังเกตได้
วอลนัท

คำตอบ:


19

ไม่มีการ จำกัด จำนวนหน่วยเก็บข้อมูลอัตโนมัติที่ std API ใช้

พวกเขาทุกคนต้องการพื้นที่สแต็ก 12 เทราไบต์

อย่างไรก็ตาม API นั้นต้องการเพียงอย่างเดียวCpp17DefaultInsertableและการใช้งานของคุณจะสร้างอินสแตนซ์เพิ่มเติมเหนือสิ่งที่ตัวสร้างต้องการ เว้นแต่ว่ามันจะถูกล้อมรอบด้านหลังการตรวจจับวัตถุที่เป็น ctorable เล็กน้อยและคัดลอกการใช้งานที่ดูผิดกฎหมาย


8
จากการดูที่ libstdc รหัส ++ การดำเนินการนี้จะใช้เฉพาะในกรณีที่ประเภทองค์ประกอบเป็นเล็กน้อยและคัดลอกมอบหมายและถ้าเริ่มต้นstd::allocatorถูกนำมาใช้ ฉันไม่แน่ใจว่าทำไมกรณีพิเศษนี้เกิดขึ้นตั้งแต่แรก
วอลนัท

3
@walnut ซึ่งหมายความว่าคอมไพเลอร์มีอิสระที่จะถ้าไม่จริงสร้างวัตถุชั่วคราวนั้น ฉันเดาว่ามีโอกาสที่ดีในการสร้างที่ปรับให้เหมาะสมซึ่งไม่ได้สร้าง
Yakk - Adam Nevraumont

4
ใช่ฉันเดาว่าเป็นไปได้ แต่สำหรับองค์ประกอบขนาดใหญ่ GCC ดูเหมือนจะไม่เป็นเช่นนั้น เสียงดังกราวกับ libstdc ++ ไม่เพิ่มประสิทธิภาพออกชั่วคราว แต่ดูเหมือนว่าเพียง แต่ถ้าขนาดของเวกเตอร์ที่ผ่านมาสร้างเป็นค่าคงที่รวบรวมเวลาดูgodbolt.org/z/-2ZDMm
วอลนัท

1
@ วอลนัตกรณีพิเศษคือมีเพื่อให้เราส่งไปstd::fillสำหรับประเภทเล็ก ๆ น้อย ๆ ซึ่งจะใช้memcpyในการระเบิดไบต์เป็นสถานที่ซึ่งอาจเร็วกว่าการสร้างวัตถุจำนวนมากในวง ฉันเชื่อว่าการใช้ libstdc ++ เป็นไปตามมาตรฐาน แต่การทำให้สแต็กล้นสำหรับออบเจ็กต์ขนาดใหญ่นั้นเป็นข้อบกพร่องของ Quality of Implementation (QoI) ฉันรายงานว่าเป็นgcc.gnu.org/PR94540และจะแก้ไข
Jonathan Wakely

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

9
huge_type t;

เห็นได้ชัดว่ามันจะผิดพลาดในแพลตฟอร์มส่วนใหญ่ ...

ฉันโต้แย้งข้อสันนิษฐานของ "มากที่สุด" เนื่องจากไม่เคยใช้หน่วยความจำของวัตถุขนาดใหญ่คอมไพเลอร์สามารถเพิกเฉยได้อย่างสมบูรณ์และไม่จัดสรรหน่วยความจำในกรณีที่จะไม่มีข้อผิดพลาด

คำถามคือการใช้งานที่สอดคล้องหรือไม่

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

จริงๆแล้วมันหมายความว่าการใช้เวกเตอร์ขนาดใหญ่นั้นค่อนข้าง จำกัด ใช่ไหม?

ที่ดูเหมือนจะเป็นกรณีที่มี libstdc ++ ความผิดพลาดไม่ได้ทำซ้ำโดยใช้ libc ++ (โดยใช้เสียงดังกราว) ดังนั้นจึงดูเหมือนว่านี่ไม่ใช่ข้อ จำกัด ในภาษา แต่จะใช้เฉพาะในการนำไปใช้นั้นเท่านั้น


6
"ไม่จำเป็นต้องล้มเหลวแม้จะมีการล้นของสแต็คเพราะโปรแกรมไม่สามารถเข้าถึงหน่วยความจำที่จัดสรร" - หากใช้สแต็คในทางใดทางหนึ่งหลังจากนี้ (เช่นการเรียกใช้ฟังก์ชัน) สิ่งนี้จะผิดพลาดแม้แต่บนแพลตฟอร์มที่ทำงานเกิน .
Ruslan

แพลตฟอร์มใด ๆ ที่สิ่งนี้ไม่ผิดพลาด (สมมติว่าวัตถุนั้นไม่ได้รับการจัดสรรอย่างประสบความสำเร็จ) จะมีความเสี่ยงต่อ Stack Clash
user253751

@ user253751 มันจะเป็นการดีที่จะสมมติว่าแพลตฟอร์ม / โปรแกรมส่วนใหญ่ไม่เสี่ยง
eerorika

ฉันคิดว่า overcommit ใช้กับ heap เท่านั้นไม่ใช่ stack สแต็กมีขอบเขตบนคงที่กับขนาดของมัน
โจนาธาน Wakely

@JonathanWakely คุณพูดถูก ปรากฏว่าสาเหตุที่ทำให้ระบบไม่ทำงานล้มเหลวเนื่องจากคอมไพเลอร์ไม่เคยจัดสรรวัตถุที่ไม่ได้ใช้
eerorika

5

ฉันไม่ใช่ทนายความด้านภาษาหรือผู้เชี่ยวชาญมาตรฐาน C ++ แต่ cppreference.com พูดว่า:

explicit vector( size_type count, const Allocator& alloc = Allocator() );

สร้างคอนเทนเนอร์ที่มีอินสแตนซ์ที่นับจำนวนเริ่มต้นของ T ไม่มีการทำสำเนา

บางทีฉันอาจเข้าใจผิด "แทรกค่าเริ่มต้น" แต่ฉันคาดหวัง:

std::vector<huge_type> v(1);

จะเท่ากับ

std::vector<huge_type> v;
v.emplace_back();

รุ่นหลังไม่ควรสร้างสำเนาสแต็ก แต่สร้าง huge_type โดยตรงในหน่วยความจำไดนามิกของเวกเตอร์

ฉันไม่สามารถพูดอย่างเผด็จการได้ว่าสิ่งที่คุณเห็นนั้นไม่สอดคล้อง แต่ก็ไม่ใช่สิ่งที่ฉันคาดหวังจากการใช้งานที่มีคุณภาพ


4
ดังที่ฉันได้กล่าวถึงในความคิดเห็นเกี่ยวกับคำถาม libstdc ++ จะใช้การดำเนินการนี้สำหรับประเภทที่มีการคัดลอกและstd::allocatorดังนั้นจึงไม่ควรมีความแตกต่างที่สังเกตได้ระหว่างการแทรกลงในหน่วยความจำเวกเตอร์โดยตรงและสร้างสำเนากลาง
วอลนัท

@ วอลนัต: ถูกต้อง แต่การจัดสรรสแต็คขนาดใหญ่และผลกระทบต่อประสิทธิภาพของ init และ copy ยังคงเป็นสิ่งที่ฉันไม่คาดหวังจากการใช้งานที่มีคุณภาพสูง
Adrian McCarthy

2
ใช่ฉันเห็นด้วย. ฉันคิดว่านี่เป็นการกำกับดูแลในการดำเนินการ ประเด็นของฉันก็คือว่ามันไม่สำคัญในแง่ของการปฏิบัติตามมาตรฐาน
วอลนัท

IIRC คุณต้องมีความสามารถในการทำสำเนาหรือเคลื่อนย้ายemplace_backได้ด้วยไม่ใช่เพื่อสร้างเวกเตอร์ ซึ่งหมายความว่าคุณสามารถมีได้vector<mutex> v(1)แต่ไม่ใช่vector<mutex> v; v.emplace_back();สำหรับบางอย่างเช่นhuge_typeคุณอาจยังมีการจัดสรรและย้ายการทำงานมากขึ้นด้วยรุ่นที่สอง ไม่ควรสร้างวัตถุชั่วคราว
dyp

1
@IgorR vector::vector(size_type, Allocator const&)ต้องการ (Cpp17) DefaultInsertable
dyp
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.