เป็นไปได้ที่จะทำนายแบบคงที่เมื่อมีการจัดสรรคืนหน่วยความจำ --- จากซอร์สโค้ดเท่านั้นหรือไม่


27

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

GCs แก้ปัญหานี้โดยการหาโดยตรงระหว่างรันไทม์เมื่อโฟลว์ควบคุมถูกดำเนินการ แต่แหล่งที่แท้จริงของความจริงเกี่ยวกับการควบคุมการไหลคือแหล่งที่มา ในทางทฤษฎีมันควรจะเป็นไปได้ที่จะกำหนดตำแหน่งที่จะแทรกการfree()เรียกก่อนที่จะรวบรวมโดยการวิเคราะห์แหล่งที่มา (หรือ AST)

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

ดูเหมือนว่าเป็นไปได้ที่จะเขียนโปรแกรมที่สามารถอ่านซอร์สของโปรแกรมและ:

  1. ทำนายการเรียงสับเปลี่ยนทั้งหมดของโฟลว์การควบคุมของโปรแกรม --- ไปยังความแม่นยำที่คล้ายกันในขณะที่ดูการทำงานจริงของโปรแกรม
  2. ติดตามการอ้างอิงทั้งหมดไปยังทรัพยากรที่จัดสรร
  3. สำหรับการอ้างอิงแต่ละครั้งให้ตรวจสอบการไหลของการควบคุมที่ตามมาทั้งหมดเพื่อหาจุดเริ่มต้นที่การอ้างอิงนั้นรับประกันว่าจะไม่ถูกยกเลิกการลงทะเบียน
  4. ณ จุดนั้นให้แทรกคำสั่งจัดสรรคืนที่บรรทัดของซอร์สโค้ด

มีอะไรอีกไหมที่ทำสิ่งนี้แล้ว? ฉันไม่คิดว่าตัวชี้สมาร์ท Rust หรือ C ++ / RAII เป็นสิ่งเดียวกัน


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

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

9
@ ratchetfreak ขอบคุณมันไม่เคยรู้เรื่องเช่นนี้ปัญหาการหยุดชะงักที่ทำให้ฉันหวังว่าฉันจะได้รับปริญญาของฉันใน comp sci แทนวิชาเคมี
zelcon

15
@ zelcon5 ตอนนี้คุณรู้เกี่ยวกับวิชาเคมีและปัญหาการหยุด ... :)
David Arno

7
@Ehorhor เว้นแต่ว่าคุณจะจัดโครงสร้างโปรแกรมของคุณดังนั้นขอบเขตของเวลาที่มีการใช้ทรัพยากรนั้นมีความชัดเจนมากเช่นกับ RAII หรือลองกับทรัพยากร
ratchet freak

คำตอบ:


23

ใช้ตัวอย่าง (วางแผน) นี้:

void* resource1;
void* resource2;

while(true){

    int input = getInputFromUser();

    switch(input){
        case 1: resource1 = malloc(500); break;
        case 2: resource2 = resource1; break;
        case 3: useResource(resource1); useResource(resource2); break;
    }
}

เมื่อใดควรโทรฟรี? ก่อน malloc และมอบหมายให้resource1เราทำไม่ได้เพราะมันอาจถูกคัดลอกไปยังresource2ก่อนที่จะมอบหมายให้resource2เราทำไม่ได้เพราะเราอาจจะได้รับ 2 จากผู้ใช้สองครั้งโดยไม่มีการแทรกแซง 1

วิธีเดียวที่จะแน่ใจได้คือการทดสอบ resource1 และ resource2 เพื่อดูว่าพวกเขาไม่เท่ากันในกรณีที่ 1 และ 2 และเพิ่มมูลค่าเก่าถ้าไม่ได้ นี่คือการอ้างอิงโดยพื้นฐานที่คุณทราบว่ามีการอ้างอิงที่เป็นไปได้เพียง 2 รายการ


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

27

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

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

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


ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
maple_shaft

13

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

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

(นี่เป็นภาพประกอบที่ดีว่าทำไมคุณไม่ควรมองปัญหา Halting สำหรับคำตอบของคำถามเชิงวิศวกรรม: เราไม่ต้องการหรือต้องการแก้ปัญหากรณีทั่วไปสำหรับโปรแกรมที่สมจริงที่สุด)


5
ฉันคิดว่านี่เป็นตัวอย่างที่ยอดเยี่ยมของการใช้ปัญหา Halting อย่างเหมาะสม ปัญหาการหยุดชะงักบอกเราว่าปัญหาไม่สามารถแก้ไขได้ในกรณีทั่วไปดังนั้นคุณจึงมองหาสถานการณ์ที่มีข้อ จำกัด ในการแก้ปัญหา
Taemyr

โปรดทราบว่าปัญหาจะแก้ไขได้มากขึ้นเมื่อเราพูดถึงภาษาที่บริสุทธิ์หรือเกือบจะใช้งานได้จริงและไม่มีผลข้างเคียงเช่น Standard ML และ Haskell
cat

10

ทำนายการเปลี่ยนแปลงทั้งหมดของโฟลว์การควบคุมของโปรแกรม

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


จุดดี. ฉันเดาว่าผู้ประมวลผลควอนตัมเป็นความหวังเดียวถ้ามีเลย
zelcon

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

8

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

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


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

ความผิดพลาดนั้นเกิดขึ้นจากโปรแกรมเมอร์ในภาษาที่โปรแกรมเมอร์ต้องจัดการการจัดสรรหน่วยความจำด้วยตนเอง @Peter เมื่อคอมไพเลอร์จัดการการจัดสรรหน่วยความจำข้อผิดพลาดประเภทนั้นจะไม่เกิดขึ้น
Karl Bielefeldt

คุณทำคำสั่งทั่วไปรวมถึงวลีที่ว่า "คอมไพเลอร์เกือบทั้งหมด" ซึ่งต้องมีคอมไพเลอร์ C
ปีเตอร์ - Reinstate Monica

2
คอมไพเลอร์ C ใช้เพื่อกำหนดตัวแปรชั่วคราวที่สามารถจัดสรรให้กับรีจิสเตอร์ได้
Karl Bielefeldt

4

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

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

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

มีทางเลือกบางอย่าง (บางที่เกิดขึ้น)

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

Swift และ Objective-C ไปอีกเส้นทางหนึ่งซึ่งส่วนใหญ่เป็นการอ้างอิงอัตโนมัติ การนับการอ้างอิงจะกลายเป็นปัญหากับรอบและมีความท้าทายโปรแกรมเมอร์ที่สำคัญตัวอย่างเช่นโดยเฉพาะอย่างยิ่งกับการปิด


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

2

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


2

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

function foo(int a) {
    void *p = malloc(1);
    ... do something which may, or may not, halt ...
    free(p);
}

https://en.wikipedia.org/wiki/Halting_problem

ที่กล่าวว่า Rust เป็นสิ่งที่ดีมาก ... https://doc.rust-lang.org/book/ownership.html

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