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


105

ฉันอ่านบรรทัดนี้ในหนังสือ:

เป็นไปไม่ได้เลยที่จะสร้างคอมไพเลอร์ที่สามารถระบุได้ว่าฟังก์ชัน C ++ จะเปลี่ยนค่าของตัวแปรเฉพาะหรือไม่

ย่อหน้านี้กำลังพูดถึงสาเหตุที่คอมไพเลอร์เป็นแบบอนุรักษ์นิยมเมื่อตรวจสอบ const-ness

เหตุใดจึงไม่สามารถสร้างคอมไพเลอร์ดังกล่าวได้

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


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

4
@MooingDuck ตรงนี้ ในวงกว้างมากขึ้นคอมไพลเลอร์ไม่ได้รวบรวมฟังก์ชันแยกกัน แต่รวบรวมเป็นส่วนหนึ่งของภาพที่กว้างขึ้นซึ่งทั้งหมดอาจไม่อยู่ในขอบเขตของคอมไพเลอร์
called2voyage

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

36
@twalberg Impossible ไม่ใช่การพูดเกินจริงปัญหาการหยุดใช้ที่นี่ตามคำตอบหลาย ๆ คำอธิบาย เป็นไปไม่ได้ที่จะวิเคราะห์โปรแกรมทั่วไปโดยใช้อัลกอริทึมอย่างสมบูรณ์
Fiktik

5
@twalberg คอมไพเลอร์ที่รวบรวมเฉพาะส่วนย่อยของโปรแกรมที่ถูกต้องไม่มีประโยชน์มากนัก
Caleb

คำตอบ:


139

เหตุใดจึงไม่สามารถสร้างคอมไพเลอร์ดังกล่าวได้

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

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

นี่คือตัวอย่างง่ายๆ:

void foo() {
    if (bar() == 0) this->a = 1;
}

คอมไพเลอร์จะตรวจสอบได้อย่างไรจากการดูรหัสนั้นว่าfooจะเปลี่ยนแปลงไปaหรือไม่? ไม่ว่าจะเป็นหรือไม่ขึ้นอยู่กับเงื่อนไขภายนอกของฟังก์ชันนั่นคือการใช้งานbar. มีมากกว่านั้นในการพิสูจน์ว่าปัญหาการหยุดชะงักนั้นไม่สามารถคำนวณได้ แต่มีการอธิบายไว้อย่างดีในบทความ Wikipedia ที่เชื่อมโยงกัน (และในตำราทฤษฎีการคำนวณทุกเล่ม) ดังนั้นฉันจะไม่พยายามอธิบายอย่างถูกต้องที่นี่


48
@mrsoltys คอมพิวเตอร์ควอนตัม "เท่านั้น" เร็วขึ้นอย่างทวีคูณสำหรับปัญหาบางอย่างพวกเขาไม่สามารถแก้ปัญหาที่ไม่สามารถตัดสินใจได้
zch

8
@mrsoltys อัลกอริธึมที่ซับซ้อนแบบทวีคูณ (เช่นการแยกตัวประกอบ) เหมาะอย่างยิ่งสำหรับคอมพิวเตอร์ควอนตัม แต่การหยุดปัญหาเป็นปัญหาเชิงตรรกะไม่สามารถคำนวณได้ไม่ว่าคุณจะมี "คอมพิวเตอร์" ประเภทใดก็ตาม
user1032613

7
@mrsoltys เพื่อเป็น smartass ใช่มันจะเปลี่ยนไป น่าเสียดายที่อาจหมายความว่าอัลกอริทึมถูกยกเลิกและยังคงทำงานอยู่ แต่น่าเสียดายที่คุณไม่สามารถบอกได้ว่าสิ่งใดหากไม่ได้สังเกตโดยตรงซึ่งคุณจะส่งผลต่อสถานะจริง
Nathan Ernst

9
@ ThorbjørnRavnAndersen: โอเคสมมติว่าฉันกำลังใช้งานโปรแกรม ฉันจะทราบได้อย่างไรว่าจะยุติหรือไม่
ruakh

8
@ ThorbjørnRavnAndersen แต่ถ้าคุณเรียกใช้งานโปรแกรมจริงและมันไม่ยุติ (เช่นการวนซ้ำที่ไม่มีที่สิ้นสุด) คุณจะไม่พบว่ามันไม่สิ้นสุด ... คุณก็ดำเนินการต่อไปอีกขั้นตอนหนึ่งเพราะมันอาจ คนสุดท้าย ...
MaxAxeHax

124

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

int variable = 0;

void f() {
    if (modifies_variable(f, variable)) {
        /* do nothing */
    } else {
        /* modify variable */
        variable = 1;
    }
}

int main(int argc, char **argv) {
    if (modifies_variable(f, variable)) {
        printf("Modifies variable\n");
    } else {
        printf("Does not modify variable\n");
    }

    return 0;
}

12
ดี! The I am a liar paradoxเขียนโดยโปรแกรมเมอร์
Krumelur

28
เป็นจริงเป็นเพียงแค่การปรับตัวที่ดีของหลักฐานที่มีชื่อเสียงสำหรับการ undecidability ของลังเลปัญหา
Konstantin Weitz

10
ในกรณีที่เป็นรูปธรรมนี้ "modifying_variable" ควรคืนค่าจริง: มีเส้นทางการดำเนินการอย่างน้อยหนึ่งเส้นทางที่ตัวแปรถูกแก้ไขอย่างแท้จริง และเส้นทางการดำเนินการนั้นจะมาถึงหลังจากการเรียกใช้ฟังก์ชันภายนอกที่ไม่ได้กำหนด - ดังนั้นฟังก์ชันทั้งหมดจึงไม่ถูกกำหนด ด้วยเหตุผล 2 ประการนี้คอมไพลเลอร์ควรมองโลกในแง่ดีและตัดสินใจว่าจะแก้ไขตัวแปร หากถึงเส้นทางในการปรับเปลี่ยนตัวแปรหลังจากการเปรียบเทียบเชิงกำหนด (ตรวจสอบได้โดยคอมไพเลอร์) ให้ผลลัพธ์เป็นเท็จ (เช่น "1 == 1") คอมไพเลอร์สามารถพูดได้อย่างปลอดภัยว่าฟังก์ชันดังกล่าวไม่เคยแก้ไขตัวแปร
Joe Pineda

6
@JoePineda: คำถามคือว่าจะfปรับเปลี่ยนตัวแปร - ไม่ว่าจะแก้ไขตัวแปรได้หรือไม่ คำตอบนี้ถูกต้อง
Neil G

4
@JoePineda: ไม่มีอะไรป้องกันไม่ให้ฉันคัดลอก / วางรหัสmodifies_variableจากแหล่งคอมไพเลอร์ทำให้อาร์กิวเมนต์ของคุณว่างเปล่าโดยสิ้นเชิง (สมมติว่าเป็นโอเพนซอร์ส แต่ประเด็นควรชัดเจน)
orlp

60

อย่าสับสนว่า "จะหรือไม่แก้ไขตัวแปรที่กำหนดให้อินพุตเหล่านี้" for "มีเส้นทางการดำเนินการที่แก้ไขตัวแปร"

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

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

ดังนั้นสิ่งที่ดูเหมือนจะเป็นคำพูดที่น่าแปลกใจเกี่ยวกับ C ++ นั้นเป็นคำพูดที่ไม่สำคัญเกี่ยวกับทุกภาษา


5
นี่คือคำตอบที่ดีที่สุด imho สิ่งสำคัญคือต้องสร้างความแตกต่างนั้น
UncleZeiv

"เป็นไปไม่ได้เล็กน้อย"?
Kip

2
@Kip "เป็นไปไม่ได้เล็กน้อยที่จะตัดสินใจ" อาจหมายความว่า "เป็นไปไม่ได้ที่จะตัดสินใจและการพิสูจน์นั้นเป็นเรื่องเล็กน้อย"
fredoverflow

28

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

void maybe(int& val) {
    cout << "Should I change value? [Y/N] >";
    string reply;
    cin >> reply;
    if (reply == "Y") {
        val = 42;
    }
}

"เป็นไปได้อย่างแน่นอนที่จะสร้างคอมไพเลอร์ที่ตรวจสอบว่าฟังก์ชัน C ++ สามารถเปลี่ยนค่าของตัวแปรเฉพาะได้หรือไม่" ไม่ใช่ไม่ใช่ ดูคำตอบของ Caleb เพื่อให้คอมไพลเลอร์ทราบว่า foo () สามารถเปลี่ยน a ได้จะต้องทราบว่าเป็นไปได้หรือไม่ที่ bar () จะส่งคืน 0 และไม่มีฟังก์ชันที่คำนวณได้ที่สามารถบอกค่าที่เป็นไปได้ทั้งหมดของฟังก์ชันที่คำนวณได้ ดังนั้นจึงมีโค้ดพา ธ ที่คอมไพลเลอร์ไม่สามารถบอกได้ว่าจะไปถึงหรือไม่ หากตัวแปรมีการเปลี่ยนแปลงเฉพาะในเส้นทางรหัสที่ไม่สามารถเข้าถึงได้จะไม่เปลี่ยนแปลง แต่คอมไพเลอร์จะตรวจไม่พบ
Martin Epsz

12
@MartinEpsz By "can" I mean "is allow to change", not "can could change". ฉันเชื่อว่านี่คือสิ่งที่ OP คำนึงถึงเมื่อพูดถึงการconstตรวจสอบ -ness
dasblinkenlight

@dasblinkenlight ฉันต้องยอมรับว่าฉันเชื่อว่า OP อาจหมายถึงอันแรก "ได้รับอนุญาตหรือเปลี่ยนแปลง" หรือ "อาจหรือไม่อาจเปลี่ยนแปลง" เทียบกับ "จะไม่เปลี่ยนแปลงอย่างแน่นอน" แน่นอนฉันไม่สามารถนึกถึงสถานการณ์ที่จะเป็นปัญหาได้ คุณยังสามารถปรับเปลี่ยนคอมไพลเลอร์เพื่อเพียงแค่ตอบว่า "อาจเปลี่ยนแปลง" ในฟังก์ชันใด ๆ ที่มีตัวระบุหรือการเรียกใช้ฟังก์ชันที่มีแอตทริบิวต์คำตอบ "อาจเปลี่ยนแปลง" ที่กล่าวว่า C และ C ++ เป็นภาษาที่น่ากลัวที่จะลองใช้เนื่องจากมีคำจำกัดความที่หลวม ๆ ฉันคิดว่านี่คือสาเหตุที่ const-ness เป็นปัญหาใน C ++ เลย
ท.บ.

@MartinEpsz: "และไม่มีฟังก์ชันใดที่สามารถคำนวณได้ที่สามารถบอกค่าการส่งคืนที่เป็นไปได้ทั้งหมดของฟังก์ชันที่คำนวณได้" ฉันคิดว่าการตรวจสอบ "ค่าส่งคืนที่เป็นไปได้ทั้งหมด" เป็นแนวทางที่ไม่ถูกต้อง มีระบบคณิตศาสตร์ (maxima, mathlab) ที่สามารถแก้สมการได้ซึ่งหมายความว่าควรใช้วิธีการที่คล้ายกันกับฟังก์ชัน กล่าวคือถือว่ามันเป็นสมการที่มีหลายสิ่งที่ไม่รู้จัก ปัญหาคือการควบคุมการไหล + ผลข้างเคียง => สถานการณ์ที่ไม่สามารถแก้ไขได้ IMO หากไม่มีสิ่งเหล่านี้ (ภาษาที่ใช้งานได้ไม่มีการกำหนด / ผลข้างเคียง) จะสามารถคาดเดาได้ว่าโปรแกรมเส้นทางใดจะใช้
SigTerm

16

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

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

int y;

int main(int argc, char *argv[]) {
   if (argc > 2) y++;
}

คอมไพเลอร์จะคาดเดาได้อย่างไรว่าyจะถูกแก้ไขหรือไม่?


7

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

สิ่งที่เป็นไปไม่ได้คือการรู้ในกรณีทั่วไป

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

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


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

การโอเวอร์โหลดเป็นแนวคิดเวลาคอมไพล์ คุณอาจหมายถึง "วิธีการที่ถูกลบล้าง"
fredoverflow

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

6

มีหลายวิธีในการอธิบายสิ่งนี้ซึ่งหนึ่งในนั้นคือปัญหาการหยุดชะงัก :

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

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

ถ้าฉันเขียนโปรแกรมที่มีลักษณะดังนี้:

do tons of complex stuff
if (condition on result of complex stuff)
{
    change value of x
}
else
{
    do not change value of x
}

คุณค่าของxการเปลี่ยนแปลงหรือไม่? ในการพิจารณาสิ่งนี้ก่อนอื่นคุณจะต้องพิจารณาว่าชิ้นdo tons of complex stuffส่วนนั้นทำให้เกิดอาการไฟไหม้หรือไม่หรือพื้นฐานกว่านั้นไม่ว่าจะหยุดหรือไม่ นั่นเป็นสิ่งที่คอมไพเลอร์ไม่สามารถทำได้


6

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

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

foo(int x){
   if(x)
       y=1;
}

ตอนนี้สำหรับโปรแกรมใด ๆ ที่เราชอบลองเขียนใหม่เป็น:

int y;
main(){
    int x;
    ...
    run the program normally
    ...
    foo(x);
}

สังเกตว่าถ้าและเฉพาะในกรณีที่โปรแกรมของเราเปลี่ยนค่าของ y มันจะยุติ - foo () เป็นสิ่งสุดท้ายที่ทำก่อนออก ซึ่งหมายความว่าเราได้แก้ไขปัญหาการหยุดชะงักแล้ว!

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


ฉันไม่แน่ใจว่าฉันทำตามเหตุผลของคุณเกี่ยวกับสาเหตุที่โปรแกรมของเรายุติ iff จึงเปลี่ยนค่าของy. ดูเหมือนฉันfoo()จะกลับมาอย่างรวดเร็วแล้วก็main()ออกไป (นอกจากนี้คุณกำลังโทรfoo()โดยไม่มีข้อโต้แย้ง ... นั่นเป็นส่วนหนึ่งของความสับสนของฉัน)
LarsH

1
@LarsH: Iff โปรแกรมที่แก้ไขจะยุติฟังก์ชันสุดท้ายที่เรียกว่า f หากแก้ไข y จะเรียกใช้ f (ข้อความอื่นไม่สามารถเปลี่ยน y ได้เนื่องจากถูกนำมาใช้โดยการแก้ไขเท่านั้น) ดังนั้นหากแก้ไข y โปรแกรมจะสิ้นสุดลง
MSalters

4

ทันทีที่ฟังก์ชั่นเรียกใช้ฟังก์ชันอื่นที่คอมไพลเลอร์ไม่ "เห็น" ที่มาของมันก็จะต้องถือว่าตัวแปรมีการเปลี่ยนแปลงหรือสิ่งต่างๆอาจผิดพลาดเพิ่มเติมด้านล่าง ตัวอย่างเช่นสมมติว่าเรามีสิ่งนี้ใน "foo.cpp":

 void foo(int& x)
 {
    ifstream f("f.dat", ifstream::binary);
    f.read((char *)&x, sizeof(x));
 }

และเรามีสิ่งนี้ใน "bar.cpp":

void bar(int& x)
{
  foo(x);
}

คอมไพเลอร์จะ "รู้" ที่xไม่เปลี่ยนแปลงได้อย่างไร (หรือ IS เปลี่ยนแปลงเหมาะสมกว่า) ในbar?

ฉันแน่ใจว่าเราสามารถหาอะไรที่ซับซ้อนกว่านี้ได้ถ้ามันไม่ซับซ้อนพอ


คอมไพเลอร์สามารถรู้ได้ว่า x ไม่เปลี่ยนแปลงใน bar ถ้า bar x ถูกส่งเป็น pass-by-reference-to-const ใช่ไหม?
คริกเก็ต

ใช่ แต่ถ้าฉันเพิ่มconst_castใน foo มันจะยังคงทำการxเปลี่ยนแปลง - ฉันผิดสัญญาที่บอกว่าคุณจะไม่เปลี่ยนconstตัวแปร แต่เนื่องจากคุณสามารถแปลงอะไรก็ได้เป็น "more const" และconst_castมีอยู่ นักออกแบบภาษาย่อมมีความคิดในใจว่าบางครั้งก็มีเหตุผลที่ดีที่จะเชื่อว่าconstค่านิยมอาจต้องเปลี่ยนแปลง
Mats Petersson

@MatsPetersson: ฉันเชื่อว่าถ้าคุณ const_cast คุณจะต้องเก็บชิ้นส่วนทั้งหมดที่แตกเพราะคอมไพเลอร์อาจได้ แต่ไม่ต้องชดเชยสิ่งนั้น
แซนคม

@ZanLynx: ใช่ฉันแน่ใจว่าถูกต้อง แต่ในขณะเดียวกันนักแสดงก็มีอยู่ซึ่งหมายความว่าคนที่ออกแบบภาษามีความคิดบางอย่างที่ว่า "เราอาจต้องการสิ่งนี้ในบางจุด" ซึ่งหมายความว่ามันไม่ได้หมายความว่าจะไม่ทำอะไรที่เป็นประโยชน์เลย
Mats Petersson

1

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

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


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

นั่นจะเป็นประโยชน์อย่างแน่นอน มีรูปแบบที่แน่นอนสำหรับสิ่งนี้ แต่ในภาษา C ++ (และภาษาอื่น ๆ อีกมากมาย) สามารถ "โกง" ได้เสมอ
Krumelur

วิธีสำคัญที่. NET เหนือกว่า Java คือมีแนวคิดเกี่ยวกับการอ้างอิงชั่วคราว แต่น่าเสียดายที่ไม่มีวิธีใดที่วัตถุจะแสดงคุณสมบัติเป็นการอ้างอิงชั่วคราว (สิ่งที่ฉันต้องการเห็นจริงๆน่าจะเป็นวิธีการโดย รหัสใดที่ใช้คุณสมบัติจะส่งผ่านการอ้างอิงชั่วคราวไปยังรหัส (พร้อมกับตัวแปรชั่วคราว) ที่ควรใช้ในการจัดการวัตถุ
supercat

1

เพื่อให้คำถามมีความเฉพาะเจาะจงมากขึ้นฉันขอแนะนำให้ใช้ข้อ จำกัด ชุดต่อไปนี้ซึ่งอาจเป็นสิ่งที่ผู้เขียนหนังสืออาจมีอยู่ในใจ:

  1. สมมติว่าคอมไพเลอร์กำลังตรวจสอบพฤติกรรมของฟังก์ชันเฉพาะที่เกี่ยวกับความเป็นไปได้ของตัวแปร เพื่อความถูกต้องคอมไพเลอร์จะต้องถือว่า (เนื่องจากการใช้นามแฝงตามที่อธิบายไว้ด้านล่าง) หากฟังก์ชันที่เรียกว่าฟังก์ชันอื่นตัวแปรมีการเปลี่ยนแปลงดังนั้นสมมติฐาน # 1 จะใช้กับส่วนของโค้ดที่ไม่เรียกใช้ฟังก์ชันเท่านั้น
  2. สมมติว่าตัวแปรไม่ได้ถูกแก้ไขโดยกิจกรรมแบบอะซิงโครนัสหรือพร้อมกัน
  3. สมมติว่าคอมไพลเลอร์กำหนดเฉพาะว่าตัวแปรสามารถแก้ไขได้หรือไม่ไม่ใช่ว่าจะถูกแก้ไข กล่าวอีกนัยหนึ่งคอมไพเลอร์กำลังทำการวิเคราะห์แบบคงที่เท่านั้น
  4. สมมติว่าคอมไพเลอร์กำลังพิจารณาเฉพาะโค้ดที่ทำงานได้อย่างถูกต้องเท่านั้น (ไม่พิจารณาการโอเวอร์รัน / อันเดอร์อาเรย์ตัวชี้ที่ไม่ดี ฯลฯ )

ในบริบทของการออกแบบคอมไพเลอร์ฉันคิดว่าสมมติฐานที่ 1,3,4 มีความหมายที่สมบูรณ์แบบในมุมมองของผู้เขียนคอมไพเลอร์ในบริบทของความถูกต้องของรหัสและ / หรือการเพิ่มประสิทธิภาพโค้ด สมมติฐานที่ 2 มีเหตุผลในกรณีที่ไม่มีคำหลักที่ผันผวน และสมมติฐานเหล่านี้ยังมุ่งเน้นไปที่คำถามมากพอที่จะทำให้การตัดสินคำตอบที่เสนอมีความชัดเจนมากขึ้น :-)

จากสมมติฐานเหล่านั้นสาเหตุสำคัญที่ทำให้ไม่สามารถสันนิษฐาน const-ness ได้เนื่องจากตัวแปรนามแฝง คอมไพเลอร์ไม่สามารถทราบได้ว่าตัวแปรอื่นชี้ไปที่ตัวแปร const หรือไม่ การใช้นามแฝงอาจเกิดจากฟังก์ชันอื่นในหน่วยคอมไพเลอร์เดียวกันซึ่งในกรณีนี้คอมไพลเลอร์สามารถค้นหาฟังก์ชันต่างๆและใช้แผนผังการเรียกเพื่อกำหนดแบบคงที่ว่าการตั้งนามแฝงอาจเกิดขึ้นได้ แต่ถ้าการตั้งนามแฝงเกิดจากไลบรารีหรือรหัสแปลกปลอมอื่น ๆ คอมไพเลอร์จะไม่มีทางทราบได้ว่ามีการกำหนดชื่อตัวแปรหรือไม่

คุณสามารถโต้แย้งได้ว่าหากตัวแปร / อาร์กิวเมนต์ถูกทำเครื่องหมายเป็น const ก็ไม่ควรมีการเปลี่ยนแปลงผ่านนามแฝง แต่สำหรับผู้เขียนคอมไพเลอร์นั้นค่อนข้างเสี่ยง แม้แต่โปรแกรมเมอร์ที่เป็นมนุษย์อาจมีความเสี่ยงในการประกาศตัวแปร const ซึ่งเป็นส่วนหนึ่งของโครงการขนาดใหญ่ที่เขาไม่ทราบพฤติกรรมของทั้งระบบหรือ OS หรือไลบรารีเพื่อให้ทราบว่าตัวแปรได้รับจริง ' t เปลี่ยน.


0

แม้ว่าจะมีการประกาศตัวแปรconstแต่ก็ไม่ได้หมายความว่าโค้ดที่เขียนไม่ดีบางตัวสามารถเขียนทับได้

//   g++ -o foo foo.cc

#include <iostream>
void const_func(const int&a, int* b)
{
   b[0] = 2;
   b[1] = 2;
}

int main() {
   int a = 1;
   int b = 3;

   std::cout << a << std::endl;
   const_func(a,&b);
   std::cout << a << std::endl;
}

เอาต์พุต:

1
2

นี้เกิดขึ้นเพราะaและbเป็นตัวแปรสแต็คและเพิ่งเกิดขึ้นจะเป็นที่ตั้งของหน่วยความจำเช่นเดียวกับb[1] a
Mark Lakata

1
-1. พฤติกรรมที่ไม่ได้กำหนดจะลบข้อ จำกัด ทั้งหมดเกี่ยวกับพฤติกรรมของคอมไพเลอร์
MSalters

ไม่แน่ใจเกี่ยวกับการโหวตลง นี่เป็นเพียงตัวอย่างที่ไปที่คำถามเดิมของ OP เกี่ยวกับสาเหตุที่ไม่สามารถออกจากร่างคอมไพเลอร์ถ้าสิ่งที่เป็นจริงถ้าทุกอย่างจะมีป้ายconst constเป็นเพราะพฤติกรรมที่ไม่ได้กำหนดเป็นส่วนหนึ่งของ C / C ++ ฉันพยายามหาวิธีอื่นในการตอบคำถามของเขาแทนที่จะพูดถึงปัญหาการหยุดชะงักหรือข้อมูลจากภายนอก
Mark Lakata

0

หากต้องการขยายความคิดเห็นของฉันข้อความในหนังสือเล่มนั้นไม่ชัดเจนซึ่งทำให้ปัญหาสับสน

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

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

ฟังก์ชันนี้สามารถวิเคราะห์ได้อย่างง่ายดาย:

static int global;

void foo()
{
}

"foo" ไม่แก้ไข "global" อย่างชัดเจน มันไม่ได้ปรับเปลี่ยนอะไรเลยและคอมไพเลอร์สามารถทำงานนี้ได้อย่างง่ายดาย

ฟังก์ชันนี้ไม่สามารถวิเคราะห์ได้:

static int global;

int foo()
{
    if ((rand() % 100) > 50)
    {
        global = 1;
    }
    return 1;

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

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


สิ่งที่คุณพูดนั้นเป็นความจริง แต่แม้กระทั่งสำหรับโปรแกรมที่เรียบง่ายมากสำหรับทุกสิ่งที่รู้ในเวลาคอมไพล์คุณจะไม่สามารถพิสูจน์อะไรได้เลยแม้แต่โปรแกรมจะหยุด นี่คือปัญหาที่หยุดชะงัก ตัวอย่างเช่นคุณสามารถเขียนโปรแกรมตามลำดับ Hailstone en.wikipedia.org/wiki/Collatz_conjectureและทำให้มันกลับมาเป็นจริงหากมันมาบรรจบกัน คอมไพเลอร์จะไม่สามารถทำได้ (เพราะมันจะล้นในหลาย ๆ กรณี) และแม้แต่นักคณิตศาสตร์ก็ไม่รู้ว่ามันจริงหรือไม่
kriss

ถ้าคุณหมายถึง "มีบางโปรแกรมมองง่ายมากสำหรับการที่คุณไม่สามารถพิสูจน์อะไร" ผมเห็นด้วยอย่างสิ้นเชิง แต่การพิสูจน์ปัญหาการหยุดชะงักแบบคลาสสิกของทัวริงนั้นอาศัยโปรแกรมที่สามารถบอกได้ว่ามันหยุดเพื่อสร้างความขัดแย้งหรือไม่ เนื่องจากเป็นคณิตศาสตร์ไม่ใช่การนำไปใช้ มีโปรแกรมแน่นอนที่เป็นไปได้ทั้งหมดในการกำหนดเวลาคอมไพล์แบบคงที่ว่าจะมีการแก้ไขตัวแปรเฉพาะหรือไม่และโปรแกรมจะหยุดหรือไม่ อาจไม่สามารถพิสูจน์ได้ทางคณิตศาสตร์ แต่สามารถทำได้จริงในบางกรณี
El Zorko
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.