เหตุใดอะเรย์ความยาวผันแปรจึงไม่ได้เป็นส่วนหนึ่งของมาตรฐาน C ++


326

ฉันไม่ได้ใช้ C มากในช่วงไม่กี่ปีที่ผ่านมา เมื่อฉันอ่านคำถามนี้วันนี้ฉันเจอไวยากรณ์ C ที่ฉันไม่คุ้นเคย

เห็นได้ชัดว่าในC99ไวยากรณ์ต่อไปนี้ถูกต้อง:

void foo(int n) {
    int values[n]; //Declare a variable length array
}

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

เหตุผลที่เป็นไปได้บางประการ:

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

สถานะมาตรฐาน C ++ ที่ขนาดอาร์เรย์ต้องเป็นนิพจน์คงที่ (8.3.4.1)

ใช่แน่นอนฉันรู้ว่าในตัวอย่างของเล่นสามารถใช้งานstd::vector<int> values(m);ได้ แต่สิ่งนี้จัดสรรหน่วยความจำจากฮีปไม่ใช่สแต็ก และถ้าฉันต้องการอาร์เรย์หลายมิติเช่น:

void foo(int x, int y, int z) {
    int values[x][y][z]; // Declare a variable length array
}

vectorรุ่นกลายเป็นเงอะงะสวย:

void foo(int x, int y, int z) {
    vector< vector< vector<int> > > values( /* Really painful expression here. */);
}

ชิ้นส่วนแถวและคอลัมน์ก็อาจกระจายไปทั่วหน่วยความจำ

มองไปที่การอภิปรายcomp.std.c++อย่างชัดเจนว่าคำถามนี้เป็นที่ถกเถียงกันค่อนข้างมากกับชื่อรุ่นหนามากทั้งสองด้านของการโต้แย้ง แน่นอนว่าไม่ชัดเจนว่า a std::vectorเป็นทางออกที่ดีกว่าเสมอ


3
เพิ่งออกมาจากความอยากรู้อยากรู้ว่าทำไมมันถึงต้องถูกจัดสรรบนสแต็ก? คุณเป็นคนที่กลัวปัญหาประสิทธิภาพการจัดสรรฮีปหรือไม่
Dimitri C.

32
@Dimitri ไม่จริง แต่ไม่มีการปฏิเสธว่าการจัดสรรสแต็กจะเร็วกว่าการจัดสรรฮีป และในบางกรณีสิ่งนี้อาจมีความสำคัญ
Andreas Brinck

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

4
อาร์เรย์ความยาวแปรผันอาจใช้แทนค่าคงที่ตัวประมวลผลล่วงหน้าด้วยตัวแปร const แบบคงที่ นอกจากนี้ใน C คุณไม่มีตัวเลือกอื่นสำหรับ VLA และบางครั้งจำเป็นต้องเขียนโค้ด C / C ++ แบบพกพา (เข้ากันได้กับคอมไพเลอร์ทั้งคู่)
Yury

2
ในฐานะที่เป็นกันมันจะปรากฏเสียงดังกราว ++ อนุญาต VLA
user3426763

คำตอบ:


204

มีเมื่อเร็ว ๆ นี้การอภิปรายเกี่ยวกับเรื่องนี้เตะปิดใน Usenet: ทำไมไม่มี Vlas ใน C

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

C99 VLA สามารถให้ประโยชน์เล็ก ๆ น้อย ๆ ในการสร้างอาร์เรย์ขนาดเล็กโดยไม่ต้องเปลืองพื้นที่หรือเรียกคอนสตรัคเตอร์สำหรับองค์ประกอบที่ไม่ได้ใช้ แต่จะแนะนำการเปลี่ยนแปลงที่ค่อนข้างใหญ่ในระบบประเภท (คุณต้องระบุประเภทขึ้นอยู่กับค่ารันไทม์ - ยังไม่มีอยู่ใน C ++ ปัจจุบันยกเว้นnewตัวระบุชนิดของตัวดำเนินการ แต่จะได้รับการปฏิบัติเป็นพิเศษเพื่อให้ runtime-ness ไม่หนีจากขอบเขตของตัวnewดำเนินการ)

คุณสามารถใช้std::vectorแต่มันไม่เหมือนกันเนื่องจากใช้หน่วยความจำแบบไดนามิกและทำให้การใช้ stack-allocator ของตัวเองนั้นไม่ใช่เรื่องง่าย (การจัดตำแหน่งก็เป็นปัญหาเช่นกัน) มันก็ไม่ได้แก้ปัญหาเดียวกันเพราะเวกเตอร์เป็นคอนเทนเนอร์ที่ปรับขนาดได้ในขณะที่ VLA นั้นมีขนาดคงที่ อาร์เรย์ C ++ แบบไดนามิกข้อเสนอมีวัตถุประสงค์เพื่อแนะนำวิธีการแก้ปัญหาห้องสมุดตามเป็นทางเลือกที่ภาษาตาม VLA อย่างไรก็ตามมันจะไม่เป็นส่วนหนึ่งของ C ++ 0x เท่าที่ฉันรู้


22
+1 และยอมรับ แต่ความคิดเห็นหนึ่งฉันคิดว่าเหตุผลด้านความปลอดภัยนั้นอ่อนแอเล็กน้อยเนื่องจากมีวิธีอื่น ๆ อีกมากมายที่จะทำให้เกิดการล้นสแต็ค อาร์กิวเมนต์ความปลอดภัยสามารถใช้เพื่อสนับสนุนตำแหน่งที่คุณไม่ควรใช้การเรียกซ้ำและคุณควรจัดสรรวัตถุทั้งหมดจากฮีป
Andreas Brinck

17
คุณกำลังบอกว่าเพราะมีวิธีอื่นที่จะทำให้เกิดการล้นสแต็คเราอาจสนับสนุนพวกเขาให้มากขึ้นเช่นกัน?
jalf

3
@ Andreas เห็นด้วยกับความอ่อนแอ แต่สำหรับการเรียกซ้ำมันต้องใช้การโทรจำนวนมากจนกว่าสแต็คจะถูกกินหมดและหากเป็นไปได้คนจะใช้การวนซ้ำ ในขณะที่บางคนในเธรด usenet พูดว่านี่ไม่ใช่การโต้แย้งกับ VLA ในทุกกรณีเนื่องจากบางครั้งคุณอาจรู้ขอบเขตแน่นอน แต่ในกรณีเหล่านั้นจากสิ่งที่ฉันเห็นอาร์เรย์คงที่สามารถอย่างเท่าเทียมกันพอเพราะมันจะไม่เสียพื้นที่มาก (ถ้ามันจะแล้วคุณจะต้องถามจริง ๆ ว่าพื้นที่สแต็คมีขนาดใหญ่พออีกครั้ง)
Johannes Schaub - litb

10
ดูที่คำตอบของ Matt Austern ในหัวข้อนั้น: ข้อกำหนดภาษาของ VLA อาจมีความซับซ้อนมากขึ้นสำหรับ C ++ เนื่องจากการจับคู่ประเภทที่เข้มงวดใน C ++ (ตัวอย่าง: C อนุญาตให้กำหนดT(*)[]a T(*)[N]- in C ++ ซึ่งไม่อนุญาตเนื่องจาก C ++ ไม่ทราบเกี่ยวกับ "ประเภทความเข้ากันได้" - มันต้องมีการจับคู่ที่ตรงกัน), พารามิเตอร์ประเภท, ข้อยกเว้น, คอนสตรัคเตอร์และ destructors และเนื้อหา ฉันไม่แน่ใจว่าประโยชน์ของ VLAs จะได้ผลจริงหรือไม่ แต่แล้วฉันไม่เคยใช้ VLAs ในชีวิตจริงดังนั้นฉันอาจจะไม่รู้จักเคสที่ดีสำหรับพวกเขา
Johannes Schaub - litb

1
@ Ahelps: อาจเป็นสิ่งที่ดีที่สุดสำหรับประเภทที่มีพฤติกรรมที่ค่อนข้างชอบvectorแต่ต้องใช้รูปแบบการใช้ LIFO คงที่และรักษาหนึ่งหรือมากกว่าหนึ่งต่อเธรดบัฟเฟอร์การจัดสรรแบบคงที่ซึ่งโดยทั่วไปจะมีขนาดตามการจัดสรรทั้งหมดที่ใหญ่ที่สุด เคยใช้ แต่ซึ่งสามารถตัดได้อย่างชัดเจน "การจัดสรร" ปกติในกรณีทั่วไปนั้นไม่ต้องการอะไรมากไปกว่าการคัดลอกตัวชี้การลบตัวชี้จากตัวชี้การเปรียบเทียบจำนวนเต็มและการเพิ่มตัวชี้ การยกเลิกการจัดสรรจะต้องมีการคัดลอกตัวชี้ ไม่ช้ากว่า VLA มาก
supercat

216

(ที่มา: ฉันมีประสบการณ์ในการใช้คอมไพเลอร์ C และ C ++)

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

  • sizeof xไม่คงที่ตลอดเวลาการรวบรวม; คอมไพเลอร์บางครั้งต้องสร้างรหัสเพื่อประเมินsizeof-expression ที่รันไทม์

  • ช่วยให้ Vlas สองมิติ ( int A[x][y]) ที่จำเป็นไวยากรณ์ใหม่สำหรับการประกาศฟังก์ชั่นที่ใช้ 2D Vlas void foo(int n, int A[][*])เป็นพารามิเตอร์:

  • มีความสำคัญน้อยกว่าในโลก C ++ แต่มีความสำคัญอย่างยิ่งสำหรับกลุ่มเป้าหมายของโปรแกรมเมอร์ระบบฝังตัวการประกาศ VLA หมายถึงการ chomping สแต็กขนาดใหญ่โดยพลการของคุณ นี่เป็นstack-overflow ที่รับประกันและมีข้อผิดพลาด (ทุกครั้งที่คุณประกาศint A[n]คุณจะยืนยันโดยปริยายว่าคุณมีสแต็กขนาด 2GB เพื่อสำรองไว้หากคุณรู้ว่า " nน้อยกว่า 1,000 ที่นี่" แน่นอนคุณต้องประกาศint A[1000]แทนค่า 32 บิตnสำหรับ1000การรับเข้า คุณไม่รู้ว่าพฤติกรรมของโปรแกรมของคุณควรเป็นอย่างไร)

โอเคเรามาพูดถึง C ++ กันดีกว่า ใน C ++ เรามีความแตกต่างที่ชัดเจนระหว่าง "ระบบพิมพ์" และ "ระบบคุ้มค่า" ที่ C89 ทำ ... แต่เราได้เริ่มต้นใช้มันในวิธีที่ C ไม่มี ตัวอย่างเช่น:

template<typename T> struct S { ... };
int A[n];
S<decltype(A)> s;  // equivalently, S<int[n]> s;

หากnไม่มีค่าคงที่เวลาคอมไพล์ (เช่นถ้าAเป็นชนิดดัดแปลงที่แปรปรวน) แล้วอะไรบนโลกจะเป็นประเภทของS? หากว่าSชนิด 's ยังได้รับการพิจารณาเท่านั้นที่รันไทม์?

เกี่ยวกับสิ่งนี้:

template<typename T> bool myfunc(T& t1, T& t2) { ... };
int A1[n1], A2[n2];
myfunc(A1, A2);

myfuncคอมไพเลอร์จะต้องสร้างรหัสสำหรับการเริ่มของบางส่วน รหัสนั้นควรมีลักษณะอย่างไร เราจะสร้างโค้ดนั้นได้อย่างไรถ้าเราไม่รู้ชนิดของA1เวลาคอมไพล์

ที่เลวร้ายยิ่งถ้ามันเกิดขึ้นตอนรันไทม์นั้นn1 != n2งั้น!std::is_same<decltype(A1), decltype(A2)>()เหรอ? ในกรณีนั้นการเรียกไปยังmyfunc ไม่ควรรวบรวมเพราะการลดประเภทเทมเพลตควรล้มเหลว! เราจะเลียนแบบพฤติกรรมนั้นในรันไทม์ได้อย่างไร?

โดยพื้นฐานแล้ว C ++ กำลังเคลื่อนไปในทิศทางของการตัดสินใจมากขึ้นเรื่อย ๆ ในการรวบรวมเวลา : การสร้างโค้ดเทมเพลตconstexprการประเมินฟังก์ชันและอื่น ๆ ในขณะเดียวกันก็กำลังยุ่ง C99 ผลักดันประเพณีรวบรวมเวลาการตัดสินใจ (เช่นsizeof) ลงในรันไทม์ กับในใจมันไม่ได้จริงๆแม้จะให้ความรู้สึกที่จะใช้จ่ายความพยายามใด ๆพยายามที่จะบูรณาการ Vlas C99 สไตล์เป็น C ++?

ตามที่ผู้ตอบคำถามทุกคนได้ชี้ให้เห็นแล้ว C ++ มีกลไกการจัดสรรฮีปมากมาย ( std::unique_ptr<int[]> A = new int[n];หรือstd::vector<int> A(n);เป็นสิ่งที่ชัดเจน) เมื่อคุณต้องการถ่ายทอดความคิด "ฉันไม่รู้ว่าต้องใช้แรมมากแค่ไหน" และ C ++ ให้รูปแบบการจัดการข้อยกเว้นที่ดีสำหรับการจัดการกับสถานการณ์ที่หลีกเลี่ยงไม่ได้ว่าจำนวนของ RAM ที่คุณต้องการมากกว่าจำนวน RAM ที่คุณมี แต่หวังว่าคำตอบนี้จะช่วยให้คุณมีความคิดที่ดีว่าทำไม VLAs สไตล์ C99 จึงไม่เหมาะสำหรับ C ++ - และไม่เหมาะกับ C99 ;)


ดูข้อมูลเพิ่มเติมเกี่ยวกับหัวข้อได้ที่N3810 "ทางเลือกสำหรับส่วนขยาย Array" , กระดาษตุลาคมของ Vjs ของ Bjarne Stroustrup ในเดือนตุลาคม 2013 POV ของ Bjarne นั้นแตกต่างจากของฉันมาก N3810 มุ่งเน้นไปที่การหาไวยากรณ์ C ++ ish ที่ดีสำหรับสิ่งต่าง ๆ และในการลดการใช้อาร์เรย์ดิบใน C ++ ในขณะที่ฉันมุ่งเน้นไปที่ความหมายของ metaprogramming และระบบพิมพ์ ฉันไม่รู้ว่าเขาเห็นว่า metaprogramming / typesystem implants แก้ไขแก้ไขได้หรือไม่สนใจเลย


โพสต์บล็อกที่ดีที่ได้รับความนิยมมากในจุดเดียวกันนี้คือ"การใช้อาร์เรย์ความยาวแปรผันที่ถูกกฎหมาย" (Chris Wellons, 2019-10-27)


15
ฉันเห็นด้วยกับ VLA ผิดแล้ว การนำมาใช้อย่างแพร่หลายและมีประโยชน์มากกว่านั้นalloca()ควรได้รับมาตรฐานใน C99 แทน VLA เป็นสิ่งที่เกิดขึ้นเมื่อคณะกรรมการมาตรฐานกระโดดออกไปก่อนการนำไปใช้แทนที่จะเป็นวิธีอื่น
MadScientist

10
ระบบประเภทที่ปรับเปลี่ยนได้นั้นเป็น IMO ที่ยอดเยี่ยมนอกจากนี้และไม่มีสัญลักษณ์แสดงหัวข้อย่อยของคุณที่ละเมิดสามัญสำนึก (1) มาตรฐาน C ไม่ได้แยกความแตกต่างระหว่าง "เวลาคอมไพล์" และ "เวลาทำงาน" ดังนั้นนี่จึงไม่ใช่ปัญหา (2) The *เป็นตัวเลือกคุณสามารถ (และควร) เขียนint A[][n]; (3) คุณสามารถใช้ระบบประเภทโดยไม่ต้องประกาศ VLA ใด ๆ ตัวอย่างเช่นฟังก์ชั่นสามารถยอมรับอาร์เรย์ของชนิดที่แก้ไขได้หลากหลายและสามารถเรียกใช้กับอาร์เรย์ที่ไม่ใช่ VLA 2-D ที่มีขนาดต่างกัน อย่างไรก็ตามคุณให้คะแนนที่ถูกต้องในส่วนหลังของโพสต์ของคุณ
MM

3
"การประกาศ VLA หมายถึงการสับสแต็คก้อนใหญ่ของคุณโดยพลการนี่เป็น stack-overflow และ crash ที่มีการรับประกัน (เมื่อใดก็ตามที่คุณประกาศ int A [n] คุณจะยืนยันว่าคุณมีสแต็ก 2GB เพื่อสำรอง" . เท็จฉันวิ่งโปรแกรม VLA กับกองน้อยกว่า 2GB โดยไม่ต้องกองล้นใด ๆ .
เจฟฟ์

3
@Jeff: มูลค่าสูงสุดของอะไรnในกรณีทดสอบของคุณและขนาดของสแต็กของคุณคือเท่าไหร่ ฉันขอแนะนำให้คุณลองป้อนค่าnอย่างน้อยใหญ่เท่ากับขนาดของสแต็กของคุณ (และหากไม่มีวิธีสำหรับผู้ใช้ในการควบคุมค่าของnในโปรแกรมของคุณฉันแนะนำให้คุณเผยแพร่ค่าสูงสุดของnการประกาศ: ประกาศint A[1000]หรืออะไรก็ตามที่คุณต้องการ VLA จำเป็นและอันตรายเท่านั้น เมื่อค่าสูงสุดของnไม่ถูก
ผูก

2
เนื่องจาก alloca () สามารถนำไปใช้งานได้โดยใช้ intrinsics ดังกล่าวโดยนิยามที่แท้จริงที่ alloca () สามารถนำไปใช้กับแพลตฟอร์มใด ๆ ก็ได้ในฐานะฟังก์ชันมาตรฐานคอมไพเลอร์ ไม่มีเหตุผลที่คอมไพเลอร์ไม่สามารถตรวจพบอินสแตนซ์แรกของ alloca () และจัดประเภทของเครื่องหมายและการเผยแพร่ที่จะฝังในโค้ดและไม่มีเหตุผลที่คอมไพเลอร์ไม่สามารถใช้ alloca () โดยใช้ฮีปหาก ไม่สามารถทำได้ด้วยสแต็ก สิ่งที่ยาก / ไม่พกพาคือมีการใช้ alloca () ด้านบนของคอมไพเลอร์ C เพื่อให้ทำงานได้กับคอมไพเลอร์และระบบปฏิบัติการที่หลากหลาย
MadScientist

26

คุณสามารถใช้ alloca () เพื่อจัดสรรหน่วยความจำบนสแต็กตอนรันไทม์หากคุณต้องการ:

void foo (int n)
{
    int *values = (int *)alloca(sizeof(int) * n);
}

การปันส่วนบนสแต็กหมายความว่ามันจะถูกทำให้เป็นอิสระโดยอัตโนมัติเมื่อสแต็กคลาย

บันทึกย่อ: ตามที่กล่าวไว้ในหน้า man Mac OS X สำหรับ alloca (3), "ฟังก์ชั่น alloca () นั้นขึ้นอยู่กับเครื่องจักรและคอมไพเลอร์ซึ่งขึ้นอยู่กับการใช้งาน เพียงเพื่อให้คุณรู้ว่า.


4
นอกจากนี้ขอบเขตสำหรับ alloca () เป็นฟังก์ชั่นทั้งหมดไม่เพียง แต่บล็อกของรหัสที่มีตัวแปร ดังนั้นการใช้ภายในลูปจะเพิ่มสแต็คอย่างต่อเนื่อง VLA ไม่มีปัญหานี้
sashoalm

3
อย่างไรก็ตาม VLA ที่มีขอบเขตของบล็อกที่ปิดล้อมหมายความว่าพวกเขามีประโยชน์น้อยกว่า alloca () ที่มีขอบเขตของฟังก์ชันทั้งหมด พิจารณา: if (!p) { p = alloca(strlen(foo)+1); strcpy(p, foo); } สิ่งนี้ไม่สามารถทำได้ด้วย VLA อย่างแม่นยำเนื่องจากขอบเขตบล็อกของพวกเขา
MadScientist

1
แต่นั่นไม่ได้คำตอบของ OP ทำไมคำถาม นอกจากนี้เป็นCวิธีการแก้ปัญหาเหมือนและไม่ได้จริงๆC++-ish
Adrian W

13

ในงานของฉันเองฉันรู้ว่าทุกครั้งที่ฉันต้องการอะไรบางอย่างเช่นอาร์เรย์อัตโนมัติความยาวผันแปรหรือ alloca () ฉันไม่สนใจจริง ๆ ว่าหน่วยความจำนั้นตั้งอยู่บนสแต็ก cpu เพียง แต่มันมาจาก ตัวจัดสรรสแต็กบางตัวที่ไม่ได้เกิดการเดินทางช้าไปยังฮีปทั่วไป ดังนั้นฉันจึงมีวัตถุต่อเธรดที่เป็นเจ้าของหน่วยความจำบางส่วนซึ่งมันสามารถผลัก / ตัวแปรบัฟเฟอร์ขนาดตัวแปร ในบางแพลตฟอร์มฉันอนุญาตให้สิ่งนี้เติบโตผ่าน mmu แพลตฟอร์มอื่น ๆ มีขนาดคงที่ (มักมาพร้อมกับสแต็ก cpu ขนาดคงที่เช่นกันเนื่องจากไม่มี mmu) แพลตฟอร์มเดียวที่ฉันทำงานด้วย (คอนโซลเกมมือถือ) มีสแต็ก cpu เล็ก ๆ น้อย ๆ ที่มีค่าอยู่แล้วเพราะมันอยู่ในหน่วยความจำที่หายากและรวดเร็ว

ฉันไม่ได้บอกว่าการผลักบัฟเฟอร์ขนาดแปรผันไปบนสแต็ก cpu นั้นไม่จำเป็นเลย จริง ๆ แล้วฉันรู้สึกประหลาดใจเมื่อฉันค้นพบว่านี่ไม่ได้มาตรฐานเพราะดูเหมือนว่าแนวคิดจะเข้ากับภาษาได้ดีพอ สำหรับฉันแล้วความต้องการ "ขนาดตัวแปร" และ "จะต้องอยู่ในสแต็ก cpu" ไม่เคยเกิดขึ้นพร้อมกัน มันเกี่ยวกับความเร็วดังนั้นฉันจึงสร้าง "สแต็กแบบขนานสำหรับบัฟเฟอร์ข้อมูล" ของตัวเอง


12

มีสถานการณ์ที่การจัดสรรหน่วยความจำฮีปมีราคาแพงมากเมื่อเปรียบเทียบกับการดำเนินการ ตัวอย่างคือคณิตศาสตร์เมทริกซ์ ถ้าคุณทำงานกับเมทริกซ์เล็ก ๆ พูดว่าองค์ประกอบ 5 ถึง 10 และทำเลขคณิตจำนวนมากค่าใช้จ่าย Malloc จะมีความสำคัญมาก ในขณะเดียวกันก็ทำให้ขนาดของเวลาในการรวบรวมนั้นดูสิ้นเปลืองและยืดหยุ่นได้มาก

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


12

ดูเหมือนว่าจะมีให้ใน C ++ 14:

https://en.wikipedia.org/wiki/C%2B%2B14#Runtime-sized_one_dimensional_arrays

อัปเดต: ไม่ได้ทำให้เป็น C ++ 14


น่าสนใจ Herb Sutter กล่าวถึงที่นี่ภายใต้Dynamic Arrays : isocpp.org/blog/2013/04/trip-report-iso-c-spring-2013-meeting (นี่คือข้อมูลอ้างอิงสำหรับข้อมูลวิกิพีเดีย)
ค่าเริ่มต้น

1
"อาร์เรย์และขนาดไดรฟ์แบบรันไทม์ได้ถูกย้ายไปยังข้อกำหนดทางเทคนิคของส่วนขยาย Array" เขียน 78.86.152.103 บน Wikipedia เมื่อวันที่ 18 มกราคม 2014: en.wikipedia.org/w/…
strager

10
Wikipedia ไม่ใช่ข้อมูลอ้างอิงเชิงบรรทัด :) ข้อเสนอนี้ไม่ได้ทำให้เป็น C ++ 14
MM

2
@ViktorSehr: สถานะของ wrt C ++ 17 นี้คืออะไร
einpoklum

@einpoklum ไม่มีความคิดใช้บูสต์ :: container :: static_vector
Viktor Sehr

7

สิ่งนี้ถือว่าเป็นการรวมไว้ใน C ++ / 1x แต่ถูกทิ้งไป (นี่เป็นการแก้ไขสิ่งที่ฉันพูดไปก่อนหน้านี้)

มันจะมีประโยชน์น้อยกว่าใน C ++ อย่างไรก็ตามเนื่องจากเราต้องstd::vectorกรอกบทบาทนี้


42
ไม่เราทำไม่ได้มาตรฐาน :: vector ไม่จัดสรรข้อมูลในสแต็ก :)
คอส

7
"สแต็ก" เป็นรายละเอียดการใช้งาน คอมไพเลอร์อาจจัดสรรหน่วยความจำจากที่ใดก็ได้ตราบใดที่รับประกันเกี่ยวกับอายุการใช้งานของวัตถุ
MM

1
@MM: ยุติธรรมเพียงพอ แต่ในทางปฏิบัติเรายังไม่สามารถใช้std::vectorแทนพูดalloca()ได้
einpoklum

@einpoklum ในแง่ของการรับเอาต์พุตที่ถูกต้องสำหรับโปรแกรมของคุณคุณสามารถทำได้ ประสิทธิภาพเป็นปัญหาคุณภาพของการใช้งาน
MM

1
คุณภาพของการใช้งาน @MM ไม่สามารถเคลื่อนย้ายได้ และถ้าคุณไม่ต้องการประสิทธิภาพคุณจะไม่ใช้ c ++ ในตอนแรก
เพื่อน

3

ใช้ std :: vector สำหรับสิ่งนี้ ตัวอย่างเช่น:

std::vector<int> values;
values.resize(n);

หน่วยความจำจะได้รับการจัดสรรในฮีป แต่จะมีข้อเสียเปรียบเล็กน้อย นอกจากนี้ก็ควรที่จะไม่จัดสรรดาต้าล็อคขนาดใหญ่บนสแต็กเนื่องจากมีขนาดค่อนข้าง จำกัด


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

1
ทำไมคุณไม่เพียงแค่ใช้std::vector<int> values(n);? โดยการใช้resizeหลังการก่อสร้างคุณจะห้ามประเภทที่เคลื่อนย้ายไม่ได้
LF

1

C99 อนุญาตให้ VLA และทำให้มีข้อ จำกัด ในการประกาศ VLA สำหรับรายละเอียดอ้างอิงที่ 6.7.5.2 ของมาตรฐาน C ++ ไม่อนุญาตให้ VLA แต่ g ++ อนุญาต


คุณสามารถให้ลิงค์ไปยังย่อหน้ามาตรฐานที่ชี้ไปได้หรือไม่?
Vincent

0

อาร์เรย์เช่นนี้เป็นส่วนหนึ่งของ C99 แต่ไม่ใช่ส่วนหนึ่งของ C ++ มาตรฐาน ดังที่คนอื่น ๆ พูดไว้เวกเตอร์มักเป็นทางออกที่ดีกว่ามากซึ่งอาจเป็นสาเหตุที่อาร์เรย์ขนาดตัวแปรไม่ได้อยู่ใน C ++ standatrd (หรือในมาตรฐาน C ++ 0x ที่เสนอ)

BTW สำหรับคำถามเกี่ยวกับ "ทำไม" มาตรฐาน C ++ เป็นอย่างที่เป็นอยู่การตรวจสอบกลุ่มข่าวสาร Usenet comp.std.c ++ ของ Usenet เป็นที่ที่ควรไป


6
-1 Vector ไม่ได้ดีกว่าเสมอไป บ่อยครั้งใช่ ไม่เสมอ หากคุณต้องการอาเรย์ขนาดเล็กอยู่บนแพลตฟอร์มที่พื้นที่ของฮีปช้าและการใช้เวกเตอร์ของไลบรารีของคุณใช้พื้นที่ของฮีพคุณลักษณะนี้อาจจะดีกว่าถ้ามันมีอยู่
Patrick M

-1

หากคุณรู้คุณค่า ณ เวลารวบรวมคุณสามารถทำสิ่งต่อไปนี้:

template <int X>
void foo(void)
{
   int values[X];

}

แก้ไข: คุณสามารถสร้างเวกเตอร์ที่ใช้ตัวจัดสรรสแต็ก (alloca) เนื่องจากตัวจัดสรรเป็นพารามิเตอร์เทมเพลต


18
หากคุณรู้คุณค่า ณ เวลารวบรวมคุณไม่จำเป็นต้องมีเทมเพลตเลย เพียงใช้ X โดยตรงในฟังก์ชั่นที่ไม่ใช่แม่แบบของคุณ
ร็อบเคนเนดี้

3
บางครั้งผู้โทรก็รู้เวลารวบรวมและผู้โทรไม่เข้าใจนั่นเป็นสิ่งที่แม่แบบนั้นดี แน่นอนในกรณีทั่วไปไม่มีใครรู้จัก X จนกว่าจะถึงเวลาทำงาน
Qwertie

คุณไม่สามารถใช้ alloca ในตัวจัดสรร STL - หน่วยความจำที่จัดสรรจาก alloca จะถูกปลดปล่อยเมื่อสแต็กเฟรมถูกทำลาย - นั่นคือเมื่อวิธีที่ควรจัดสรรหน่วยความจำคืน
Oliver

-5

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

void varTest(int iSz)
{
    char *varArray;
    __asm {
        sub esp, iSz       // Create space on the stack for the variable array here
        mov varArray, esp  // save the end of it to our pointer
    }

    // Use the array called varArray here...  

    __asm {
        add esp, iSz       // Variable array is no longer accessible after this point
    } 
}

อันตรายที่นี่มีมากมาย แต่ฉันจะอธิบายไม่กี่: 1. การเปลี่ยนขนาดของตัวแปรครึ่งทางจะฆ่าตำแหน่งสแต็ค 2. การใช้เกินขอบเขตอาร์เรย์จะทำลายตัวแปรอื่น ๆ และรหัสที่เป็นไปได้ 3 ซึ่งไม่ทำงานใน 64 บิต บิลด์ ... ต้องการแอสเซมบลีที่แตกต่างกันสำหรับอันนั้น (แต่แมโครอาจแก้ปัญหานั้นได้) 4. คอมไพเลอร์เฉพาะ (อาจมีปัญหาในการย้ายระหว่างคอมไพเลอร์) ฉันไม่ได้ลองเลยไม่รู้จริงๆ


... และถ้าคุณต้องการที่จะม้วนนี้ด้วยตัวคุณเองอาจจะใช้คลาส RAII?
einpoklum

คุณสามารถใช้ boost :: container :: static_vector
Viktor Sehr

สิ่งนี้ไม่มีสิ่งเทียบเท่าสำหรับคอมไพเลอร์อื่น ๆ ที่มีแอสเซมบลีดิบมากกว่า MSVC VC มีแนวโน้มที่จะเข้าใจว่าespมีการเปลี่ยนแปลงและจะปรับการเข้าถึงสแต็ก แต่ใน GCC เช่นคุณจะทำลายมันอย่างสมบูรณ์ - อย่างน้อยถ้าคุณใช้การเพิ่มประสิทธิภาพและ-fomit-frame-pointerโดยเฉพาะอย่างยิ่ง
Ruslan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.