ฉันกำลังเตรียมเอกสารการฝึกอบรมบางอย่างในภาษา C และฉันต้องการให้ตัวอย่างของฉันพอดีกับแบบจำลองกองซ้อนทั่วไป
C stack เติบโตไปในทิศทางใดใน Linux, Windows, Mac OSX (PPC และ x86), Solaris และ Unixes ล่าสุด
ฉันกำลังเตรียมเอกสารการฝึกอบรมบางอย่างในภาษา C และฉันต้องการให้ตัวอย่างของฉันพอดีกับแบบจำลองกองซ้อนทั่วไป
C stack เติบโตไปในทิศทางใดใน Linux, Windows, Mac OSX (PPC และ x86), Solaris และ Unixes ล่าสุด
คำตอบ:
การเติบโตของสแต็คไม่ได้ขึ้นอยู่กับระบบปฏิบัติการ แต่ขึ้นอยู่กับโปรเซสเซอร์ที่ทำงานอยู่ ตัวอย่างเช่น Solaris ทำงานบน x86 และ SPARC Mac OSX (ตามที่คุณกล่าวถึง) ทำงานบน PPC และ x86 ลินุกซ์ทำงานบนทุกอย่างจาก Honkin ใหญ่ของฉัน System z ที่ทำงานกับนาฬิกาข้อมืออ่อนแอเล็ก ๆ น้อย ๆ
หากซีพียูมีตัวเลือกแบบใดแบบหนึ่งรูปแบบ ABI / การโทรที่ใช้โดยระบบปฏิบัติการจะระบุว่าคุณต้องเลือกตัวเลือกใดหากคุณต้องการให้รหัสของคุณเรียกรหัสของคนอื่น
โปรเซสเซอร์และทิศทางคือ:
แสดงอายุของฉันในช่วงไม่กี่คนที่ผ่านมา 1802 เป็นชิปที่ใช้ในการควบคุมรถรับส่งในยุคแรก ๆ (เมื่อตรวจสอบว่าประตูเปิดอยู่ฉันสงสัยว่าขึ้นอยู่กับพลังการประมวลผลที่มี :-) และคอมพิวเตอร์เครื่องที่สองของฉันCOMX-35 ( ตามZX80ของฉัน)
PDP11 รายละเอียดที่รวบรวมได้จากที่นี่ , 8051 รายละเอียดจากที่นี่
สถาปัตยกรรม SPARC ใช้โมเดลการลงทะเบียนหน้าต่างบานเลื่อน รายละเอียดที่มองเห็นได้ทางสถาปัตยกรรมยังรวมถึงบัฟเฟอร์วงกลมของหน้าต่างรีจิสเตอร์ที่ถูกต้องและถูกแคชไว้ภายในพร้อมกับดักเมื่อล้น / ต่ำเกินไป ดูรายละเอียดได้ที่นี่ ตามที่คู่มือ SPARCv8 อธิบายคำแนะนำ SAVE และ RESTORE เป็นเหมือนคำแนะนำ ADD บวกกับการหมุนหน้าต่างลงทะเบียน การใช้ค่าคงที่ที่เป็นบวกแทนที่จะเป็นค่าลบตามปกติจะทำให้กองซ้อนเติบโตขึ้น
เทคนิค SCRT ที่กล่าวถึงข้างต้นเป็นอีกเทคนิคหนึ่ง - 1802 ใช้การลงทะเบียน 16 บิต 16 บิตหรือ 16 บิตสำหรับ SCRT (เทคนิคการโทรและการส่งคืนมาตรฐาน) หนึ่งคือตัวนับโปรแกรมคุณสามารถใช้รีจิสเตอร์เป็นพีซีพร้อมSEP Rn
คำสั่ง หนึ่งคือตัวชี้สแต็กและสองตัวถูกตั้งค่าให้ชี้ไปที่ที่อยู่รหัส SCRT เสมอหนึ่งตัวสำหรับการโทรหนึ่งตัวสำหรับการส่งคืน ไม่มีการลงทะเบียนในลักษณะพิเศษ โปรดทราบว่ารายละเอียดเหล่านี้มาจากหน่วยความจำอาจไม่ถูกต้องทั้งหมด
ตัวอย่างเช่นถ้า R3 เป็นพีซี R4 คือที่อยู่การโทร SCRT R5 คือที่อยู่สำหรับส่งคืน SCRT และ R2 คือ "สแต็ก" (เครื่องหมายคำพูดที่นำไปใช้ในซอฟต์แวร์) SEP R4
จะตั้งค่า R4 ให้เป็นพีซีและเริ่มเรียกใช้ SCRT รหัสโทร.
จากนั้นจะจัดเก็บ R3 บน R2 "สแต็ก" (ฉันคิดว่า R6 ใช้สำหรับการจัดเก็บชั่วคราว) ปรับขึ้นหรือลงจับสองไบต์ตาม R3 โหลดลงใน R3 จากนั้นทำSEP R3
และรันที่ที่อยู่ใหม่
ในการส่งคืนมันจะSEP R5
ดึงที่อยู่เก่าออกจากสแต็ก R2 เพิ่มสองที่อยู่ (เพื่อข้ามไบต์ที่อยู่ของการโทร) โหลดลงใน R3 และSEP R3
เริ่มรันโค้ดก่อนหน้า
ยากมากที่จะห่อหัวของคุณไปรอบ ๆ ในตอนแรกหลังจากรหัสสแต็ก 6502/6809 / z80 ทั้งหมด แต่ยังคงสง่างามในแบบที่หัวชนกับผนัง นอกจากนี้หนึ่งในคุณสมบัติการขายที่ยิ่งใหญ่ของชิปคือชุดการลงทะเบียน 16 16 บิตเต็มรูปแบบแม้ว่าคุณจะสูญเสีย 7 ในทันที (5 สำหรับ SCRT สองตัวสำหรับ DMA และการขัดจังหวะจากหน่วยความจำ) อ่าชัยชนะของการตลาดเหนือความเป็นจริง :-)
จริงๆแล้วระบบ z นั้นค่อนข้างคล้ายกันโดยใช้การลงทะเบียน R14 และ R15 สำหรับการโทร / ส่งคืน
ใน C ++ (ปรับให้เข้ากับ C) stack.cc :
static int
find_stack_direction ()
{
static char *addr = 0;
auto char dummy;
if (addr == 0)
{
addr = &dummy;
return find_stack_direction ();
}
else
{
return ((&dummy > addr) ? 1 : -1);
}
}
static
สำหรับสิ่งนี้ แต่คุณสามารถส่งที่อยู่เป็นอาร์กิวเมนต์ไปยังการโทรซ้ำได้
static
ถ้าคุณโทรมากกว่าหนึ่งครั้งการโทรที่ตามมาอาจล้มเหลว ...
ข้อได้เปรียบของการเติบโตขึ้นคือในระบบเก่าโดยทั่วไปสแต็กจะอยู่ที่ด้านบนสุดของหน่วยความจำ โดยทั่วไปแล้วโปรแกรมจะเติมหน่วยความจำโดยเริ่มจากด้านล่างดังนั้นการจัดการหน่วยความจำประเภทนี้จึงลดความจำเป็นในการวัดและวางด้านล่างของสแต็กไว้ในที่ที่เหมาะสม
สแต็คขยายตัวลงบน x86 (กำหนดโดยสถาปัตยกรรมตัวชี้สแต็กที่เพิ่มขึ้นป๊อปการลดการกด)
ใน MIPS และสถาปัตยกรรม RISCสมัยใหม่จำนวนมาก(เช่น PowerPC, RISC-V, SPARC ... ) ไม่มีpush
และpop
คำแนะนำ การดำเนินการเหล่านั้นทำได้อย่างชัดเจนโดยการปรับตัวชี้สแต็กด้วยตนเองจากนั้นโหลด / จัดเก็บค่าตามตัวชี้ที่ปรับแล้ว การลงทะเบียนทั้งหมด (ยกเว้นการลงทะเบียนศูนย์) เป็นจุดประสงค์ทั่วไปดังนั้นในทางทฤษฎีรีจิสเตอร์ใด ๆก็สามารถเป็นตัวชี้สแต็กได้และสแต็กสามารถเติบโตไปในทิศทางใดก็ได้ที่โปรแกรมเมอร์ต้องการ
ที่กล่าวว่าสแต็กมักจะเติบโตขึ้นตามสถาปัตยกรรมส่วนใหญ่อาจจะเพื่อหลีกเลี่ยงกรณีที่ข้อมูลสแต็กและโปรแกรมหรือข้อมูลฮีปเติบโตขึ้นและปะทะกัน นอกจากนี้ยังมีดีอยู่จากเหตุผลดังกล่าวตอบ SH-ของ ตัวอย่างบางส่วน: MIPS ABI ลดลงและใช้$29
(AKA $sp
) เป็นตัวชี้สแต็ก RISC-V ABI จะขยายลงด้านล่างและใช้ x2 เป็นตัวชี้สแต็ก
ใน Intel 8051 สแต็กเติบโตขึ้นอาจเป็นเพราะพื้นที่หน่วยความจำมีขนาดเล็กมาก (128 ไบต์ในเวอร์ชันดั้งเดิม) ซึ่งไม่มีฮีปและคุณไม่จำเป็นต้องวางสแต็กไว้ด้านบนเพื่อที่จะแยกออกจากฮีปที่เพิ่มขึ้น จากด้านล่าง
คุณสามารถค้นหาข้อมูลเพิ่มเติมเกี่ยวกับการใช้สแต็กในสถาปัตยกรรมต่างๆได้ในhttps://en.wikipedia.org/wiki/Calling_convention
ดูสิ่งนี้ด้วย
เพียงเล็กน้อยสำหรับคำตอบอื่น ๆ ซึ่งเท่าที่ฉันเห็นยังไม่ได้แตะจุดนี้:
การมีสแต็กเติบโตลงทำให้ที่อยู่ทั้งหมดภายในสแต็กมีค่าชดเชยที่เป็นบวกเมื่อเทียบกับตัวชี้สแต็ก ไม่จำเป็นต้องมีการชดเชยเชิงลบเนื่องจากจะชี้ไปที่พื้นที่สแต็กที่ไม่ได้ใช้เท่านั้น สิ่งนี้ช่วยลดความยุ่งยากในการเข้าถึงตำแหน่งสแต็กเมื่อโปรเซสเซอร์รองรับการกำหนดแอดเดรสแบบสแต็กพอยน์เตอร์
โปรเซสเซอร์หลายตัวมีคำแนะนำที่อนุญาตให้เข้าถึงด้วยค่าออฟเซ็ตบวกอย่างเดียวที่สัมพันธ์กับรีจิสเตอร์บางตัว ซึ่งรวมถึงสถาปัตยกรรมสมัยใหม่จำนวนมากและสถาปัตยกรรมเก่าแก่บางส่วน ตัวอย่างเช่น ARM Thumb ABI จัดเตรียมการเข้าถึงแบบสัมพันธ์กับสแต็กพอยน์เตอร์ด้วยออฟเซ็ตบวกที่เข้ารหัสภายในคำสั่ง 16 บิตเดียว
หากสแต็กโตขึ้นการชดเชยที่เป็นประโยชน์ทั้งหมดเมื่อเทียบกับสแต็กพอยน์เตอร์จะเป็นลบซึ่งใช้งานง่ายน้อยกว่าและสะดวกน้อยกว่า นอกจากนี้ยังขัดแย้งกับแอปพลิเคชันอื่น ๆ ของการระบุแอดเดรสที่สัมพันธ์กับการลงทะเบียนเช่นสำหรับการเข้าถึงฟิลด์ของโครงสร้าง
ในระบบส่วนใหญ่สแต็กจะเติบโตลดลงและบทความของฉันที่https://gist.github.com/cpq/8598782อธิบายว่าทำไมมันถึงเติบโตลง เป็นเรื่องง่าย: จะจัดวางบล็อกหน่วยความจำที่เพิ่มขึ้นสองบล็อก (ฮีปและสแต็ก) ในหน่วยความจำคงที่ได้อย่างไร? ทางออกที่ดีที่สุดคือวางไว้ที่ด้านตรงข้ามและปล่อยให้เติบโตเข้าหากัน
มันเติบโตลงเนื่องจากหน่วยความจำที่จัดสรรให้กับโปรแกรมมีรหัส "ข้อมูลถาวร" สำหรับโปรแกรมเองที่ด้านล่างจากนั้นฮีปที่อยู่ตรงกลาง คุณต้องการจุดคงที่อีกจุดเพื่ออ้างอิงสแต็กเพื่อที่จะทำให้คุณอยู่ในจุดสูงสุด ซึ่งหมายความว่าสแต็กจะขยายตัวลดลงจนกว่าจะอยู่ติดกับวัตถุบนฮีป
มาโครนี้ควรตรวจพบที่รันไทม์โดยไม่มี UB:
#define stk_grows_up_eh() stk_grows_up__(&(char){0})
_Bool stk_grows_up__(char *ParentsLocal);
__attribute((__noinline__))
_Bool stk_grows_up__(char *ParentsLocal) {
return (uintptr_t)ParentsLocal < (uintptr_t)&ParentsLocal;
}