“ หน่วยความจำที่จัดสรรในเวลารวบรวม” หมายความว่าอย่างไร?


159

ในการเขียนโปรแกรมภาษาเช่น C และ C ++ ผู้คนมักจะอ้างถึงการจัดสรรหน่วยความจำแบบคงที่และแบบไดนามิก ฉันเข้าใจแนวคิด แต่วลี "หน่วยความจำทั้งหมดถูกจัดสรร (สงวนไว้) ในช่วงเวลารวบรวม" ทำให้ฉันสับสนอยู่เสมอ

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

การจัดสรรหน่วยความจำตามคำนิยามไม่ใช่แนวคิดของรันไทม์ใช่หรือไม่

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

นี่เป็นหนึ่งในหน้าที่ใช้วลีภายใต้หัวข้อ "การจัดสรรแบบคงที่"

กลับไปสู่พื้นฐาน: การจัดสรรหน่วยความจำประวัติความเป็นมา


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

คำตอบ:


184

หน่วยความจำที่จัดสรรในเวลาคอมไพล์หมายถึงคอมไพเลอร์แก้ไข ณ เวลารวบรวมซึ่งบางสิ่งจะถูกจัดสรรภายในแผนที่หน่วยความจำกระบวนการ

ตัวอย่างเช่นพิจารณาอาร์เรย์ทั่วโลก:

int array[100];

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

แน่นอนว่าที่อยู่หน่วยความจำคือที่อยู่เสมือน โปรแกรมถือว่ามีพื้นที่หน่วยความจำทั้งหมดของตัวเอง (ตัวอย่างเช่นจาก 0x00000000 ถึง 0xFFFFFFFF) นั่นเป็นสาเหตุที่คอมไพเลอร์สามารถตั้งสมมติฐานเช่น "โอเคอาร์เรย์จะอยู่ที่ 0x00A33211" ณ รันไทม์ที่แอดเดรสถูกแปลเป็นแอดเดรสจริง / ฮาร์ดแวร์โดย MMU และ OS

ค่าเริ่มต้นสิ่งที่เก็บแบบคงที่แตกต่างกันเล็กน้อย ตัวอย่างเช่น:

int array[] = { 1 , 2 , 3 , 4 };

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

นี่คือสองตัวอย่างของชุดประกอบที่สร้างขึ้นโดยคอมไพเลอร์ (GCC4.8.1 พร้อมเป้าหมาย x86):

รหัส C ++:

int a[4];
int b[] = { 1 , 2 , 3 , 4 };

int main()
{}

การชุมนุมเอาท์พุท:

a:
    .zero   16
b:
    .long   1
    .long   2
    .long   3
    .long   4
main:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $0, %eax
    popq    %rbp
    ret

อย่างที่คุณเห็นค่าต่างๆจะถูกฉีดเข้าไปในชุดประกอบโดยตรง ในอาร์เรย์aคอมไพเลอร์สร้างการเริ่มต้นเป็นศูนย์ของ 16 ไบต์เนื่องจากมาตรฐานบอกว่าสิ่งที่เก็บแบบคงที่ควรเริ่มต้นเป็นศูนย์โดยค่าเริ่มต้น:

8.5.9 (Initializers) [หมายเหตุ]:
วัตถุทุกอันของระยะเวลาการจัดเก็บแบบสแตติกจะเริ่มต้นที่ศูนย์เมื่อเริ่มต้นโปรแกรมก่อนที่จะมีการเริ่มต้นอื่น ๆ ในบางกรณีการเตรียมใช้งานเพิ่มเติมเสร็จในภายหลัง

ฉันมักจะแนะนำให้คนถอดรหัสของพวกเขาเพื่อดูว่าคอมไพเลอร์ทำอะไรกับรหัส C ++ สิ่งนี้ใช้จากคลาส / ระยะเวลาการเก็บข้อมูล (เช่นคำถามนี้) กับการปรับแต่งคอมไพเลอร์ขั้นสูง คุณสามารถสั่งคอมไพเลอร์ของคุณเพื่อสร้างแอสเซมบลี แต่มีเครื่องมือที่ยอดเยี่ยมในการทำเช่นนี้บนอินเทอร์เน็ตในลักษณะที่เป็นมิตร ที่ฉันชอบคือGCC Explorer ที่


2
ขอบคุณ ชี้แจงนี้มาก คอมไพเลอร์เอาท์พุทสิ่งที่เทียบเท่ากับ "จองหน่วยความจำจาก 0xABC จนถึง 0xXYZ สำหรับตัวแปรอาร์เรย์ [] ฯลฯ " แล้วตัวโหลดจะใช้การจัดสรรจริงๆก่อนที่มันจะรันโปรแกรม?
Talha Sayed

1
@TalhaSayed แน่นอน ดูการแก้ไขเพื่อดูตัวอย่าง
Manu343726

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

2
@Secko ใช่ mmm "created" เป็นคำที่ดีกว่าที่ฉันคิด
Manu343726

2
"มันถูกจัดสรรในพื้นที่ mamory แบบคงที่ของพื้นที่หน่วยความจำกระบวนการ" การอ่านที่จัดสรรพื้นที่เต้านมแบบคงที่ในพื้นที่หน่วยความจำกระบวนการของฉัน
Radiodef

27

หน่วยความจำที่จัดสรร ณ เวลาคอมไพล์หมายความว่าจะไม่มีการจัดสรรเพิ่มเติม ณ เวลารัน - ไม่มีการเรียกไปยัง malloc, ใหม่หรือวิธีการจัดสรรแบบไดนามิกอื่น ๆ คุณจะมีการใช้หน่วยความจำคงที่แม้ว่าคุณจะไม่ต้องการหน่วยความจำทั้งหมดตลอดเวลาก็ตาม

การจัดสรรหน่วยความจำตามคำนิยามไม่ใช่แนวคิดของรันไทม์ใช่หรือไม่

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

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

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


1
ถ้าฉันเขียนstatic int i[4] = {2 , 3 , 5 ,5 }มันจะเพิ่มขึ้นตามขนาดที่ใช้งานได้ 16 ไบต์ คุณกล่าวว่า "เพียงแค่ประกาศคงที่จะไม่เพิ่มขนาดของปฏิบัติการของคุณมากกว่าสองสามไบต์ประกาศด้วยค่าเริ่มต้นที่ไม่เป็นศูนย์จะ" การประกาศด้วยค่าเริ่มต้นจะหมายถึงอะไร
Suraj Jain

ปฏิบัติการของคุณมีสองส่วนสำหรับข้อมูลคงที่ - หนึ่งสำหรับสถิตที่ไม่ได้กำหนดค่าเริ่มต้นและอีกหนึ่งสำหรับสถิตเริ่มต้น พื้นที่ที่ไม่ได้กำหนดค่าเริ่มต้นเป็นเพียงตัวบ่งชี้ขนาดเท่านั้น เมื่อเรียกใช้โปรแกรมของคุณขนาดนั้นจะถูกใช้เพื่อขยายพื้นที่เก็บข้อมูลแบบสแตติก แต่ตัวโปรแกรมเองไม่จำเป็นต้องเก็บอะไรไว้มากกว่าปริมาณข้อมูลที่ไม่ได้เตรียมใช้ สำหรับสถิตศาสตร์เริ่มต้นโปรแกรมของคุณต้องมีขนาดไม่คงที่เท่านั้น (แต่ละอัน) แต่ยังคงขนาดที่ได้รับการเตรียมใช้งาน ดังนั้นในตัวอย่างของคุณโปรแกรมของคุณจะมี 2, 3, 5 และ 5 อยู่ในนั้น
mah

มันใช้งานได้ถูกกำหนดว่ามันจะถูกวางไว้ที่ไหน / มันถูกจัดสรรอย่างไร แต่ฉันไม่แน่ใจว่าฉันเข้าใจถึงความจำเป็นในการรู้หรือไม่
mah

23

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

char a[32];
char b;
char c;

ตัวแปร 3 ตัวนั้นคือ "จัดสรร ณ เวลารวบรวม" หมายความว่าคอมไพเลอร์คำนวณขนาดของมัน (ซึ่งได้รับการแก้ไข) ณ เวลารวบรวม ตัวแปรaจะเป็นออฟเซ็ตในหน่วยความจำสมมติว่าชี้ไปที่ที่อยู่ 0 bจะชี้ไปที่ที่อยู่ที่ 33 และcที่ 34 (หากไม่มีการปรับการจัดตำแหน่งให้เหมาะสม) ดังนั้นการจัดสรรข้อมูลแบบสแตติก 1Kb จะไม่เพิ่มขนาดรหัสของคุณเนื่องจากมันจะเปลี่ยนการชดเชยภายใน พื้นที่จริงจะได้รับการจัดสรรเวลาในการโหลด

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

จำได้ว่าเราจะพูดคุยเกี่ยวกับที่อยู่ญาติ ที่อยู่จริงที่ตัวแปรจะอยู่จะแตกต่างกัน ณ เวลาโหลดเคอร์เนลจะสงวนหน่วยความจำบางส่วนสำหรับกระบวนการให้พูดที่ที่อยู่xและที่อยู่ที่กำหนดรหัสฮาร์ดทั้งหมดที่อยู่ในไฟล์เรียกทำงานจะเพิ่มขึ้นทีละxไบต์ดังนั้นตัวแปรaในตัวอย่างจะเป็นที่อยู่x, b ที่ที่อยู่x+33และ เป็นต้น


17

การเพิ่มตัวแปรในสแต็กที่ใช้ N ไบต์ไม่ได้ (จำเป็น) เพิ่มขนาดถังขยะเป็น N ไบต์ ในความเป็นจริงมันจะเพิ่ม แต่ไม่กี่ไบต์ส่วนใหญ่เวลา
มาเริ่มด้วยตัวอย่างของการเพิ่ม 1,000 chars ลงในโค้ดของคุณจะเพิ่มขนาดของ bin ในลักษณะเชิงเส้น

ถ้า 1k เป็นสตริงหนึ่งพัน chars ซึ่งถูกประกาศเช่นนั้น

const char *c_string = "Here goes a thousand chars...999";//implicit \0 at end

และจากนั้นvim your_compiled_binคุณต้องคุณจะสามารถเห็นสตริงนั้นในถังขยะที่ใดที่หนึ่ง ในกรณีนั้นใช่: ไฟล์ที่เรียกทำงานจะใหญ่กว่า 1 k เนื่องจากมีสตริงเต็ม
อย่างไรก็ตามหากคุณจัดสรรอาเรย์ของints charหรือlongs บนสแต็กและกำหนดในลูปจะมีบางสิ่งที่อยู่ในบรรทัดเหล่านี้

int big_arr[1000];
for (int i=0;i<1000;++i) big_arr[i] = some_computation_func(i);

จากนั้นไม่ใช่: มันจะไม่เพิ่มถังขยะ ... โดย1000*sizeof(int)
การจัดสรรในเวลารวบรวมหมายถึงสิ่งที่คุณเข้าใจในตอนนี้หมายความว่า (ตามความคิดเห็นของคุณ): ถังขยะที่รวบรวมมีข้อมูลที่ระบบต้องการทราบจำนวนหน่วยความจำ จำเป็นต้องใช้ฟังก์ชั่น / บล็อกใดเมื่อมีการดำเนินการพร้อมกับข้อมูลเกี่ยวกับขนาดสแต็กที่แอปพลิเคชันของคุณต้องการ นั่นคือสิ่งที่ระบบจะจัดสรรเมื่อดำเนินการ bin ของคุณและโปรแกรมของคุณกลายเป็นกระบวนการ (ดีการดำเนินการ bin ของคุณเป็นกระบวนการที่ ... ดีคุณจะได้รับสิ่งที่ฉันพูด)
แน่นอนว่าฉันไม่ได้วาดภาพเต็มได้ที่นี่: ถังขยะมีข้อมูลเกี่ยวกับจำนวนถังขยะที่จะต้องใช้จริง ๆ จากข้อมูลนี้ (เหนือสิ่งอื่นใด) ระบบจะจองหน่วยความจำอันหนึ่งเรียกว่าสแต็กซึ่งโปรแกรมได้รับการครอบครองแบบอิสระ หน่วยความจำสแต็คยังคงถูกจัดสรรโดยระบบเมื่อเริ่มต้นกระบวนการ (ผลลัพธ์ของถังขยะของคุณที่ถูกเรียกใช้งาน) กระบวนการจัดการหน่วยความจำสแต็กสำหรับคุณแล้ว เมื่อมีฟังก์ชั่นหรือห่วง (ชนิดของบล็อกใด ๆ ) จะเรียก / ได้รับการดำเนินตัวแปรท้องถิ่นเพื่อป้องกันที่จะผลักดันไปยังกองและพวกเขาจะถูกลบออก (หน่วยความจำสแต็คเป็น"อิสระ"เพื่อที่จะพูด) เพื่อนำมาใช้โดยอื่น ๆ ฟังก์ชั่น / บล็อก ดังนั้นการประกาศint some_array[100]จะเพิ่มข้อมูลเพิ่มเติมลงในถังขยะเพียงไม่กี่ไบต์ซึ่งจะบอกระบบว่าฟังก์ชัน X จะต้องใช้100*sizeof(int)+ เพิ่มพื้นที่เก็บหนังสือบางส่วน


ขอบคุณมาก. คำถามอีกข้อหนึ่ง, ตัวแปรโลคัลสำหรับฟังก์ชั่นยังได้รับการจัดสรรแบบเดียวกันในช่วงเวลารวบรวม?
Talha Sayed

@ TalhaSayed: ใช่นั่นคือสิ่งที่ฉันหมายถึงเมื่อฉันพูดว่า: "ข้อมูลที่ระบบต้องการทราบจำนวนหน่วยความจำที่ฟังก์ชัน / บล็อกจะต้องมี" เมื่อคุณเรียกใช้ฟังก์ชันระบบจะจัดสรรหน่วยความจำที่จำเป็นสำหรับฟังก์ชันนั้น ขณะที่ฟังก์ชันส่งคืนหน่วยความจำนั้นจะถูกปลดปล่อยอีกครั้ง
Elias Van Ootegem

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

@ phant0m: ฉันไม่เคยบอกว่าสตริงถูกจัดสรรบนสแต็กมีเพียงตัวชี้เกินไปมันจะสตริงตัวเองจะอยู่ในหน่วยความจำแบบอ่านอย่างเดียว ฉันรู้ว่าหน่วยความจำที่เกี่ยวข้องกับตัวแปรท้องถิ่นไม่ได้รับการปลดปล่อยในแง่ของการfree()โทร แต่หน่วยความจำสแต็กที่พวกเขาใช้นั้นฟรีสำหรับการใช้งานโดยฟังก์ชั่นอื่น ๆ เมื่อฟังก์ชั่นที่ฉันแสดงรายการกลับมา ฉันลบรหัสออกเนื่องจากอาจทำให้เกิดความสับสนกับบางคน
Elias Van Ootegem

ฉันเข้าใจแล้ว ในกรณีนี้ใช้ความคิดเห็นของฉันเพื่อหมายถึง "ฉันสับสนกับถ้อยคำของคุณ"
phant0m

16

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

int a;
const int b[6] = {1,2,3,4,5,6};
char c[200];
const int d = 23;
int e[4] = {1,2,3,4};
int f;

มันจะบอก linker ว่าต้องการ 208 bytes สำหรับ bss, 16 bytes สำหรับ "data" และ 28 bytes สำหรับ "const" นอกจากนี้การอ้างอิงถึงตัวแปรใด ๆ จะถูกแทนที่ด้วยตัวเลือกพื้นที่และออฟเซ็ตดังนั้น a, b, c, d, และ e จะถูกแทนที่ด้วย bss + 0, const + 0, bss + 4, const + 24, data +0 หรือ bss + 204 ตามลำดับ

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

เมื่อโหลดโปรแกรมหนึ่งในสี่สิ่งจะเกิดขึ้นโดยทั่วไปขึ้นอยู่กับแพลตฟอร์ม:

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

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

  3. ระบบปฏิบัติการจะไม่จัดสรรหน่วยความจำใด ๆ ให้กับแอปพลิเคชั่นในตอนแรกยกเว้นสิ่งที่เก็บรหัสไบนารี่ แต่สิ่งแรกที่แอปพลิเคชันจะขอการจัดสรรที่เหมาะสมจากระบบปฏิบัติการซึ่งมันจะเก็บไว้ในทะเบียนตลอดไป

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

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


13

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

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

  • ระบบบางระบบใช้ในการตัดสินใจที่อยู่เสมือนที่จะจัดเก็บรายการ
  • ที่อยู่เสมือนถูกแมปกับที่อยู่ทางกายภาพ

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

  • คอมไพเลอร์เห็นไฟล์ต้นฉบับที่มีบรรทัดที่มีลักษณะดังนี้:

    int c;
  • สร้างเอาต์พุตสำหรับแอสเซมเบลอร์ที่สั่งให้สำรองหน่วยความจำสำหรับตัวแปร 'c' อาจมีลักษณะเช่นนี้:

    global _c
    section .bss
    _c: resb 4
  • เมื่อแอสเซมเบลอร์ทำงานเครื่องจะเก็บตัวนับเพื่อติดตามการปรับออฟเซ็ตของแต่ละรายการตั้งแต่เริ่มต้น 'ส่วน' (หรือ 'ส่วน') ของหน่วยความจำ นี่เป็นเหมือนส่วนของ 'struct' ที่มีขนาดใหญ่มากซึ่งมีทุกอย่างในไฟล์ทั้งหมดมันไม่ได้มีการจัดสรรหน่วยความจำจริงในขณะนี้และอาจเป็นที่ใดก็ได้ มันบันทึกในตารางที่_cมีการชดเชยเฉพาะ (พูด 510 ไบต์จากจุดเริ่มต้นของส่วน) แล้วเพิ่มเคาน์เตอร์ของมันด้วย 4 ดังนั้นตัวแปรดังกล่าวต่อไปจะอยู่ที่ (เช่น) 514 ไบต์ สำหรับรหัสใด ๆ ที่ต้องการที่อยู่_cเพียงแค่ใส่ 510 ในไฟล์เอาต์พุตและเพิ่มหมายเหตุว่าเอาต์พุตต้องการที่อยู่ของเซ็กเมนต์ที่มี_cการเพิ่มเข้าไปในภายหลัง

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

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


9

ไฟล์สั่งการอธิบายพื้นที่ที่จะจัดสรรสำหรับตัวแปรสแตติก การจัดสรรนี้กระทำโดยระบบเมื่อคุณเรียกใช้ไฟล์ปฏิบัติการ ดังนั้นตัวแปรสแตติก 1kB ของคุณจะไม่เพิ่มขนาดของไฟล์ปฏิบัติการด้วย 1kB:

static char[1024];

ยกเว้นกรณีที่คุณระบุ initializer:

static char[1024] = { 1, 2, 3, 4, ... };

ดังนั้นนอกเหนือจาก 'ภาษาเครื่อง' (เช่นคำสั่ง CPU) ปฏิบัติการที่มีคำอธิบายของรูปแบบหน่วยความจำที่จำเป็น


5

หน่วยความจำสามารถจัดสรรได้หลายวิธี:

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

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

แต่ในกรณีส่วนใหญ่คนเพียงแค่ต้องการที่จะบอกว่าจำนวนหน่วยความจำที่ถูกจัดสรรเป็นที่รู้จักกันที่รวบรวมเวลา

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


1
คำตอบนี้ทำให้สับสน (หรือสับสน) เมื่อพูดถึง "แอปพลิเคชันฮีป", "ฮีปของระบบปฏิบัติการ" และ "ฮีปของ GC" ราวกับว่านี่เป็นแนวคิดที่มีความหมายทั้งหมด ฉันอนุมานว่าด้วย # 1 คุณกำลังพยายามพูดว่าภาษาการเขียนโปรแกรมบางภาษาอาจ (สมมุติ) ใช้รูปแบบ "การจัดสรรฮีป" ที่จัดสรรหน่วยความจำออกจากบัฟเฟอร์ขนาดคงที่ในส่วนข้อมูล. แต่ดูเหมือนว่าไม่สมจริงพอที่จะเป็นอันตราย เพื่อความเข้าใจของ OP # 2 และ # 3 อีกครั้งการปรากฏตัวของ GC ไม่ได้เปลี่ยนแปลงอะไรเลย และอีกครั้ง # 5 คุณละเว้นค่อนข้างสำคัญมากขึ้นความแตกต่างระหว่างและ.data .bss
Quuxplusone

4

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


2

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

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


ฉันชอบคำตอบที่ใช้วิธีการแบบเสคราล ลองดูคำตอบที่ได้รับคะแนนสูงสุดเพื่อดูว่าคอมไพเลอร์สามารถ "จัดสรรหน่วยความจำ" โดยไม่ต้องสร้างคำสั่ง "รันไทม์" ได้อย่างไร (โปรดทราบว่า "คำแนะนำ" ในบริบทของภาษาแอสเซมบลีมีความหมายเฉพาะคือ opcodes ที่ปฏิบัติการได้คุณอาจใช้คำว่าเรียกขานเพื่อแปลความหมายบางอย่างเช่น "สูตร" แต่ในบริบทนี้ที่จะสับสน OP )
Quuxplusone

1
@Quuxplusone: ฉันอ่าน (และ upvoted) คำตอบนั้น และไม่คำตอบของฉันไม่ได้แก้ปัญหาของตัวแปรเริ่มต้นโดยเฉพาะ นอกจากนี้ยังไม่ได้แก้ไขรหัสการแก้ไขตัวเอง ในขณะที่คำตอบนั้นยอดเยี่ยม แต่ก็ไม่ได้พูดถึงสิ่งที่ฉันคิดว่าเป็นประเด็นสำคัญ ดังนั้นคำตอบของฉันซึ่งฉันหวังว่าจะช่วยให้ OP (และอื่น ๆ ) หยุดและคิดเกี่ยวกับสิ่งที่เป็นหรือสามารถเกิดขึ้นเมื่อพวกเขามีปัญหาที่พวกเขาไม่เข้าใจ
jmoreno

@Quuxplusone: ขออภัยถ้าฉันทำข้อกล่าวหาที่ผิดพลาดที่นี่ แต่ฉันคิดว่าคุณเป็นหนึ่งในคนที่ได้รับคำตอบของฉันเช่นกัน ถ้าเป็นเช่นนั้นคุณจะชี้ไปที่คำตอบของฉันอย่างมากซึ่งเป็นเหตุผลหลักในการทำเช่นนั้นและคุณจะตรวจสอบการแก้ไขด้วยหรือไม่ ฉันรู้ว่าฉันข้ามสองสามบิตเกี่ยวกับ internals ที่แท้จริงของวิธีการจัดการหน่วยความจำสแต็คดังนั้นตอนนี้ฉันเพิ่มเล็กน้อยเกี่ยวกับความไม่ถูกต้อง 100% สำหรับคำตอบของฉันตอนนี้ต่อไป :)
Elias Van Ootegem

@jmoreno จุดที่คุณทำเกี่ยวกับ "มันหมายความว่าหน่วยความจำบนชิปที่ยังไม่ได้ผลิตสำหรับคอมพิวเตอร์ที่ยังไม่ได้รับการออกแบบจะถูกจองอย่างใด? เป็นความหมายที่ผิดที่คำว่า "การจัดสรร" หมายถึงสิ่งที่ทำให้ฉันสับสนตั้งแต่เริ่มต้น ฉันชอบคำตอบนี้เพราะมันหมายถึงปัญหาที่ฉันพยายามชี้ให้เห็น ไม่มีคำตอบใดที่สัมผัสกับจุดนั้นจริงๆ ขอบคุณ
Talha Sayed

2

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

ดังนั้นถ้าเซ็กเมนต์ข้อมูลของคุณคือ 500 ไบต์โปรแกรมของคุณมีพื้นที่ 500 ไบต์ หากคุณเปลี่ยนเซ็กเมนต์ข้อมูลเป็น 1500 ไบต์ขนาดของโปรแกรมจะใหญ่กว่า 1000 ไบต์ ข้อมูลถูกรวมเข้าในโปรแกรมจริง

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


2

ฉันต้องการอธิบายแนวคิดเหล่านี้ด้วยความช่วยเหลือของไดอะแกรมน้อย

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

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

ตอนนี้จินตนาการว่าไม่มีโปรแกรมใดทำงานในหน่วยความจำ นี่ฉันแสดงโดยสี่เหลี่ยมผืนผ้าที่ว่างเปล่าขนาดใหญ่

ช่องว่าง

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

ตัวอย่างแรก

เมื่ออินสแตนซ์ที่สองของโปรแกรมนี้ทำงานหน่วยความจำจะมีลักษณะดังนี้

ตัวอย่างที่สอง

และที่สาม ..

อินสแตนซ์ที่สาม

เป็นต้นไปเรื่อย ๆ

ฉันหวังว่าการสร้างภาพข้อมูลนี้จะอธิบายแนวคิดนี้ได้ดี


2
หากแผนภาพเหล่านั้นแสดงความแตกต่างระหว่างหน่วยความจำแบบคงที่และแบบไดนามิกพวกเขาจะมีประโยชน์มากขึ้น IMHO
Bartek Banachewicz

ฉันหลีกเลี่ยงสิ่งนี้โดยจงใจเพื่อให้สิ่งต่าง ๆ เรียบง่าย ฉันมุ่งเน้นคือการอธิบาย funda นี้ด้วยความคมชัดโดยไม่มีความยุ่งเหยิงทางเทคนิคมาก เท่านี้มีไว้สำหรับตัวแปรแบบคงที่ .. จุดนี้ได้รับการจัดตั้งขึ้นอย่างดีจากคำตอบก่อนหน้าดังนั้นฉันข้ามนี้
user3258051

1
เอ๊ะแนวคิดนี้ไม่ซับซ้อนเป็นพิเศษดังนั้นฉันจึงไม่เห็นว่าทำไมทำให้มันง่ายกว่าที่ควรจะเป็น แต่เนื่องจากมันมีไว้เพื่อเป็นคำตอบฟรีเท่านั้น
Bartek Banachewicz

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