ทำไมคอมไพเลอร์ตรวจไม่พบเดดโค้ดจึงไม่สามารถแก้ไขได้อย่างสมบูรณ์?


192

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


91
สร้าง loop ใส่ code หลังจากนั้นใช้en.wikipedia.org/wiki/Halting_problem
zapl

48
if (isPrime(1234234234332232323423)){callSomething();}รหัสนี้จะเรียกอะไรบางอย่างหรือไม่? มีตัวอย่างอื่น ๆ อีกมากมายที่การตัดสินใจว่าฟังก์ชั่นที่เคยเรียกว่ามีราคาแพงกว่าแค่รวมไว้ในโปรแกรม
idclev 463035818

33
public static void main(String[] args) {int counterexample = findCollatzConjectureCounterexample(); System.out.println(counterexample);}<- println call dead code คืออะไร? ไม่ใช่แม้แต่มนุษย์ที่สามารถแก้ปัญหานั้นได้!
user253751

15
@ tobi303 ไม่ใช่ตัวอย่างที่ดีมันเป็นเรื่องง่ายมากที่จะแยกแยะตัวเลขเฉพาะ ... เพียงแค่ไม่คำนึงถึงพวกมันอย่างมีประสิทธิภาพ ปัญหาการหยุดทำงานไม่ได้อยู่ใน NP แต่แก้ไม่ได้
en_Knight

57
@alephzero และ en_Knight - คุณผิดทั้งคู่ isPrime เป็นตัวอย่างที่ดี คุณตั้งสมมติฐานว่าฟังก์ชันกำลังตรวจสอบหมายเลขเฉพาะ บางทีหมายเลขนั้นอาจเป็นหมายเลขซีเรียลและค้นหาฐานข้อมูลเพื่อดูว่าผู้ใช้เป็นสมาชิก Amazon Prime หรือไม่ เหตุผลที่เป็นตัวอย่างที่ดีก็คือวิธีเดียวที่จะรู้ว่าเงื่อนไขนั้นคงที่หรือไม่คือการใช้งานฟังก์ชั่น isPrime ดังนั้นตอนนี้ที่จะต้องมีคอมไพเลอร์เป็นล่าม แต่นั่นก็ยังคงไม่สามารถแก้กรณีเหล่านี้ที่ข้อมูลมีความผันผวน
Dunk

คำตอบ:


275

ปัญหาโค้ดเดดซีเกี่ยวข้องกับ ปัญหาลังเล

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

สิ่งนี้เกี่ยวข้องกับโค้ดที่ตายแล้วอย่างไร

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

คุณจะถ่ายโอนอัลกอริทึมสำหรับเดดโค้ดเข้าสู่อัลกอริทึมสำหรับปัญหาการหยุดงานได้อย่างไร

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


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

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


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

50
ตัวประมวลผล @Vality 64 บิตสามารถระบุ 2 ^ 64 ไบต์ ขอให้สนุกกับการค้นหาสถานะทั้งหมด 256 ^ (2 ^ 64)!
Daniel Wagner

82
@DanielWagner นี่ไม่ควรเป็นปัญหา การค้นหา256^(2^64)คือสถานะO(1)ดังนั้นการตรวจหาโค้ดที่ไม่ทำงานจึงสามารถทำได้ในเวลาพหุนาม
aebabis

13
@Leliel นั่นคือการเสียดสี
พอลเดรเปอร์

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

77

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

โปรแกรม C #

using System;
using YourVendor.Compiler;

class Program
{
    static void Main(string[] args)
    {
        string quine_text = @"using System;
using YourVendor.Compiler;

class Program
{{
    static void Main(string[] args)
    {{
        string quine_text = @{0}{1}{0};
        quine_text = string.Format(quine_text, (char)34, quine_text);

        if (YourVendor.Compiler.HasDeadCode(quine_text))
        {{
            System.Console.WriteLine({0}Dead code!{0});
        }}
    }}
}}";
        quine_text = string.Format(quine_text, (char)34, quine_text);

        if (YourVendor.Compiler.HasDeadCode(quine_text))
        {
            System.Console.WriteLine("Dead code!");
        }
    }
}

ถ้า YourVendor.Compiler.HasDeadCode(quine_text)ส่งคืนจะไม่มีการดำเนินการfalseบรรทัดSystem.Console.WriteLn("Dead code!");ดังนั้นโปรแกรมนี้จึงมีรหัสตายตัวและเครื่องมือตรวจจับผิด

แต่ถ้ามันกลับtrueมาบรรทัดSystem.Console.WriteLn("Dead code!");จะถูกดำเนินการและเนื่องจากไม่มีรหัสเพิ่มเติมในโปรแกรมจึงไม่มีรหัสตายเลยดังนั้นอีกครั้งเครื่องตรวจจับก็ผิด

ดังนั้นคุณก็มีเครื่องตรวจจับรหัสตายตัวที่ส่งกลับเฉพาะ "ไม่มีรหัสตาย" หรือ "ไม่มีรหัสตาย" บางครั้งต้องตอบคำตอบที่ผิด


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

1
การเพิ่มขึ้นสำหรับคำตอบ Godelian
Jared Smith

@ighigh Ugh นั่นเป็นคำที่ไม่ถูกต้อง ฉันไม่ได้ป้อนซอร์สโค้ดของตัวตรวจจับรหัสตายตัวให้กับตัวเอง แต่เป็นรหัสแหล่งที่มาของโปรแกรมที่ใช้งาน แน่นอนในบางจุดมันอาจจะต้องดูรหัสของตัวเอง แต่มันเป็นธุรกิจของมัน
Joker_vD

65

หากปัญหาการหยุดชะงักไม่ชัดเจนเกินไปให้คิดแบบนี้

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

 for (BigInt n = 4; ; n+=2) {
     if (!isGoldbachsConjectureTrueFor(n)) {
         print("Conjecture is false for at least one value of n\n");
         exit(0);
     }
 }

การดำเนินการของisGoldbachsConjectureTrueFor()เหลือเป็นแบบฝึกหัดสำหรับผู้อ่าน แต่เพื่อจุดประสงค์นี้อาจซ้ำง่ายกว่าทุกช่วงเวลาน้อยกว่าn

ทีนี้เหตุผลข้างต้นต้องเท่ากับ:

 for (; ;) {
 }

(เช่นวงวนไม่สิ้นสุด) หรือ

print("Conjecture is false for at least one value of n\n");

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

 String target = "f3c5ac5a63d50099f3b5147cabbbd81e89211513a92e3dcd2565d8c7d302ba9c";
 for (BigInt n = 0; n < 2**2048; n++) {
     String s = n.toString();
     if (sha256(s).equals(target)) {
         print("Found SHA value\n");
         exit(0);
     }
 }
 print("Not found SHA value\n");

เรารู้ว่าโปรแกรมจะพิมพ์ "Found SHA value" หรือ "Not Found SHA value" (คะแนนโบนัสถ้าคุณสามารถบอกได้ว่าอันไหนเป็นจริง) อย่างไรก็ตามสำหรับคอมไพเลอร์ที่จะสามารถเพิ่มประสิทธิภาพที่เหมาะสมที่จะใช้คำสั่งของการทำซ้ำ 2 ^ 2048 ในความเป็นจริงมันจะเป็นการเพิ่มประสิทธิภาพที่ดีที่สุดเท่าที่ฉันคาดการณ์โปรแกรมข้างต้นจะ (หรืออาจ) ทำงานจนกว่าความร้อนจากจักรวาลมากกว่าการพิมพ์อะไรโดยไม่มีการเพิ่มประสิทธิภาพ


4
มันเป็นคำตอบที่ดีที่สุดโดยไกล +1
jean

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

2
ซ้ำ 2 ^ 2048? แม้แต่ความคิดลึกก็ยอมแพ้
Peter Mortensen

มันจะพิมพ์ "Found SHA value" ที่มีความน่าจะเป็นสูงถึงแม้ว่าเป้าหมายนั้นจะเป็นสตริงสุ่ม 64 หลักเลขฐานสิบหก เว้นแต่sha256จะส่งคืนอาร์เรย์ไบต์และอาร์เรย์ไบต์จะไม่เปรียบเทียบเท่ากับสตริงในภาษาของคุณ
user253751

4
Implementation of isGoldbachsConjectureTrueFor() is left as an exercise for the readerนี่ทำให้ฉันหัวเราะหึ ๆ
biziclop

34

ผมไม่ทราบว่าถ้า C ++ หรือ Java มีEvalฟังก์ชั่นประเภท แต่หลายภาษาจะช่วยให้คุณทำวิธีการโทรโดยใช้ชื่อ พิจารณาตัวอย่าง VBA (ที่วางแผน) ต่อไปนี้

Dim methodName As String

If foo Then
    methodName = "Bar"
Else
    methodName = "Qux"
End If

Application.Run(methodName)

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

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

Application.Run("Bar")

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


2
ใน Java (หรือ C #) สิ่งนี้สามารถทำได้ด้วยการสะท้อนกลับ C ++ คุณอาจดึงความไม่พอใจออกมาได้โดยใช้มาโครทำ จะไม่สวย แต่ C ++ ไม่ค่อยเป็น
Darrel Hoffman

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

1
@ArtOfWarfare เราไม่ได้พูดถึงวิธีการที่สามารถทำได้ เห็นได้ชัดว่าการวิเคราะห์ความหมายของรหัสที่สามารถทำได้เพื่อหาสถานการณ์เช่นนี้ประเด็นก็คือว่าคอมไพเลอร์ไม่ได้ มันอาจจะเป็นไปได้ แต่อาจจะไม่
RubberDuck

3
@ArtOfWarfare: ถ้าคุณต้องการ nitpick แน่นอน ฉันถือว่าตัวประมวลผลล่วงหน้าเป็นส่วนหนึ่งของคอมไพเลอร์ แต่ฉันรู้ว่าไม่ใช่เทคนิค อย่างไรก็ตามพอยน์เตอร์ของฟังก์ชั่นอาจผิดกฎที่ฟังก์ชั่นไม่ได้อ้างอิงโดยตรงที่ใดก็ได้ - มันเป็นเพียงแค่ตัวชี้แทนการโทรโดยตรงเหมือนกับตัวแทนใน C # โดยทั่วไปแล้ว C ++ นั้นยากกว่ามากสำหรับผู้รวบรวมที่จะทำนายเนื่องจากมันมีวิธีการทำสิ่งต่าง ๆ มากมายโดยอ้อม แม้แต่งานที่เรียบง่ายเพียงแค่ "ค้นหาการอ้างอิงทั้งหมด" นั้นไม่สำคัญเพราะพวกเขาสามารถซ่อนใน typedefs, macros และอื่น ๆ ไม่แปลกใจเลยที่จะไม่สามารถค้นหารหัสที่ตายได้ง่าย
Darrel Hoffman

1
คุณไม่จำเป็นต้องมีการเรียกใช้เมธอดแบบไดนามิกเพื่อแก้ไขปัญหานี้ เมธอดสาธารณะใด ๆ สามารถเรียกใช้โดยฟังก์ชันที่ยังไม่ได้เขียนซึ่งจะขึ้นอยู่กับคลาสที่คอมไพล์แล้วใน Java หรือ C # หรือภาษาที่คอมไพล์อื่น ๆ ด้วยกลไกบางอย่างสำหรับการลิงก์แบบไดนามิก หากคอมไพเลอร์กำจัดสิ่งเหล่านี้เป็น "รหัสตายตัว" ดังนั้นเราจะไม่สามารถทำแพ็กเกจไลบรารีที่คอมไพล์แล้วล่วงหน้าเพื่อการแจกจ่าย (NuGet, jars, Python ล้อที่มีส่วนประกอบแบบไบนารี)
jpmc26

12

โค้ดที่ไม่มีเงื่อนไขสามารถตรวจจับและลบโดยคอมไพเลอร์ขั้นสูง

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

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


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

6
@Taemyr ถ้างั้นมันก็ไม่เป็นที่ทราบแน่ชัดว่าตายไปแล้วหรือ
JAB

1
@Taemyr คุณดูเหมือนจะเข้าใจผิดว่าคำว่า "ไม่มีเงื่อนไข" หากรหัสตายขึ้นอยู่กับผลลัพธ์ของฟังก์ชั่นก็เป็นรหัสตายตามเงื่อนไข "เงื่อนไข" เป็นผลลัพธ์ของฟังก์ชัน การเป็น "ไม่มีเงื่อนไข" มันจะต้องไม่ขึ้นอยู่กับผลลัพธ์ใด ๆ
Kyeotic

12

ตัวอย่างง่ายๆ:

int readValueFromPort(const unsigned int portNum);

int x = readValueFromPort(0x100); // just an example, nothing meaningful
if (x < 2)
{
    std::cout << "Hey! X < 2" << std::endl;
}
else
{
    std::cout << "X is too big!" << std::endl;
}

ตอนนี้สมมติว่าพอร์ต 0x100 ได้รับการออกแบบให้คืนค่าเป็น 0 หรือ 1 เท่านั้นในกรณีนี้คอมไพเลอร์ไม่สามารถคิดได้ว่าelseบล็อกจะไม่ถูกดำเนินการ

อย่างไรก็ตามในตัวอย่างพื้นฐานนี้:

bool boolVal = /*anything boolean*/;

if (boolVal)
{
  // Do A
}
else if (!boolVal)
{
  // Do B
}
else
{
  // Do C
}

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

แก้ไข

บางครั้งข้อมูลไม่สามารถใช้ได้ในเวลารวบรวม:

// File a.cpp
bool boolMethod();

bool boolVal = boolMethod();

if (boolVal)
{
  // Do A
}
else
{
  // Do B
}

//............
// File b.cpp
bool boolMethod()
{
    return true;
}

ขณะคอมไพล์ a.cpp คอมไพเลอร์ไม่สามารถรู้ได้ boolMethodtrueมักกลับมาเสมอ


1
ในขณะที่ความจริงอย่างเคร่งครัดที่คอมไพเลอร์ไม่ทราบฉันคิดว่ามันอยู่ในจิตวิญญาณของคำถามที่ถามด้วยเช่นกันว่าลิงเกอร์สามารถรู้ได้หรือไม่
Casey Kuball

1
@Darthfett มันไม่ได้เป็นความรับผิดชอบของลิงเกอร์ Linker ไม่ได้วิเคราะห์เนื้อหาของรหัสที่รวบรวม ตัวเชื่อมโยง (พูดโดยทั่วไป) เพียงเชื่อมโยงวิธีการและข้อมูลทั่วโลกมันไม่สนใจเนื้อหา อย่างไรก็ตามคอมไพเลอร์บางตัวมีตัวเลือกในการเชื่อมไฟล์ต้นทาง (เช่น ICC) แล้วทำการเพิ่มประสิทธิภาพ ในกรณีเช่นนี้กรณีที่อยู่ภายใต้การแก้ไขจะครอบคลุม แต่ตัวเลือกนี้จะมีผลต่อเวลาการรวบรวมโดยเฉพาะอย่างยิ่งเมื่อโครงการมีขนาดใหญ่
Alex Lop

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

@AntonGolovIt ระบบปฏิบัติการไม่เป็นความจริงเสมอไป ในหลายกรณีเมื่อมีข้อมูลคอมไพเลอร์สามารถตรวจจับโค้ดที่ตายแล้วและปรับให้เหมาะสม
Alex Lop

@abforce เพียงแค่บล็อกโค้ด มันอาจเป็นอย่างอื่นก็ได้ :)
Alex Lop

4

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


4

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

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


3

ถ้าคอมไพเลอร์สามารถกำจัดรหัสตายทุกอย่างถูกต้องก็จะได้ชื่อว่าเป็นล่าม

พิจารณาสถานการณ์ง่าย ๆ นี้:

if (my_func()) {
  am_i_dead();
}

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

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


หากคุณพิจารณาคอมไพเลอร์เป็นฟังก์ชั่น c()ที่c(source)=compiled codeและสภาพแวดล้อมในการทำงานตามr()ที่แล้วเพื่อตรวจสอบการส่งออกรหัสที่มาที่คุณต้องคำนวณค่าของr(compiled code)=program output r(c(source code))หากการคำนวณc()ต้องการความรู้เกี่ยวกับค่าของr(c())อินพุตใด ๆ ก็ไม่จำเป็นต้องแยกจากกันr()และc()คุณสามารถรับฟังก์ชั่นi()จากc()สิ่งนั้นi(source)=program outputได้


2

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

ใน. NET / Java / JavaScript และสภาพแวดล้อมที่ขับเคลื่อนด้วยรันไทม์อื่น ๆ จะไม่มีการหยุดการโหลดประเภทใด ๆ ผ่านการสะท้อนกลับ สิ่งนี้ได้รับความนิยมจากเฟรมเวิร์กการฉีดแบบพึ่งพาและยิ่งยากที่จะให้เหตุผลเมื่อเผชิญกับการ deserialisation หรือการโหลดโมดูลแบบไดนามิก

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

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


ฉันไม่รู้เกี่ยวกับ Java และ javascript แต่ .NET จริง ๆ แล้วมีปลั๊กอินตัวแก้ไขสำหรับการตรวจจับ DI ชนิดนั้น (เรียกว่า Agent Mulder) แน่นอนว่ามันจะไม่สามารถตรวจจับไฟล์การกำหนดค่า แต่สามารถตรวจจับ confit ในรหัส (ซึ่งเป็นที่นิยมมาก)
ไท

2

ใช้ฟังก์ชั่น

void DoSomeAction(int actnumber) 
{
    switch(actnumber) 
    {
        case 1: Action1(); break;
        case 2: Action2(); break;
        case 3: Action3(); break;
    }
}

คุณสามารถพิสูจน์ได้ว่าactnumberจะไม่เป็น2อย่างนั้นอย่างที่Action2()ไม่เคยถูกเรียกว่า ... ?


7
หากคุณสามารถวิเคราะห์ผู้โทรของฟังก์ชั่นได้คุณก็สามารถทำได้
abligh

2
@ ระดับสูง แต่คอมไพเลอร์มักไม่สามารถวิเคราะห์รหัสการโทรทั้งหมดได้ อย่างไรก็ตามการวิเคราะห์เต็มรูปแบบอาจต้องการเพียงแค่การจำลองกระแสการควบคุมที่เป็นไปได้ทั้งหมดซึ่งเกือบจะเป็นไปไม่ได้เสมอเนื่องจากทรัพยากรและเวลาที่ต้องการ ดังนั้นแม้ว่าในทางทฤษฎีมีอยู่หลักฐานว่า 'a Action2()จะไม่ถูกเรียกว่า' มันเป็นไปไม่ได้ที่จะพิสูจน์ว่าการเรียกร้องในทางปฏิบัติ - ไม่สามารถแก้ไขได้อย่างเต็มที่โดยคอมไพเลอร์ ความแตกต่างเป็นเหมือน 'มีจำนวน X' กับ 'เราสามารถเขียนตัวเลข X เป็นทศนิยม' สำหรับบางคนของ X คนหลังจะไม่เกิดขึ้นแม้ว่าอดีตจะเป็นจริง
CiaPan

นี่เป็นคำตอบที่ไม่ดี คำตอบอื่น ๆพิสูจน์actnumber==2ว่ามันเป็นไปไม่ได้ที่จะทราบว่า คำตอบนี้เพียงอ้างว่ามันยากโดยไม่ต้องระบุความซับซ้อน
MSalters

1

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

ให้ลองพิจารณา:

for (int N = 3;;N++)
  for (int A = 2; A < int.MaxValue; A++)
    for (int B = 2; B < int.MaxValue; B++)
    {
      int Square = Math.Pow(A, N) + Math.Pow(B, N);
      float Test = Math.Sqrt(Square);
      if (Test == Math.Trunc(Test))
        FermatWasWrong();
    }

private void FermatWasWrong()
{
  Press.Announce("Fermat was wrong!");
  Nobel.Claim();
}

(ละเว้นข้อผิดพลาดประเภทและการโอเวอร์โฟลว์) รหัสที่ผิดพลาดหรือไม่


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

@Taemyr Aha! โปรแกรมนี้ทดสอบทฤษฎีบทสุดท้ายของแฟร์มาต์ไม่ถูกต้อง ตัวอย่างการทดสอบสิ่งที่ทำคือ N = 3, A = 65536, B = 65536 (ซึ่งให้ผลการทดสอบ = 0)
253751

@ กลไกใช่ฉันพลาดว่ามันจะล้น int ก่อนความแม่นยำในการลอยกลายเป็นปัญหา
Taemyr

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

-1

ดูตัวอย่างนี้:

public boolean isEven(int i){

    if(i % 2 == 0)
        return true;
    if(i % 2 == 1)
        return false;
    return false;
}

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


1
อืมจริงเหรอ? ถ้าฉันเขียนสิ่งนั้นใน C # + ReSharper ฉันจะได้คำใบ้สองสามข้อ return i%2==0;ต่อไปนี้พวกเขาในที่สุดก็ทำให้ผมมีรหัส
โธมัสเวลเลอร์

10
ตัวอย่างของคุณง่ายเกินไปที่จะเชื่อ กรณีเฉพาะของi % 2 == 0และi % 2 != 0ไม่จำเป็นต้องให้เหตุผลเกี่ยวกับค่าของโมดูโลจำนวนเต็มค่าคงที่ (ซึ่งยังคงง่ายต่อการทำ), มันต้องการเพียงการกำจัด subexpression ทั่วไปและหลักการทั่วไป (canonicalization, แม้) ที่if (cond) foo; if (!cond) bar;สามารถทำให้ง่ายif (cond) foo; else bar;ขึ้น แน่นอนว่า "การเข้าใจความหมาย" เป็นปัญหาที่ยากมาก แต่โพสต์นี้ไม่แสดงว่าเป็นหรือไม่แสดงให้เห็นว่าการแก้ปัญหานี้ยากสำหรับการตรวจจับโค้ดที่ตายแล้ว

5
ในตัวอย่างของคุณคอมไพเลอร์การปรับให้เหมาะสมจะมองเห็นนิพจน์ย่อยทั่วไปi % 2และดึงมันออกมาเป็นตัวแปรชั่วคราว จากนั้นจะรับรู้ว่าทั้งสองifข้อความเป็นเอกสิทธิ์เฉพาะบุคคลและสามารถเขียนเป็นif(a==0)...else...และจากนั้นเห็นว่าเส้นทางการปฏิบัติที่เป็นไปได้ทั้งหมดจะต้องผ่านทั้งสองreturnข้อความแรกดังนั้นข้อความที่สามreturnจึงเป็นรหัสที่ตาย ( คอมไพเลอร์การเพิ่มประสิทธิภาพที่ดียิ่งขึ้นนั้นมีความก้าวร้าวมากขึ้น: GCC เปลี่ยนรหัสทดสอบของฉันเป็นการดำเนินการจัดการบิตคู่)
Mark

1
ตัวอย่างนี้ดีสำหรับฉัน มันแสดงถึงกรณีเมื่อคอมไพเลอร์ไม่ทราบเกี่ยวกับการไหลเวียนของข้อเท็จจริงบางอย่าง if (availableMemory()<0) then {dead code}เดียวกันจะไปสำหรับ
Little Santi

1
@ LittleSanti: จริง ๆ แล้ว GCC จะตรวจพบว่าทุกสิ่งที่คุณเขียนมีรหัสตายตัว! มันไม่ใช่แค่{dead code}ส่วนหนึ่ง GCC ค้นพบสิ่งนี้โดยการพิสูจน์ว่ามีจำนวนเต็มล้นที่มีลายเซ็นที่หลีกเลี่ยงไม่ได้ รหัสทั้งหมดในส่วนโค้งนั้นในกราฟการกระทำจึงเป็นรหัสตาย GCC สามารถลบสาขาที่มีเงื่อนไขซึ่งนำไปสู่ส่วนโค้งนั้นได้
MSalters
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.