ทำไมการใช้ alloca () จึงไม่ถือว่าเป็นแนวปฏิบัติที่ดี?


400

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

ทำไมการใช้งานalloca()ท้อแท้ทั้งๆที่มีคุณสมบัติข้างต้น?


40
เพียงแค่บันทึกย่อ แม้ว่าฟังก์ชั่นนี้สามารถพบได้ในคอมไพเลอร์ส่วนใหญ่ แต่ก็ไม่จำเป็นต้องใช้ตามมาตรฐาน ANSI-C ดังนั้นจึงสามารถ จำกัด การพกพาได้ อีกสิ่งหนึ่งคือคุณต้องไม่! ฟรี () ตัวชี้ที่คุณได้รับและมันจะถูกปลดปล่อยโดยอัตโนมัติหลังจากที่คุณออกจากฟังก์ชั่น
merkuro

9
นอกจากนี้ฟังก์ชั่นที่มี alloca () จะไม่ถูกแทรกหากประกาศเช่นนี้
Justicle

2
@Justicle คุณสามารถให้คำอธิบายได้ไหม? ฉันอยากรู้ว่าสิ่งใดที่อยู่เบื้องหลังพฤติกรรมนี้
migajek

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

5
ด้านบวกอีกอย่างallocaคือกองไม่สามารถแยกส่วนเหมือนกอง สิ่งนี้สามารถพิสูจน์ได้ว่ามีประโยชน์สำหรับแอปพลิเคชันสไตล์เรียลไทม์รันตลอดไปหรือแม้กระทั่งแอปพลิเคชันที่สำคัญด้านความปลอดภัยเนื่องจาก WCRU สามารถวิเคราะห์แบบคงที่ได้โดยไม่ต้องหันไปใช้พูลหน่วยความจำแบบกำหนดเอง ใช้).
Andreas

คำตอบ:


245

คำตอบอยู่ที่นั่นในmanหน้า (อย่างน้อยบนLinux ):

ค่าตอบแทนผลตอบแทนฟังก์ชั่น alloca () ส่งกลับตัวชี้ไปที่จุดเริ่มต้นของพื้นที่ที่จัดสรร หากการจัดสรรทำให้กองซ้อนมากเกินไปพฤติกรรมของโปรแกรมจะไม่ได้กำหนด

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


35
ดังนั้นจึงไม่มีปัญหาอะไรที่คุณจะไม่ต้องประกาศอาร์เรย์ขนาดใหญ่ด้วย?
TED

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

49
ประเด็นของฉันคือการให้เหตุผลในหน้าคนไม่สมเหตุสมผลเนื่องจาก alloca () นั้นเหมือนกับ "ไม่ดี" เหมือนกับสิ่งอื่น ๆ เหล่านั้น (อาร์เรย์ในท้องถิ่นหรือฟังก์ชั่นวนซ้ำ) ที่ถือว่าเป็น kosher
j_random_hacker

39
@ninjalj: ไม่ได้โดยที่มีประสบการณ์สูง C / C ++ โปรแกรมเมอร์ แต่ผมคิดว่าหลาย ๆ คนที่กลัวalloca()ไม่ได้มีความกลัวเดียวกันของอาร์เรย์ท้องถิ่นหรือเรียกซ้ำ (ในความเป็นจริงหลาย ๆ คนที่จะตะโกนลงalloca()จะสรรเสริญเรียกซ้ำเพราะมัน "ดูสง่างาม") . ฉันเห็นด้วยกับคำแนะนำของ Shaun ("alloca () เป็นเรื่องปกติสำหรับการจัดสรรเล็ก ๆ ") แต่ฉันไม่เห็นด้วยกับความคิดที่กำหนด alloca () ว่าเป็นความชั่วร้ายที่ไม่ซ้ำกันในหมู่ 3 - พวกมันอันตรายเท่ากัน!
j_random_hacker

35
หมายเหตุ: เมื่อกำหนดกลยุทธ์การจัดสรรหน่วยความจำ "แง่ดี" ของ Linux คุณมีโอกาสมากที่จะไม่ได้รับข้อบ่งชี้ของความล้มเหลวของฮีป - อ่อนเพลีย ... แทน malloc () จะส่งคืนตัวชี้ที่ไม่ใช่ค่า NULL ที่ดีและเมื่อคุณพยายาม เข้าถึงพื้นที่ที่อยู่ที่ชี้ไปกระบวนการของคุณ (หรือกระบวนการอื่น ๆ ที่คาดเดาไม่ได้) จะถูก OOM-killer ฆ่า แน่นอนว่านี่เป็น "คุณสมบัติ" ของ Linux แทนที่จะเป็นปัญหา C / C ++ ต่อ se แต่เป็นสิ่งที่ควรคำนึงถึงเมื่อพิจารณาว่า alloca () หรือ malloc () เป็น "ปลอดภัยกว่า" :)
Jeremy Friesner

209

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

ในไฟล์ส่วนหัว:

void DoSomething() {
   wchar_t* pStr = alloca(100);
   //......
}

ในไฟล์การนำไปใช้งาน:

void Process() {
   for (i = 0; i < 1000000; i++) {
     DoSomething();
   }
}

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

ดังนั้นบทเรียนคือ - อย่าใช้allocaในฟังก์ชั่นที่คุณคิดว่าอาจจะมีการอนุมาน


91
น่าสนใจ แต่นั่นจะไม่ถือว่าเป็นข้อผิดพลาดของคอมไพเลอร์หรือไม่? ท้ายที่สุดแล้วอินไลน์ที่เปลี่ยนพฤติกรรมของรหัส (มันล่าช้าการปลดปล่อยพื้นที่ที่จัดสรรโดยใช้ alloca)
sleske

60
เห็นได้ชัดว่าอย่างน้อย GCC จะคำนึงถึงสิ่งนี้: "โปรดทราบว่าการใช้งานบางอย่างในการกำหนดฟังก์ชันสามารถทำให้ไม่เหมาะสมสำหรับการทดแทนแบบอินไลน์ในการใช้งานเหล่านี้ ได้แก่ : การใช้ varargs การใช้ alloca, [... ]" gcc.gnu.org/onlinedocs/gcc/Inline.html
sleske

136
คุณรวบรวมบุหรี่อะไร
Thomas Eding

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

7
ฉันได้ downvoted แต่คำตอบคือได้เขียนดี: ผมเห็นด้วยกับคนอื่น ๆ ที่คุณกำลัง faulting จัดสรรสำหรับสิ่งที่เป็นอย่างชัดเจนข้อผิดพลาดคอมไพเลอร์ คอมไพเลอร์ได้ตั้งสมมติฐานที่ผิดพลาดในการปรับให้เหมาะสมซึ่งไม่ควรทำ การแก้ไขข้อผิดพลาดของคอมไพเลอร์นั้นใช้ได้ แต่ฉันจะไม่ผิดอะไรเลยนอกจากคอมไพเลอร์
Evan Carroll

75

คำถามเก่า แต่ไม่มีใครพูดถึงว่ามันควรจะถูกแทนที่ด้วยอาร์เรย์ความยาวตัวแปร

char arr[size];

แทน

char *arr=alloca(size);

มันอยู่ในมาตรฐาน C99 และมีอยู่เป็นส่วนขยายคอมไพเลอร์ในคอมไพเลอร์จำนวนมาก


5
มันถูกกล่าวถึงโดย Jonathan Leffler ในความคิดเห็นต่อคำตอบของ Arthur Ulfeldt
ninjalj

2
แน่นอน แต่มันก็แสดงให้เห็นว่ามันง่ายเพียงใดที่ฉันไม่ได้เห็นมันแม้จะอ่านคำตอบทั้งหมดก่อนโพสต์
Patrick Schlüter

6
One note - สิ่งเหล่านี้คืออาร์เรย์ความยาวผันแปรไม่ใช่อาเรย์แบบไดนามิก หลังมีการปรับขนาดและมักจะนำไปใช้กับกอง
Tim Čas

1
Visual Studio 2015 รวบรวม C ++ บางตัวมีปัญหาเดียวกัน
ahcox

2
Linus Torvalds ไม่ชอบVlas ในลินุกซ์ ในฐานะเวอร์ชั่น 4.20 Linux ควรเป็น VLA เกือบฟรี
Cristian Ciupitu

60

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

คุณสามารถค่อนข้างปลอดภัยถ้าคุณ

  • ห้ามส่งคืนพอยน์เตอร์หรือสิ่งใด ๆ ที่มีอยู่
  • อย่าเก็บตัวชี้ไว้ในโครงสร้างใด ๆ ที่จัดสรรไว้บนกอง
  • อย่าปล่อยให้เธรดอื่นใช้ตัวชี้

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


12
คุณสมบัติ VLA (อาเรย์ความยาวแปรผัน) ของ C99 รองรับตัวแปรท้องถิ่นที่มีขนาดแบบไดนามิกโดยไม่ต้องใช้ alloca () อย่างชัดเจน
Jonathan Leffler

2
Neato! พบข้อมูลเพิ่มเติมในส่วน '3.4 Variable Length Array' ของprogrammersheaven.com/2Pointers-and-Arrays-page-2
Arthur Ulfeldt

1
แต่นั่นไม่แตกต่างจากการจัดการกับพอยน์เตอร์ไปจนถึงตัวแปรโลคอล พวกเขาสามารถถูกหลอกได้เช่นกัน ...
glglgl

2
@ Jonathan Leffler สิ่งหนึ่งที่คุณสามารถทำได้กับ alloca แต่คุณไม่สามารถทำได้กับ VLA คือการใช้คำหลักที่ จำกัด กับพวกเขา เช่นนี้ลอย * จำกัด heavy_used_arr = alloca (ขนาดของ (ลอย) * ขนาด); แทนการลอยอย่างหนัก _used_arr [ขนาด] มันอาจช่วยให้คอมไพเลอร์บางส่วน (gcc 4.8 ในกรณีของฉัน) เพื่อเพิ่มประสิทธิภาพการประกอบแม้ว่าขนาดจะเป็นค่าคงที่การรวบรวม ดูคำถามของฉันเกี่ยวกับมัน: stackoverflow.com/questions/19026643/using-restrict-with-arrays
Piotr Lopusiewicz

@JonathanLeffler VLA เป็นโลคัลสำหรับบล็อกที่มีอยู่ ในอีกทางหนึ่งalloca()จัดสรรหน่วยความจำที่ยังคงอยู่จนถึงจุดสิ้นสุดของฟังก์ชั่น ซึ่งหมายความว่ามีดูเหมือนจะไม่ตรงไปตรงมา, การแปลที่สะดวกในการ VLA f() { char *p; if (c) { /* compute x */ p = alloca(x); } else { p = 0; } /* use p */ }ของ หากคุณคิดว่าเป็นไปได้ที่จะแปลการใช้allocaเพื่อการใช้งาน VLA โดยอัตโนมัติแต่ต้องการมากกว่าความคิดเห็นเพื่ออธิบายวิธีฉันสามารถสร้างคำถามนี้ได้
Pascal Cuoq

40

ดังที่ได้กล่าวไว้ในการโพสต์ของกลุ่มข่าวนี้มีสาเหตุบางประการที่การใช้งานallocaนั้นถือเป็นเรื่องยากและอันตราย

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

24
สิ่งหนึ่งที่ฉันเห็นในลิงค์ที่ไม่ได้อยู่ที่อื่นในหน้านี้คือฟังก์ชั่นที่ใช้alloca()ต้องมีการลงทะเบียนแยกต่างหากสำหรับการถือตัวชี้กองซ้อนและตัวชี้เฟรม บน x86 CPUs> = 386 ตัวชี้แบบสแต็กESPสามารถใช้สำหรับทั้งสองซึ่งทำให้หมดEBP- ยกเว้นว่าalloca()ใช้
j_random_hacker

10
อีกจุดที่ดีในหน้านั้นคือหากไม่มีตัวสร้างโค้ดของคอมไพเลอร์จัดการมันเป็นกรณีพิเศษf(42, alloca(10), 43);อาจมีปัญหาเนื่องจากความเป็นไปได้ที่ตัวชี้สแต็กจะถูกปรับโดยalloca() หลังจากอาร์กิวเมนต์อย่างน้อยหนึ่งตัวถูกผลัก
j_random_hacker

3
โพสต์ที่เชื่อมโยงดูเหมือนจะเขียนโดย John Levine - เพื่อนที่เขียนว่า "Linkers and Loaders" ฉันจะไว้วางใจสิ่งที่เขาพูด
user318904

3
โพสต์ที่เชื่อมโยงเป็นการตอบกลับการโพสต์ของ John Levine
A. Wilcox

6
จำไว้เป็นจำนวนมากมีการเปลี่ยนแปลงตั้งแต่ปี 1991 คอมไพเลอร์ C ทั้งหมดที่ทันสมัย (แม้ในปี 2009) มีการจัดการจัดสรรเป็นกรณีพิเศษ มันเป็นสิ่งที่แท้จริงมากกว่าฟังก์ชั่นธรรมดาและอาจไม่เรียกฟังก์ชั่น ดังนั้นปัญหา alloca-in-parameter (ซึ่งเกิดขึ้นใน K&R C จากปี 1970) ไม่น่าจะมีปัญหาในขณะนี้ รายละเอียดเพิ่มเติมในความคิดเห็นที่ฉันทำกับคำตอบของโทนี่ D
greggo

26

ปัญหาหนึ่งคือมันไม่ได้มาตรฐานแม้ว่าจะได้รับการสนับสนุนอย่างกว้างขวาง สิ่งอื่น ๆ ที่เท่าเทียมกันฉันมักจะใช้ฟังก์ชั่นมาตรฐานมากกว่าส่วนขยายคอมไพเลอร์ทั่วไป


21

ยังคงใช้ alloca ท้อทำไม?

ฉันไม่เข้าใจฉันทามติดังกล่าว ข้อดีที่แข็งแกร่งมากมาย ข้อเสียไม่กี่:

  • C99 มีอาร์เรย์ความยาวผันแปรซึ่งมักจะถูกใช้เป็นพิเศษเนื่องจากสัญกรณ์มีความสอดคล้องกับอาร์เรย์ที่มีความยาวคงที่และโดยรวมที่ใช้งานง่าย
  • ระบบจำนวนมากมีหน่วยความจำรวม / ที่อยู่พื้นที่ว่างน้อยกว่าสำหรับสแต็กมากกว่าที่ทำสำหรับฮีปซึ่งทำให้โปรแกรมมีความอ่อนไหวต่อความจำของหน่วยความจำมากขึ้นเล็กน้อย (ผ่านสแต็คล้น): นี่อาจเป็นสิ่งที่ดี สาเหตุที่สแต็คไม่เติบโตโดยอัตโนมัติตามที่ฮีปทำคือป้องกันโปรแกรมที่ไม่อยู่ในการควบคุมไม่ให้เกิดผลกระทบต่อทั้งเครื่อง
  • เมื่อใช้ในขอบเขตภายในเพิ่มเติม (เช่น a whileหรือforloop) หรือในหลายขอบเขตหน่วยความจำจะสะสมตามการวนซ้ำ / ขอบเขตและจะไม่ปล่อยจนกว่าฟังก์ชันจะออก: ฟังก์ชันนี้ขัดแย้งกับตัวแปรปกติที่กำหนดไว้ในขอบเขตของโครงสร้างการควบคุม (เช่นfor {int i = 0; i < 2; ++i) { X }จะสะสมallocaหน่วยความจำที่ร้องขอที่ X แต่หน่วยความจำสำหรับอาร์เรย์ที่มีขนาดคงที่จะถูกรีไซเคิลต่อการทำซ้ำ)
  • คอมไพเลอร์ที่ทันสมัยมักจะไม่inlineทำงานที่เรียกว่าallocaแต่ถ้าคุณบังคับพวกเขาแล้วallocaจะเกิดขึ้นในบริบทของผู้โทร (เช่นสแต็กจะไม่ออกจนกว่าผู้โทรกลับ)
  • เมื่อนานมาแล้วได้allocaเปลี่ยนจากฟีเจอร์ / แฮ็คที่ไม่ใช่แบบพกพาเป็นส่วนขยายมาตรฐาน แต่การรับรู้เชิงลบบางอย่างอาจยังคงมีอยู่
  • อายุการใช้งานถูกผูกไว้กับขอบเขตของฟังก์ชั่นซึ่งอาจเหมาะสมกับโปรแกรมเมอร์มากกว่าmallocการควบคุมอย่างชัดเจน
  • ต้องใช้การmallocกระตุ้นให้คิดถึงการจัดสรรคืน - หากมีการจัดการผ่านฟังก์ชั่น wrapper (เช่นWonderfulObject_DestructorFree(ptr)) จากนั้นฟังก์ชั่นจะเป็นจุดสำหรับการดำเนินการล้างข้อมูลการดำเนินงาน (เช่นตัวอธิบายไฟล์ปิดการปลดปล่อยพอยน์เตอร์ภายในหรือทำการบันทึกบางอย่าง) รหัส: บางครั้งมันเป็นรูปแบบที่ดีที่จะนำมาใช้อย่างสม่ำเสมอ
    • ในรูปแบบการเขียนโปรแกรมแบบหลอก OO นี้เป็นเรื่องปกติที่จะต้องการWonderfulObject* p = WonderfulObject_AllocConstructor();- ที่เป็นไปได้เมื่อ "constructor" เป็นฟังก์ชั่นmallocหน่วยความจำที่ส่งคืนหน่วยความจำ (เนื่องจากหน่วยความจำยังคงถูกจัดสรรหลังจากฟังก์ชันส่งคืนค่าที่เก็บไว้p) ถ้า "constructor" ใช้alloca
      • มาโครรุ่นหนึ่งWonderfulObject_AllocConstructorสามารถบรรลุสิ่งนี้ได้ แต่ "มาโครนั้นชั่วร้าย" ซึ่งพวกเขาสามารถขัดแย้งกันและไม่ใช่โค้ดแมโครและสร้างการแทนที่ที่ไม่ได้ตั้งใจและปัญหาที่ยากต่อการวินิจฉัยที่ตามมา
    • freeการดำเนินการที่หายไปสามารถตรวจพบได้โดย ValGrind, Purify ฯลฯ แต่การเรียก "destructor" ที่หายไปนั้นไม่สามารถตรวจพบได้ตลอดเวลา - ผลประโยชน์ที่สำคัญเพียงอย่างเดียวในแง่ของการบังคับใช้การใช้งานที่ตั้งใจไว้ alloca()การใช้งานบางอย่าง(เช่น GCC) ใช้มาโครแบบอินไลน์สำหรับalloca()ดังนั้นการทดแทนรันไทม์ของไลบรารีการวินิจฉัยการใช้หน่วยความจำจึงไม่สามารถทำได้สำหรับmalloc/ realloc/ free(เช่นรั้วไฟฟ้า)
  • การใช้งานบางอย่างมีปัญหาเล็กน้อย: ตัวอย่างเช่นจาก manpage Linux:

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


ฉันรู้ว่าคำถามนี้ถูกติดแท็ก C แต่ในฐานะโปรแกรมเมอร์ C ++ ฉันคิดว่าฉันใช้ C ++ เพื่อแสดงอรรถประโยชน์ที่เป็นไปได้ของalloca: โค้ดด้านล่าง (และที่นี่ที่ ideone ) สร้างเวกเตอร์การติดตามเวกเตอร์ชนิด polymorphic ขนาดแตกต่างกัน อายุการใช้งานเชื่อมโยงกับฟังก์ชันส่งคืน) แทนที่จะจัดสรรฮีป

#include <alloca.h>
#include <iostream>
#include <vector>

struct Base
{
    virtual ~Base() { }
    virtual int to_int() const = 0;
};

struct Integer : Base
{
    Integer(int n) : n_(n) { }
    int to_int() const { return n_; }
    int n_;
};

struct Double : Base
{
    Double(double n) : n_(n) { }
    int to_int() const { return -n_; }
    double n_;
};

inline Base* factory(double d) __attribute__((always_inline));

inline Base* factory(double d)
{
    if ((double)(int)d != d)
        return new (alloca(sizeof(Double))) Double(d);
    else
        return new (alloca(sizeof(Integer))) Integer(d);
}

int main()
{
    std::vector<Base*> numbers;
    numbers.push_back(factory(29.3));
    numbers.push_back(factory(29));
    numbers.push_back(factory(7.1));
    numbers.push_back(factory(2));
    numbers.push_back(factory(231.0));
    for (std::vector<Base*>::const_iterator i = numbers.begin();
         i != numbers.end(); ++i)
    {
        std::cout << *i << ' ' << (*i)->to_int() << '\n';
        (*i)->~Base();   // optionally / else Undefined Behaviour iff the
                         // program depends on side effects of destructor
    }
}

ไม่มี +1 เพราะวิธีที่แปลกประหลาดของการจัดการหลายประเภท :-(
einpoklum

@ einpoklum: ดีนั่นคือการตรัสรู้อย่างลึกซึ้ง ... ขอบคุณ
Tony Delroy

1
ให้ฉันใช้ถ้อยคำใหม่: นี่เป็นคำตอบที่ดีมาก จนถึงจุดที่ฉันคิดว่าคุณกำลังแนะนำให้ผู้คนใช้แบบเคาน์เตอร์
einpoklum

ความคิดเห็นจาก manpage ของ linux นั้นเก่ามากและฉันค่อนข้างแน่ใจว่าล้าสมัยแล้ว ผู้รวบรวมสมัยใหม่ทุกคนรู้ว่า alloca () คืออะไรและจะไม่เดินทางข้ามเชือกผูกรองเท้าของพวกเขาเช่นนั้น ใน K&R C เก่า (1) ฟังก์ชั่นทั้งหมดที่ใช้ตัวชี้เฟรม (2) การเรียกใช้ฟังก์ชันทั้งหมดคือ {push args บน stack} {call func} {add # n, sp} alloca เป็นฟังก์ชั่น lib ที่เพิ่งจะกระแทกกองขึ้นคอมไพเลอร์ไม่ได้รู้เกี่ยวกับสิ่งที่เกิดขึ้น (1) และ (2) ไม่เป็นความจริงอีกต่อไปดังนั้น alloca จึงไม่สามารถทำงานได้ (ตอนนี้มันเป็นสิ่งที่แท้จริง) ใน C เก่าการเรียกอัลโตในช่วงกลางของการผลัก args จะทำลายสมมติฐานเหล่านั้นด้วย
greggo

4
เกี่ยวกับตัวอย่างผมมีความกังวลโดยทั่วไปเกี่ยวกับสิ่งที่ต้อง always_inline การทุจริตหน่วยความจำหลีกเลี่ยง ....
greggo

14

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

กล่าวอีกนัยหนึ่งalloca( 0x00ffffff )เป็นสิ่งที่อันตรายและมีแนวโน้มที่จะทำให้เกิดน้ำท่วมมากเท่าที่char hugeArray[ 0x00ffffff ];ควร ระมัดระวังและมีเหตุผลและคุณจะสบายดี


12

คำตอบที่น่าสนใจมากมายสำหรับคำถาม "เก่า" นี้แม้จะมีคำตอบที่ค่อนข้างใหม่ แต่ฉันไม่พบสิ่งใดเลยที่พูดถึงเรื่องนี้ ....

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

ฉันพบว่าข้อความอ้างอิงใน .... ตกลงฉันทำข้อความนั้นขึ้น แต่ลองคิดดูสิ ....

@j_random_hacker เป็นอย่างมากที่ถูกต้องในการแสดงความคิดเห็นของเขาภายใต้คำตอบอื่น ๆ : การหลีกเลี่ยงการใช้alloca()ในความโปรดปรานของอาร์เรย์ท้องถิ่นขนาดใหญ่ไม่ได้ทำให้โปรแกรมของคุณปลอดภัยมากขึ้นจากการล้นสแต็ค (ยกเว้นกรณีที่คอมไพเลอร์ของคุณก็พอเก่าที่จะช่วยให้ inlining ของฟังก์ชั่นที่ใช้งานalloca()ซึ่งในกรณีนี้คุณควร อัปเกรดหรือยกเว้นว่าคุณใช้alloca()ลูปภายในซึ่งในกรณีนี้คุณไม่ควร ... ใช้alloca()ลูปภายใน)

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

alloca() (หรือ VLAs) อาจเป็นเพียงเครื่องมือที่เหมาะสมสำหรับงาน

ฉันได้เห็นเวลาและเวลาอีกครั้งที่โปรแกรมเมอร์สร้างบัฟเฟอร์สแต็กจัดสรร "ใหญ่พอที่จะรองรับกรณีที่เป็นไปได้" ในทรีคอลที่ซ้อนกันอย่างลึกการใช้รูปแบบ (anti -?) ซ้ำ ๆ จะนำไปสู่การใช้สแต็กที่พูดเกินจริง (ลองนึกภาพสายเรียกเข้าลึก 20 ระดับซึ่งแต่ละระดับด้วยเหตุผลที่แตกต่างกันฟังก์ชั่นสุ่มจัดสรรจัดสรรบัฟเฟอร์ที่ 1024 ไบต์ "ตาบอดปลอดภัย" โดยทั่วไปแล้วจะใช้เพียง 16 หรือน้อยกว่าเท่านั้น กรณีที่หายากอาจใช้มากขึ้น) ทางเลือกคือการใช้alloca()หรือ VLAs และจัดสรรพื้นที่สแต็คให้มากที่สุดเท่าที่ความต้องการของฟังก์ชันของคุณเพื่อหลีกเลี่ยงการสร้างสแต็กโดยไม่จำเป็น หวังว่าเมื่อหนึ่งฟังก์ชันใน call tree ต้องการการจัดสรรที่มีขนาดใหญ่กว่าปกติส่วนอื่น ๆ ใน call tree ยังคงใช้การจัดสรรขนาดเล็กตามปกติและการใช้งานโดยรวมของแอปพลิเคชันโดยรวมนั้นน้อยกว่าอย่างมาก .

แต่ถ้าคุณเลือกที่จะใช้alloca()...

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


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

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

Sidenote: ตัวอย่าง "การใช้บัฟเฟอร์คงที่ [MAX_SIZE]" จะเน้นว่าทำไมนโยบายหน่วยความจำทับซ้อนจึงทำงานได้ดี โปรแกรมจัดสรรหน่วยความจำที่พวกเขาไม่เคยสัมผัสยกเว้นที่ขีดจำกัดความยาวของบัฟเฟอร์ ดังนั้นจึงเป็นเรื่องดีที่ Linux (และระบบปฏิบัติการอื่น ๆ ) ไม่ได้กำหนดหน้าหน่วยความจำจนกว่าจะมีการใช้งานครั้งแรก (ตรงข้ามกับ malloc'd) หากบัฟเฟอร์มีขนาดใหญ่กว่าหนึ่งหน้าโปรแกรมอาจใช้หน้าแรกเท่านั้นและไม่ทำให้หน่วยความจำกายภาพเหลืออยู่
Katastic Voyage

@KatasticVoyage เว้นเสียแต่ว่า MAX_SIZE จะมากกว่า (หรืออย่างน้อยเท่ากับ) ขนาดของขนาดหน่วยความจำเสมือนของระบบของคุณอาร์กิวเมนต์ของคุณไม่ค้าง นอกจากนี้ในระบบฝังตัวที่ไม่มีหน่วยความจำเสมือน (MCU ที่ฝังตัวจำนวนมากไม่มี MMU) นโยบายหน่วยความจำ overcommit อาจดีจาก "ให้แน่ใจว่าโปรแกรมของคุณจะทำงานได้ในทุกสถานการณ์" แต่ความมั่นใจนั้นมาพร้อมกับราคาที่ขนาดสแต็กของคุณ จะต้องได้รับการจัดสรรเช่นเดียวกันเพื่อสนับสนุนนโยบายหน่วยความจำส่วนเกิน ในระบบฝังตัวบางตัวนั่นเป็นราคาที่ผู้ผลิตบางรายของผลิตภัณฑ์ต้นทุนต่ำไม่เต็มใจจ่าย
phonetagger

11

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

คุณสามารถตรวจสอบข้อยกเว้น SEH นี้และโทร _resetstkoflw เพื่อรีเซ็ตสแต็กและดำเนินการต่อด้วยวิธีที่สนุกสนาน มันไม่เหมาะ แต่เป็นอีกกลไกหนึ่งที่อย่างน้อยก็รู้ว่ามีบางอย่างผิดปกติเมื่อสิ่งที่กระทบแฟน ๆ * ระวังอาจมีบางสิ่งที่คล้ายกันซึ่งฉันไม่ทราบ

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

นอกจากนี้ยังไม่อนุญาตให้มีการรั่วไหลของหน่วยความจำ alloca ไม่ก่อให้เกิดการกระจายตัวของหน่วยความจำซึ่งเป็นสิ่งสำคัญสวย ฉันไม่คิดว่า alloca เป็นการฝึกที่ไม่ดีถ้าคุณใช้อย่างชาญฉลาดซึ่งเป็นเรื่องจริงสำหรับทุกสิ่ง :-)


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

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

10

alloca ()ดีและมีประสิทธิภาพ ... แต่มันก็แตกได้เช่นกัน

  • ลักษณะการทำงานของขอบเขตที่เสียหาย (ขอบเขตฟังก์ชันแทนที่จะเป็นขอบเขตบล็อก)
  • ใช้ไม่สอดคล้องกับ malloc ( alloca () -ตัวชี้ไม่ควรเป็นอิสระต่อจากนี้ไปคุณต้องติดตามว่าตัวชี้มาจากไหนถึงฟรี ()เฉพาะที่คุณมีmalloc ( )
  • พฤติกรรมไม่ดีเมื่อคุณใช้อินไลน์ด้วย (บางครั้งขอบเขตจะไปที่ฟังก์ชันผู้โทรขึ้นอยู่กับว่ามีการแทรกอินไลน์หรือไม่)
  • ไม่มีการตรวจสอบขอบเขตสแต็ก
  • พฤติกรรมที่ไม่ได้กำหนดในกรณีที่เกิดความล้มเหลว (ไม่ส่งคืนค่า NULL เช่น malloc ... และความล้มเหลวหมายถึงอะไรเนื่องจากไม่ได้ตรวจสอบขอบเขตสแต็กต่อไป ... )
  • ไม่ได้มาตรฐาน ansi

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

หากคุณต้องการ C จริงๆคุณสามารถใช้ VLA (ไม่มี vla ใน C ++, แย่มาก) พวกเขาดีกว่า alloca () เกี่ยวกับพฤติกรรมขอบเขตและความมั่นคง อย่างที่ฉันเห็นมันVLAเป็นแบบalloca () ที่ถูกต้อง

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


ฉันไม่เห็นสาเหตุของการลงคะแนนเสียง (โดยไม่มีความคิดเห็นใด ๆ )
gd1

ชื่อเท่านั้นที่มีขอบเขต allocaไม่ได้สร้างชื่อเพียงช่วงหน่วยความจำซึ่งมีอายุการใช้งาน
curiousguy

@currguy: คุณแค่เล่นด้วยคำพูด สำหรับตัวแปรอัตโนมัติฉันสามารถพูดถึงอายุการใช้งานของหน่วยความจำพื้นฐานได้เช่นกันซึ่งตรงกับขอบเขตของชื่อ อย่างไรก็ตามปัญหาไม่ใช่วิธีที่เราเรียกมันว่า แต่ความไม่แน่นอนของอายุการใช้งาน / ขอบเขตของหน่วยความจำที่คืนมาจาก alloca และพฤติกรรมที่ยอดเยี่ยม
kriss

2
ฉันหวังว่า alloca จะมี "freea" ที่ตรงกันด้วยข้อกำหนดที่เรียกว่า "freea" จะยกเลิกผลกระทบของ "alloca" ที่สร้างวัตถุและสิ่งที่ตามมาทั้งหมดและความต้องการที่เก็บ 'alloca'ed ภายใน fucntion ต้อง เป็น 'freea'ed ภายในเช่นกัน ที่จะทำให้มันเป็นไปได้สำหรับการใช้งานเกือบทั้งหมดเพื่อสนับสนุน alloca / freea ในรูปแบบที่เข้ากันได้จะได้ปลดเปลื้องปัญหาที่ inline และโดยทั่วไปทำให้สิ่งที่สะอาดมากขึ้น
supercat

2
@supercat - ฉันก็หวังเช่นกัน สำหรับเหตุผลที่ (และอื่น ๆ ) ผมใช้ชั้น abstraction (ส่วนใหญ่แมโครและฟังก์ชั่นแบบอินไลน์) เพื่อที่ฉันไม่เคยโทรallocaหรือmallocหรือfreeโดยตรง ผมพูดสิ่งที่ต้องการและ{stack|heap}_alloc_{bytes,items,struct,varstruct} {stack|heap}_deallocดังนั้นheap_deallocเพียงแค่โทรfreeและstack_deallocเป็นแบบไม่มี ด้วยวิธีนี้การจัดสรรสแต็กสามารถลดระดับเป็นการจัดสรรฮีปได้อย่างง่ายดายและความตั้งใจมีความชัดเจนยิ่งขึ้นเช่นกัน
ทอดด์เลห์แมน

9

นี่คือเหตุผล:

char x;
char *y=malloc(1);
char *z=alloca(&x-y);
*z = 1;

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

แทบทุกรหัสใช้ allocaและ / หรือ C99 vlas ทั้งหมดมีข้อบกพร่องร้ายแรงซึ่งจะนำไปสู่การล่ม (ถ้าคุณโชคดี) หรือการประนีประนอมสิทธิ์ (ถ้าคุณไม่โชคดีมาก)


1
โลกอาจไม่เคยรู้ :( ที่กล่าวว่าฉันหวังว่าคุณจะสามารถชี้แจงคำถามที่ฉันมีเกี่ยวกับallocaคุณกล่าวว่าเกือบทุกรหัสที่ใช้มันมีข้อผิดพลาด แต่ฉันวางแผนที่จะใช้มันปกติแล้วฉันจะไม่สนใจการเรียกร้องดังกล่าว แต่มา จากคุณฉันจะไม่ฉันเขียนเครื่องเสมือนและฉันต้องการจัดสรรตัวแปรที่ไม่หนีจากฟังก์ชั่นบนสแต็คแทนที่จะเป็นแบบไดนามิกเพราะความเร็วมหาศาลมหาศาลมีทางเลือกอื่น วิธีการที่มีลักษณะการทำงานเหมือนกันฉันรู้ว่าฉันสามารถเข้าใกล้กับพูลหน่วยความจำได้ แต่ก็ยังไม่ถูกเท่าไหร่คุณจะทำอะไรดี
GManNickG

7
รู้ว่าสิ่งที่อันตรายยัง? นี่: *0 = 9;น่าอัศจรรย์ !!! ฉันเดาว่าฉันไม่ควรใช้พอยน์เตอร์ เอ่อรอ ฉันสามารถทดสอบเพื่อดูว่ามันเป็นโมฆะ อืมมม allocaผมคิดว่าผมยังสามารถทดสอบขนาดของหน่วยความจำที่ฉันต้องการที่จะจัดสรรผ่าน ชายแปลก ๆ แปลก.
Thomas Eding

7
*0=9;ไม่ถูกต้อง C. ส่วนการทดสอบขนาดที่คุณส่งให้allocaทดสอบกับอะไรบ้าง ไม่มีทางที่จะรู้ถึงขีด จำกัด และถ้าคุณเพิ่งจะทดสอบกับขนาดที่ปลอดภัยขนาดเล็กที่รู้จักกันดี (เช่น 8k) คุณอาจใช้ขนาดคงที่บนสแต็ก
. GitHub หยุดช่วยน้ำแข็ง

7
ปัญหาของคุณ "ทั้งขนาดเป็นที่รู้กันว่าเล็กพอหรือไม่ก็ขึ้นอยู่กับปัจจัยการผลิตและอาจเป็นข้อโต้แย้งที่ใหญ่มาก" อย่างที่ฉันเห็นมันคือมันใช้อย่างยิ่งกับการเรียกซ้ำ การประนีประนอมในทางปฏิบัติ (สำหรับทั้งสองกรณี) คือการสันนิษฐานว่าถ้าขนาดถูกล้อมด้วยsmall_constant * log(user_input)เราอาจมีหน่วยความจำเพียงพอ
j_random_hacker

1
แน่นอนคุณได้ระบุกรณีหนึ่งที่ VLA / alloca มีประโยชน์: อัลกอริทึมแบบเรียกซ้ำที่พื้นที่สูงสุดที่ต้องการในกรอบการโทรใด ๆ อาจมีขนาดใหญ่เท่ากับ N แต่ที่ผลรวมของพื้นที่ที่ต้องการในทุกระดับการเรียกซ้ำคือ N หรือบางฟังก์ชัน ของ N ที่ไม่เติบโตอย่างรวดเร็ว
.. GitHub หยุดช่วยน้ำแข็ง

9

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

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

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

UPDATE: เนื่องจากอาร์เรย์ท้องถิ่นที่มีความยาวผันแปรได้ถูกเพิ่มใน C และเนื่องจากปัญหาการสร้างโค้ดที่คล้ายกันมากในคอมไพเลอร์ในฐานะ alloca ฉันเห็นว่า 'ความหายากในการใช้งานและสถานะร่มรื่น' ไม่ได้ใช้กับกลไกพื้นฐาน แต่ฉันก็ยังสงสัยว่าการใช้ alloca หรือ VLA นั้นมีแนวโน้มที่จะประนีประนอมการสร้างโค้ดภายในฟังก์ชั่นที่ใช้มัน ฉันยินดีรับข้อเสนอแนะจากนักออกแบบคอมไพเลอร์


1
ไม่เคยเพิ่มอาร์เรย์ความยาวแปรผันใน C ++
Nir Friedman

@NirFriedman แน่นอน ฉันคิดว่ามีรายการคุณสมบัติวิกิพีเดียที่ยึดตามข้อเสนอแบบเก่า
greggo

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

8

หนึ่งในอันตรายด้วยallocaคือว่าlongjmprewinds มัน

นั่นคือจะบอกว่าถ้าคุณบันทึกบริบทด้วยsetjmpแล้วallocaหน่วยความจำบางส่วนแล้วlongjmpกับบริบทคุณอาจสูญเสียallocaหน่วยความจำ ตัวชี้สแต็กกลับมาอยู่ในตำแหน่งเดิมดังนั้นหน่วยความจำจึงไม่ถูกสงวนไว้อีกต่อไป ถ้าคุณเรียกใช้ฟังก์ชันหรือทำอีกคุณจะบังคับเดิมallocaalloca

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

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

allocaอนึ่งนี้ถ้ามันวิธีการทำงานนั้นให้เป็นกลไกที่เป็นไปได้สำหรับหน่วยความจำจงใจพ้นที่จัดสรร

ฉันพบสิ่งนี้เป็นสาเหตุของข้อผิดพลาด


1
นั่นคือสิ่งที่ควรทำ - longjmpย้อนกลับไปและทำให้โปรแกรมลืมทุกอย่างที่เกิดขึ้นในสแต็ค: ตัวแปรทั้งหมด, ฟังก์ชั่นการโทร ฯลฯ และallocaเป็นเหมือนอาเรย์บนสแต็คดังนั้นจึงคาดว่าพวกเขาจะถูกอุดตัน ชอบทุกอย่างอื่นในสแต็ค
tehftw

1
man allocaให้ประโยคต่อไปนี้: "เนื่องจากพื้นที่ที่จัดสรรโดย alloca () ได้รับการจัดสรรภายในกรอบสแต็กพื้นที่นั้นจะถูกทำให้เป็นอิสระโดยอัตโนมัติหากฟังก์ชันส่งคืนข้ามโดยการเรียกไปยัง longjmp (3) หรือ siglongjmp (3)" ดังนั้นมันจึงเป็นเอกสารที่จัดสรรหน่วยความจำที่มีallocaได้รับ clobbered longjmpหลังจาก
tehftw

@tehftw longjmpสถานการณ์ที่อธิบายเกิดขึ้นโดยไม่ต้องมีฟังก์ชั่นการกลับมาเป็นเพิ่มขึ้นกว่าโดย ฟังก์ชั่นเป้าหมายยังไม่ได้กลับมา มันได้ทำsetjmp, และจากนั้นalloca อาจย้อนกลับรัฐกลับไปยังสิ่งที่มันเป็นที่เวลา กล่าวคือตัวชี้ย้ายโดยความทุกข์จากปัญหาเดียวกันกับตัวแปรท้องถิ่นที่ไม่ได้ทำเครื่องหมาย! longjmplongjmpallocasetjmpallocavolatile
Kaz

3
ฉันไม่เข้าใจว่าทำไมมันควรจะคาดไม่ถึง เมื่อคุณsetjmpจากallocaนั้นlongjmpมันเป็นเรื่องปกติที่ allocaจะถูกย้อนกลับ จุดทั้งหมดของlongjmpคือการกลับไปที่สถานะที่ถูกบันทึกไว้setjmp!
tehftw

@tehftw ฉันไม่เคยเห็นเอกสารการโต้ตอบนี้มาก่อน ดังนั้นจึงไม่สามารถใช้วิธีใดวิธีหนึ่งนอกเหนือจากการตรวจสอบเชิงประจักษ์กับคอมไพเลอร์
Kaz

7

สถานที่ที่alloca()มีอันตรายโดยเฉพาะอย่างยิ่งmalloc()เป็นเคอร์เนล - เคอร์เนลของระบบปฏิบัติการทั่วไปมีพื้นที่สแต็กขนาดคงที่ซึ่งกำหนดรหัสฮาร์ดไว้ในส่วนหัวหนึ่ง ไม่มีความยืดหยุ่นเท่ากับสแต็กของแอปพลิเคชัน การโทรติดต่อalloca()ด้วยขนาดที่ไม่ได้รับการรับรองอาจทำให้เคอร์เนลขัดข้อง คอมไพเลอร์บางตัวเตือนการใช้งานalloca()(และแม้กระทั่ง VLA สำหรับเรื่องนั้น) ภายใต้ตัวเลือกบางอย่างที่ควรเปิดในขณะที่คอมไพล์โค้ดเคอร์เนล - ที่นี่จะเป็นการดีกว่าที่จะจัดสรรหน่วยความจำในฮีปที่ไม่คงที่


7
alloca()ไม่เป็นอันตรายมากกว่าint foo[bar];ที่barเป็นจำนวนเต็มโดยพลการ
ทอดด์เลห์แมนใน

@ToddLehman ถูกต้องและด้วยเหตุผลที่แน่นอนเราได้ห้าม VLAs ในเคอร์เนลเป็นเวลาหลายปีและได้รับ VLA ฟรีตั้งแต่ปี 2561 :-)
Chris Down

6

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

_alloca บล็อกบนสแต็ก

ผลที่ตามมาคือสองเท่า:

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

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

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


11
ไม่มีสิ่งใดในสถานการณ์นี้ที่แตกต่างอย่างมากจากอันตรายของบัฟเฟอร์ที่มีมากเกินไปของบัฟเฟอร์ที่จัดสรรสแต็กขนาดคงที่ allocaอันตรายนี้จะไม่ซ้ำกับ
โทรศัพท์

2
ไม่แน่นอน แต่โปรดตรวจสอบคำถามเดิม คำถามคือสิ่งที่เป็นอันตรายallocaเมื่อเทียบกับmalloc(จึงไม่ใช่บัฟเฟอร์ขนาดคงที่ในกอง)
rustyx

จุดเล็ก ๆ น้อย ๆ แต่อยู่ในระบบบางระบบที่เติบโตสูงขึ้น (เช่น PIC 16- บิตไมโครโปรเซสเซอร์)
EBlake

5

น่าเศร้าที่น่ากลัวอย่างแท้จริงalloca()หายไปจาก tcc ที่น่ากลัวเกือบ GCC alloca()จะมี

  1. มันหว่านเมล็ดพืชแห่งการทำลายล้างของมันเอง ด้วยการกลับมาเป็นผู้ทำลายล้าง

  2. เช่นเดียวกับmalloc()มันจะส่งคืนตัวชี้ที่ไม่ถูกต้องในความล้มเหลวซึ่งจะ segfault ในระบบที่ทันสมัยด้วย MMU (และหวังว่าจะรีสตาร์ทโดยไม่ต้อง)

  3. แตกต่างจากตัวแปรอัตโนมัติคุณสามารถระบุขนาดในขณะใช้งาน

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

หากคุณกดลึกเกินไปคุณจะมั่นใจใน segfault (ถ้าคุณมี MMU)

โปรดทราบว่าmalloc()ไม่มีข้อเสนอเพิ่มเติมเนื่องจากจะส่งคืนค่า NULL (ซึ่งจะแยก segfault หากกำหนดไว้) เมื่อระบบหน่วยความจำไม่เพียงพอ นั่นคือทั้งหมดที่คุณสามารถทำได้คือการประกันตัวหรือเพียงแค่พยายามมอบหมายมัน แต่อย่างใด

เมื่อต้องการใช้malloc()ฉันใช้ globals และกำหนดค่าเป็นศูนย์ หากตัวชี้ไม่ใช่ NULL malloc()ฉันเป็นอิสระก่อนที่จะใช้งาน

คุณสามารถใช้realloc()เป็นกรณีทั่วไปหากต้องการคัดลอกข้อมูลที่มีอยู่ คุณต้องตรวจสอบตัวชี้ก่อนที่จะออกไปทำงานถ้าคุณจะคัดลอกหรือ concatenate realloc()หลังจากที่

3.2.5.2 ข้อดีของการจัดสรร


4
ที่จริงแล้วสเป็คของ alloca ไม่ได้บอกว่ามันส่งคืนตัวชี้ที่ไม่ถูกต้องเมื่อล้มเหลว (stack overflow) มันบอกว่ามันมีพฤติกรรมที่ไม่ได้กำหนด ... และสำหรับ malloc มันบอกว่ามันคืน NULL ไม่ใช่ตัวชี้ที่ไม่ถูกต้องแบบสุ่ม ไร้ประโยชน์)
kriss

@kriss Linux อาจฆ่ากระบวนการของคุณ แต่อย่างน้อยก็ไม่ได้ทำให้เกิดพฤติกรรมที่ไม่ได้กำหนด
craig65535

@ craig65535: การแสดงออกที่ไม่ได้กำหนดพฤติกรรมมักจะหมายความว่าพฤติกรรมที่ไม่ได้กำหนดไว้โดยข้อกำหนด C หรือ C ++ ไม่ว่าในลักษณะใดก็ตามมันจะสุ่มหรือไม่เสถียรใน OS หรือคอมไพเลอร์ใด ๆ ก็ตาม ดังนั้นจึงไม่มีความหมายเลยที่จะเชื่อมโยง UB กับชื่อของระบบปฏิบัติการเช่น "Linux" หรือ "Windows" มันไม่มีอะไรเกี่ยวข้องกับมัน
kriss

ฉันพยายามที่จะบอกว่า malloc ที่คืนค่า NULL หรือในกรณีของ Linux การเข้าถึงหน่วยความจำที่ทำให้กระบวนการของคุณดีกว่าพฤติกรรมที่ไม่ได้กำหนดของ alloca ฉันคิดว่าฉันต้องอ่านความคิดเห็นแรกของคุณผิด
craig65535

3

กระบวนการมีเพียงจำนวน จำกัด ของพื้นที่สแต็คที่มีอยู่ - malloc()ไกลน้อยกว่าจำนวนหน่วยความจำที่มีอยู่เพื่อ

ด้วยการใช้alloca()คุณจะเพิ่มโอกาสในการได้รับข้อผิดพลาด Stack Overflow อย่างมาก (หากคุณโชคดีหรือเกิดข้อผิดพลาดที่อธิบายไม่ได้หากคุณไม่ได้)


ขึ้นอยู่กับแอพพลิเคชั่นเป็นอย่างมาก ไม่ใช่เรื่องผิดปกติสำหรับแอปพลิเคชั่นที่ จำกัด หน่วยความจำที่จะมีขนาดสแต็กที่ใหญ่กว่าฮีป (หากมีแม้กระทั่ง IS heap)
EBlake

3

ไม่สวยมาก แต่ถ้าประสิทธิภาพมีความสำคัญคุณสามารถจัดสรรพื้นที่บนสแต็กล่วงหน้าได้

ถ้าตอนนี้คุณมีขนาดสูงสุดของหน่วยความจำที่บล็อกความต้องการของคุณและคุณต้องการที่จะเก็บการตรวจสอบมากเกินไปคุณสามารถทำสิ่งที่ชอบ:

void f()
{
    char array_on_stack[ MAX_BYTES_TO_ALLOCATE ];
    SomeType *p = (SomeType *)array;

    (...)
}

12
char char รับประกันว่าจะจัดตำแหน่งอย่างถูกต้องสำหรับชนิดข้อมูลใด ๆ หรือไม่? alloca ให้สัญญาดังกล่าว
Juho Östman

@ JuhoÖstman: คุณสามารถใช้อาร์เรย์ของ struct (หรือชนิดใดก็ได้) แทนที่จะเป็นแบบ char หากคุณมีปัญหาเกี่ยวกับการจัดตำแหน่ง
kriss

ที่เรียกว่าความยาวอาร์เรย์ตัวแปร รองรับใน C90 ขึ้นไป แต่ไม่ใช่ C ++ ดูฉันสามารถใช้ C Variable Length Array ใน C ++ 03 และ C ++ 11 ได้หรือไม่
jww

3

ฟังก์ชั่นของ alloca นั้นยอดเยี่ยมและผู้ที่ naysayers ทุกคนกำลังแพร่กระจาย FUD

void foo()
{
    int x = 50000; 
    char array[x];
    char *parray = (char *)alloca(x);
}

Array and Parray นั้นเหมือนกันทุกประการและมีความเสี่ยงเหมือนกันทุกประการ การพูดอย่างใดอย่างหนึ่งดีกว่าตัวเลือกอื่นเป็นตัวเลือกประโยคไม่ใช่ทางเทคนิค

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

ทำไมสิ่งนี้ถึงไม่ดี?


3

ที่จริงแล้ว alloca ไม่รับประกันว่าจะใช้สแต็ก แท้จริงแล้วการใช้ gcc-2.95 ของจัดสรรจัดสรรหน่วยความจำจากฮีปโดยใช้ malloc เอง นอกจากนี้การติดตั้งใช้งานนั้นอาจนำไปสู่การรั่วไหลของหน่วยความจำและพฤติกรรมที่ไม่คาดคิดหากคุณเรียกมันว่าภายในบล็อกที่มีการใช้งานข้ามไปอีก ไม่ใช่เพื่อบอกว่าคุณไม่ควรใช้มัน แต่บางครั้งก็ทำให้ค่าใช้จ่ายมากกว่าที่จะปลดปล่อย


มันฟังดูราวกับว่า GCC-2.95 allocaยากจนจัดสรรและอาจจะไม่สามารถใช้งานได้อย่างปลอดภัยสำหรับโปรแกรมที่จำเป็นต้องมี วิธีก็จะมีการทำความสะอาดหน่วยความจำเมื่อlongjmpจะใช้ในการละทิ้งเฟรมที่ไม่alloca? เมื่อไหร่ที่ทุกคนจะใช้ gcc 2.95 วันนี้?
Kaz

2

IMHO ถือเป็นการกระทำที่ไม่ดีเพราะทุกคนกลัวที่จะ จำกัด ขนาดสแต็ก

ฉันได้เรียนรู้มากมายจากการอ่านกระทู้นี้และลิงก์อื่น ๆ :

ฉันใช้ alloca เป็นหลักในการทำให้ไฟล์ C ธรรมดาของฉันสามารถคอมไพล์ได้ใน msvc และ gcc โดยไม่มีการเปลี่ยนแปลงสไตล์ C89 ไม่มี #ifdef _MSC_VER ฯลฯ

ขอบคุณ ! กระทู้นี้ทำให้ฉันสมัครใช้งานเว็บไซต์นี้ :)


โปรดทราบว่าไม่มีสิ่งเช่น "เธรด" ในไซต์นี้ Stack Overflow มีรูปแบบคำถามและคำตอบไม่ใช่รูปแบบเธรดการสนทนา "คำตอบ" ไม่เหมือนกับ "ตอบกลับ" ในฟอรัมการสนทนา หมายความว่าคุณให้คำตอบกับคำถามจริง ๆ และไม่ควรใช้เพื่อตอบคำถามหรือแสดงความคิดเห็นอื่น ๆ ในหัวข้อ เมื่อคุณมีตัวแทนอย่างน้อย 50 คนคุณสามารถโพสต์ความคิดเห็นแต่ต้องอ่าน " ฉันไม่ควรแสดงความคิดเห็นเมื่อใด" มาตรา. โปรดอ่านหน้าเกี่ยวกับเพื่อทำความเข้าใจกับรูปแบบของเว็บไซต์ให้ดียิ่งขึ้น
Adi Inbar

1

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

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

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

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

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

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


1

คำตอบส่วนใหญ่ที่นี่พลาดจุด: มีเหตุผลทำไมใช้ _alloca()อาจเลวร้ายยิ่งกว่าเพียงการจัดเก็บวัตถุขนาดใหญ่ในกอง

ความแตกต่างที่สำคัญระหว่างการจัดเก็บอัตโนมัติและ_alloca()ที่หลังประสบปัญหาเพิ่มเติม (ร้ายแรง): บล็อกที่ปันส่วนไม่ได้ถูกควบคุมโดยคอมไพเลอร์ดังนั้นจึงไม่มีวิธีใดที่คอมไพเลอร์จะปรับหรือรีไซเคิลให้เหมาะสม

เปรียบเทียบ:

while (condition) {
    char buffer[0x100]; // Chill.
    /* ... */
}

ด้วย:

while (condition) {
    char* buffer = _alloca(0x100); // Bad!
    /* ... */
}

ปัญหาเกี่ยวกับหลังควรจะชัดเจน


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

มีกรณีการใช้งานสำหรับกรณีที่สองซึ่งกรณีแรกไม่รองรับ ฉันอาจต้องการมีระเบียน 'n' หลังจากวนรอบเสร็จแล้วรัน 'n' ครั้ง - อาจอยู่ในรายการหรือต้นไม้ที่เชื่อมโยง โครงสร้างข้อมูลนี้จะถูกกำจัดเมื่อฟังก์ชั่นส่งกลับในที่สุด ซึ่งไม่ได้ที่จะบอกว่าฉันจะรหัสอะไรที่ทาง :-)
greggo

1
และฉันจะบอกว่า "คอมไพเลอร์ไม่สามารถควบคุมได้" เป็นเพราะนั่นคือวิธีที่กำหนด alloca (); คอมไพเลอร์สมัยใหม่รู้ว่า alloca คืออะไรและจัดการมันเป็นพิเศษ มันไม่ใช่แค่ฟังก์ชั่นห้องสมุดเหมือนในยุค 80 C99 VLA นั้นโดยทั่วไปแล้วมีขอบเขตบล็อก (และการพิมพ์ที่ดีขึ้น) ไม่ควบคุมมากหรือน้อยเพียงแค่ปรับให้เข้ากับความหมายที่แตกต่างกัน
greggo

@greggo: หากคุณเป็นผู้ลงมือฉันยินดีที่จะได้ยินว่าทำไมคุณคิดว่าคำตอบของฉันไม่เป็นประโยชน์
alecov

ใน C การรีไซเคิลไม่ใช่งานของคอมไพเลอร์ แต่เป็นงานของ c library (free ()) alloca () เป็นอิสระในการกลับมา
peterh - Reinstate Monica

1

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

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

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