Tại sao phát hiện mã chết không thể được giải quyết hoàn toàn bởi trình biên dịch?


192

Các trình biên dịch mà tôi đang sử dụng trong C hoặc Java có chức năng ngăn chặn mã chết (cảnh báo khi một dòng sẽ không được thực thi). Giáo sư của tôi nói rằng vấn đề này không bao giờ có thể được giải quyết hoàn toàn bằng trình biên dịch. Tôi đã tự hỏi tại sao đó là. Tôi không quá quen thuộc với mã hóa thực tế của trình biên dịch vì đây là lớp dựa trên lý thuyết. Nhưng tôi đã tự hỏi những gì họ kiểm tra (chẳng hạn như chuỗi đầu vào có thể so với đầu vào chấp nhận được, v.v.) và tại sao điều đó là không đủ.


91
tạo một vòng lặp, đặt mã sau nó, sau đó áp dụng en.wikipedia.org/wiki/Halting_pro
Hiệu

48
if (isPrime(1234234234332232323423)){callSomething();}mã này sẽ bao giờ gọi một cái gì đó hay không? Có nhiều ví dụ khác, trong đó việc quyết định thời tiết một chức năng được gọi là đắt hơn nhiều so với việc đưa nó vào chương trình.
idclev 463035818

33
public static void main(String[] args) {int counterexample = findCollatzConjectureCounterexample(); System.out.println(counterexample);}<- là println gọi mã chết? Thậm chí con người không thể giải quyết cái đó!
dùng253751

15
@ tobi303 không phải là một ví dụ tuyệt vời, thật dễ dàng để tính các số nguyên tố ... chỉ cần không tính hệ số tương đối hiệu quả. Vấn đề tạm dừng không nằm ở NP, nó không thể giải quyết được.
en_Knight

57
@alephzero và en_Knight - Cả hai bạn đều sai. isPrime là một ví dụ tuyệt vời. Bạn đã đưa ra một giả định rằng hàm đang kiểm tra Số nguyên tố. Có lẽ số đó là một số sê-ri và nó có tra cứu cơ sở dữ liệu để xem người dùng có phải là thành viên của Amazon Prime không? Lý do nó là một ví dụ tuyệt vời là vì cách duy nhất để biết điều kiện có phải là hằng số hay không là thực sự thực hiện hàm isPrime. Vì vậy, bây giờ điều đó sẽ yêu cầu Trình biên dịch cũng phải là một trình thông dịch. Nhưng điều đó vẫn không giải quyết được những trường hợp dữ liệu không ổn định.
Dunk

Câu trả lời:


275

Vấn đề mã chết có liên quan đến vấn đề Dừng .

Alan Turing đã chứng minh rằng không thể viết một thuật toán chung sẽ được cung cấp một chương trình và có thể quyết định liệu chương trình đó có dừng lại cho tất cả các đầu vào hay không. Bạn có thể viết một thuật toán như vậy cho các loại chương trình cụ thể, nhưng không phải cho tất cả các chương trình.

Làm thế nào điều này liên quan đến mã chết?

Vấn đề Dừng có thể giảm bớt đối với vấn đề tìm mã chết. Đó là, nếu bạn tìm thấy một thuật toán có thể phát hiện mã chết trong bất kỳ chương trình nào , thì bạn có thể sử dụng thuật toán đó để kiểm tra xem chương trình nào dừng lại không. Vì điều đó đã được chứng minh là không thể, nên theo sau đó, việc viết một thuật toán cho mã chết là không thể.

Làm thế nào để bạn chuyển một thuật toán cho mã chết thành một thuật toán cho vấn đề Dừng?

Đơn giản: bạn thêm một dòng mã sau khi kết thúc chương trình bạn muốn kiểm tra tạm dừng. Nếu trình phát hiện mã chết của bạn phát hiện ra rằng dòng này đã chết, thì bạn biết rằng chương trình không dừng lại. Nếu không, thì bạn biết rằng chương trình của bạn tạm dừng (đến dòng cuối cùng, và sau đó đến dòng mã được thêm vào của bạn).


Trình biên dịch thường kiểm tra những thứ có thể được chứng minh tại thời điểm biên dịch là đã chết. Ví dụ, các khối phụ thuộc vào các điều kiện có thể được xác định là sai tại thời điểm biên dịch. Hoặc bất kỳ tuyên bố nào sau một return(trong cùng phạm vi).

Đây là những trường hợp cụ thể và do đó có thể viết một thuật toán cho chúng. Có thể viết thuật toán cho các trường hợp phức tạp hơn (như thuật toán kiểm tra xem một điều kiện có phải là mâu thuẫn về mặt cú pháp hay không và do đó sẽ luôn trả về sai), nhưng vẫn không bao gồm tất cả các trường hợp có thể.


8
Tôi sẽ lập luận rằng vấn đề tạm dừng không được áp dụng ở đây, vì mọi nền tảng là mục tiêu biên dịch của mọi trình biên dịch trong thế giới thực đều có lượng dữ liệu tối đa mà nó có thể truy cập, do đó nó sẽ có số lượng trạng thái tối đa có nghĩa là nó trong thực tế là một máy trạng thái hữu hạn, không phải là máy turing. Vấn đề tạm dừng không thể giải quyết được đối với các FSM vì vậy bất kỳ trình biên dịch nào trong thế giới thực đều có thể thực hiện phát hiện mã chết.
Vality

50
@Vality Bộ xử lý 64 bit có thể xử lý 2 ^ 64 byte. Hãy vui vẻ tìm kiếm tất cả 256 ^ (2 ^ 64) tiểu bang!
Daniel Wagner

82
@DanielWagner Đây không phải là một vấn đề. Tìm kiếm 256^(2^64)trạng thái là O(1), vì vậy phát hiện mã chết có thể được thực hiện trong thời gian đa thức.
aebabis

13
@Leliel, đó là mỉa mai.
Paul Draper

44
@Vality: Hầu hết các máy tính hiện đại đều có đĩa, thiết bị đầu vào, giao tiếp mạng, v.v ... Mọi phân tích hoàn chỉnh sẽ phải xem xét tất cả các thiết bị như vậy - bao gồm, theo nghĩa đen, internet và mọi thứ được nối với nó. Đây không phải là một vấn đề có thể kéo được.
Nat

77

Chà, hãy lấy bằng chứng cổ điển về tính không ổn định của vấn đề tạm dừng và thay đổi máy dò tạm dừng thành máy dò mã chết!

Chương trình 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!");
        }
    }
}

Nếu YourVendor.Compiler.HasDeadCode(quine_text)lợi nhuận false, sau đó dòng System.Console.WriteLn("Dead code!");sẽ không được bao giờ thực hiện, vì vậy chương trình này thực sự không có mã chết, và các máy dò đã sai.

Nhưng nếu nó trả về true, thì dòng System.Console.WriteLn("Dead code!");sẽ được thực thi và vì không có thêm mã trong chương trình, nên không có mã chết nào cả, vì vậy một lần nữa, trình phát hiện đã sai.

Vì vậy, bạn có nó, một trình phát hiện mã chết chỉ trả về "Có mã chết" hoặc "Không có mã chết" đôi khi phải đưa ra câu trả lời sai.


1
Nếu tôi đã hiểu chính xác đối số của bạn, thì về mặt kỹ thuật, một lựa chọn khác là không thể viết một công cụ tìm mã chết, nhưng có thể viết trình phát hiện mã chết trong trường hợp chung. :-)
rút ngắn

1
gia tăng cho câu trả lời Godelian.
Jared Smith

@ableigh Ugh, đó là một lựa chọn không tốt về từ ngữ. Tôi không thực sự cung cấp mã nguồn của trình phát hiện mã chết cho chính nó, mà là mã nguồn của chương trình sử dụng nó. Chắc chắn, đến một lúc nào đó có lẽ nó sẽ phải xem mã riêng của nó, nhưng đó là việc của nó.
Joker_vD

65

Nếu vấn đề tạm dừng quá mơ hồ, hãy nghĩ về nó theo cách này.

Đi một vấn đề toán học mà được tin là đúng đối với tất cả các số nguyên dương của n , nhưng chưa được chứng minh là đúng đối với mỗi n . Một ví dụ điển hình sẽ là phỏng đoán của Goldbach , rằng bất kỳ số nguyên dương nào lớn hơn hai có thể được biểu diễn bằng tổng của hai số nguyên tố. Sau đó (với một thư viện bigint thích hợp) chạy chương trình này (mã giả theo sau):

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

Việc thực hiện isGoldbachsConjectureTrueFor()được để lại như một bài tập cho người đọc nhưng với mục đích này có thể là một phép lặp đơn giản trên tất cả các số nguyên tố ít hơnn

Bây giờ, về mặt logic ở trên phải tương đương với:

 for (; ;) {
 }

(tức là một vòng lặp vô hạn) hoặc

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

theo phỏng đoán của Goldbach phải đúng hoặc không đúng. Nếu một trình biên dịch luôn có thể loại bỏ mã chết, chắc chắn sẽ có mã chết để loại bỏ ở đây trong cả hai trường hợp. Tuy nhiên, khi làm như vậy ít nhất trình biên dịch của bạn sẽ cần phải giải quyết các vấn đề khó khăn tùy ý. Chúng tôi có thể cung cấp những vấn đề có thể chứng minh khó khăn mà nó sẽ phải giải quyết (vấn đề ví dụ như NP-đầy đủ) để xác định chút mã để loại bỏ. Ví dụ: nếu chúng tôi thực hiện chương trình này:

 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");

chúng tôi biết rằng chương trình sẽ in ra "Giá trị SHA được tìm thấy" hoặc "Không tìm thấy giá trị SHA" (điểm thưởng nếu bạn có thể cho tôi biết cái nào là đúng). Tuy nhiên, để một trình biên dịch có thể tối ưu hóa một cách hợp lý sẽ có thứ tự lặp lại 2 ^ 2048. Thực tế nó sẽ là một sự tối ưu hóa tuyệt vời khi tôi dự đoán chương trình trên sẽ (hoặc có thể) chạy cho đến khi cái chết nóng của vũ trụ thay vì in bất cứ thứ gì mà không tối ưu hóa.


4
Đó là câu trả lời tốt nhất cho đến nay +1
jean

2
Điều khiến mọi thứ trở nên đặc biệt thú vị là sự mơ hồ về những gì mà Tiêu chuẩn C cho phép hoặc không cho phép khi giả định rằng các vòng lặp sẽ chấm dứt. Có giá trị trong việc cho phép trình biên dịch trì hoãn các phép tính chậm mà kết quả của chúng có thể hoặc không thể được sử dụng cho đến thời điểm mà kết quả của chúng thực sự cần thiết; tối ưu hóa này trong một số trường hợp có thể hữu ích ngay cả khi trình biên dịch không thể chứng minh các phép tính chấm dứt.
supercat

2
2 ^ 2048 lần lặp? Ngay cả tư tưởng sâu sắc cũng sẽ từ bỏ.
Peter Mortensen

Nó sẽ in "Tìm thấy giá trị SHA" với xác suất rất cao, ngay cả khi mục tiêu đó là một chuỗi ngẫu nhiên gồm 64 chữ số hex. Trừ khi sha256trả về một mảng byte và mảng byte không so sánh bằng các chuỗi trong ngôn ngữ của bạn.
dùng253751

4
Implementation of isGoldbachsConjectureTrueFor() is left as an exercise for the readerĐiều này làm tôi cười thầm.
biziclop

34

Tôi không biết nếu C ++ hoặc Java có Evalhàm loại, nhưng nhiều ngôn ngữ cho phép bạn thực hiện các phương thức gọi theo tên . Hãy xem xét ví dụ VBA (giả định) sau đây.

Dim methodName As String

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

Application.Run(methodName)

Tên của phương thức được gọi là không thể biết cho đến khi thời gian chạy. Do đó, theo định nghĩa, trình biên dịch không thể biết chắc chắn rằng một phương thức cụ thể không bao giờ được gọi.

Trên thực tế, được đưa ra ví dụ về cách gọi một phương thức theo tên, logic phân nhánh thậm chí không cần thiết. Nói đơn giản

Application.Run("Bar")

Là nhiều hơn trình biên dịch có thể xác định. Khi mã được biên dịch, tất cả các trình biên dịch đều biết rằng một giá trị chuỗi nhất định đang được truyền cho phương thức đó. Nó không kiểm tra xem phương thức đó có tồn tại cho đến khi runtime không. Nếu phương thức không được gọi ở nơi khác, thông qua các phương thức bình thường hơn, một nỗ lực tìm phương thức chết có thể trả về kết quả dương tính giả. Vấn đề tương tự tồn tại trong bất kỳ ngôn ngữ nào cho phép mã được gọi thông qua sự phản chiếu.


2
Trong Java (hoặc C #), điều này có thể được thực hiện với sự phản chiếu. C ++ có lẽ bạn có thể rút ra một số điều khó chịu bằng cách sử dụng macro để làm điều đó. Sẽ không đẹp, nhưng hiếm khi C ++ thì có.
Darrel Hoffman

6
@DarrelHoffman - Macro được mở rộng trước khi mã được trao cho trình biên dịch, vì vậy macro chắc chắn không phải là cách bạn sẽ làm điều này. Con trỏ đến các chức năng là cách bạn sẽ làm điều này. Tôi đã không sử dụng C ++ trong nhiều năm vì vậy xin lỗi nếu tên loại chính xác của tôi sai, nhưng bạn chỉ có thể lưu trữ bản đồ chuỗi cho con trỏ hàm. Sau đó, có một cái gì đó chấp nhận một chuỗi từ đầu vào của người dùng, tìm kiếm chuỗi đó trong bản đồ và sau đó thực hiện chức năng được chỉ vào.
ArtOfWarfare

1
@ArtOfWarfare chúng tôi không nói về cách nó có thể được thực hiện. Rõ ràng, phân tích ngữ nghĩa của mã có thể được thực hiện để tìm ra tình huống này, vấn đề là trình biên dịch không . Nó có thể, có thể, có thể, nhưng nó không.
RubberDuck

3
@ArtOfWarfare: Nếu bạn muốn nittc, chắc chắn. Tôi coi bộ tiền xử lý là một phần của trình biên dịch, mặc dù tôi biết về mặt kỹ thuật thì không. Dù sao đi nữa, các con trỏ hàm có thể phá vỡ quy tắc rằng các hàm không được tham chiếu trực tiếp ở bất cứ đâu - chúng giống như một con trỏ thay vì một cuộc gọi trực tiếp, giống như một đại biểu trong C #. Nói chung, C ++ khó đoán hơn nhiều đối với trình biên dịch vì nó có quá nhiều cách để thực hiện một cách gián tiếp. Ngay cả các tác vụ đơn giản như "tìm tất cả các tham chiếu" cũng không tầm thường, vì chúng có thể ẩn trong typedefs, macro, v.v. Không có gì ngạc nhiên khi nó không thể tìm thấy mã chết một cách dễ dàng.
Darrel Hoffman

1
Bạn thậm chí không cần các cuộc gọi phương thức động để đối mặt với vấn đề này. Bất kỳ phương thức công khai nào cũng có thể được gọi bởi một hàm chưa được viết, nó sẽ phụ thuộc vào lớp đã được biên dịch trong Java hoặc C # hoặc bất kỳ ngôn ngữ được biên dịch nào khác với một số cơ chế để liên kết động. Nếu trình biên dịch loại bỏ chúng là "mã chết", thì chúng ta sẽ không thể đóng gói các thư viện được biên dịch sẵn để phân phối (NuGet, jars, Python wheel với thành phần nhị phân).
jpmc26

12

Mã chết vô điều kiện có thể được phát hiện và loại bỏ bởi các trình biên dịch nâng cao.

Nhưng cũng có mã chết có điều kiện. Đó là mã không thể được biết tại thời điểm biên dịch và chỉ có thể được phát hiện trong thời gian chạy. Ví dụ: một phần mềm có thể được cấu hình để bao gồm hoặc loại trừ các tính năng nhất định tùy thuộc vào sở thích của người dùng, làm cho các phần mã nhất định dường như đã chết trong các tình huống cụ thể. Đó không phải là mã chết thực sự.

Có các công cụ cụ thể có thể thực hiện kiểm tra, giải quyết các phụ thuộc, loại bỏ mã chết có điều kiện và kết hợp lại mã hữu ích trong thời gian chạy để đạt hiệu quả. Điều này được gọi là loại bỏ mã chết động. Nhưng như bạn có thể thấy nó nằm ngoài phạm vi của trình biên dịch.


5
"Mã chết vô điều kiện có thể được phát hiện và loại bỏ bởi các trình biên dịch nâng cao." Điều này dường như không có khả năng. Độ chết của mã có thể phụ thuộc vào kết quả của một hàm nhất định và hàm đó đã cho có thể giải quyết các vấn đề tùy ý. Vì vậy, tuyên bố của bạn khẳng định rằng trình biên dịch nâng cao có thể giải quyết các vấn đề tùy ý.
Taemyr

6
@Taemyr Sau đó, nó sẽ không được biết là đã chết vô điều kiện, bây giờ phải không?
JAB

1
@Taemyr Bạn dường như hiểu nhầm từ "vô điều kiện". Nếu độ chết của mã phụ thuộc vào kết quả của hàm, thì đó là mã chết có điều kiện. "Điều kiện" là kết quả của hàm. Để trở thành "vô điều kiện", nó sẽ không phụ thuộc vào bất kỳ kết quả nào.
Kyeotic

12

Một ví dụ đơn giản:

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;
}

Bây giờ giả sử rằng cổng 0x100 được thiết kế để chỉ trả về 0 hoặc 1. Trong trường hợp đó, trình biên dịch không thể tìm ra rằng elsekhối sẽ không bao giờ được thực thi.

Tuy nhiên trong ví dụ cơ bản này:

bool boolVal = /*anything boolean*/;

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

Ở đây trình biên dịch có thể tính toán elsekhối là một mã chết. Vì vậy, trình biên dịch có thể cảnh báo về mã chết chỉ khi nó có đủ dữ liệu để tìm ra mã chết và cũng nên biết cách áp dụng dữ liệu đó để tìm ra liệu khối đã cho có phải là mã chết hay không.

BIÊN TẬP

Đôi khi dữ liệu không có sẵn tại thời điểm biên dịch:

// File a.cpp
bool boolMethod();

bool boolVal = boolMethod();

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

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

Trong khi biên dịch a.cpp, trình biên dịch không thể biết rằng boolMethodluôn trả về true.


1
Mặc dù đúng là trình biên dịch không biết, tôi nghĩ rằng đó là trên tinh thần của câu hỏi để hỏi liệu trình liên kết có thể biết.
Casey Kuball

1
@Darthfett Đây không phải là trách nhiệm của người liên kết . Linker không phân tích nội dung của mã được biên dịch. Trình liên kết (nói chung) chỉ liên kết các phương thức và dữ liệu toàn cầu, nó không quan tâm đến nội dung. Tuy nhiên, một số trình biên dịch có tùy chọn nối các tệp nguồn (như ICC) và sau đó thực hiện tối ưu hóa. Trong trường hợp như vậy, trường hợp theo EDIT được bảo hiểm nhưng tùy chọn này sẽ ảnh hưởng đến thời gian biên dịch đặc biệt là khi dự án lớn.
Alex Lop.

Câu trả lời này có vẻ gây hiểu lầm cho tôi; bạn đang đưa ra hai ví dụ không thể thực hiện được vì không phải tất cả thông tin đều có sẵn, nhưng bạn không nên nói rằng điều đó là không thể ngay cả khi thông tin có ở đó không?
Anton Golov

@AntonGolovIt os không phải lúc nào cũng đúng. Trong nhiều trường hợp khi có thông tin, trình biên dịch có thể phát hiện mã chết và tối ưu hóa nó.
Alex Lop.

@abforce chỉ là một khối mã. Nó có thể là bất cứ điều gì khác. :)
Alex Lop.

4

Trình biên dịch sẽ luôn thiếu một số thông tin ngữ cảnh. Ví dụ, bạn có thể biết rằng giá trị kép không bao giờ vượt quá 2, vì đó là một tính năng của hàm toán học, bạn sử dụng từ thư viện. Trình biên dịch thậm chí không nhìn thấy mã trong thư viện và nó không bao giờ có thể biết tất cả các tính năng của tất cả các hàm toán học và phát hiện tất cả các cách thức phức tạp và phức tạp để thực hiện chúng.


4

Trình biên dịch không nhất thiết phải xem toàn bộ chương trình. Tôi có thể có một chương trình gọi một thư viện dùng chung, gọi lại một chức năng trong chương trình của tôi không được gọi trực tiếp.

Vì vậy, một chức năng đã chết đối với thư viện mà nó được biên dịch có thể trở nên sống động nếu thư viện đó bị thay đổi khi chạy.


3

Nếu một trình biên dịch có thể loại bỏ tất cả mã chết một cách chính xác, nó sẽ được gọi là trình thông dịch .

Hãy xem xét kịch bản đơn giản này:

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

my_func() có thể chứa mã tùy ý và để trình biên dịch xác định xem nó trả về đúng hay sai, nó sẽ phải chạy mã hoặc làm một cái gì đó có chức năng tương đương với việc chạy mã.

Ý tưởng của trình biên dịch là nó chỉ thực hiện phân tích một phần mã, do đó đơn giản hóa công việc của một môi trường chạy riêng biệt. Nếu bạn thực hiện phân tích đầy đủ, đó không còn là trình biên dịch nữa.


Nếu bạn coi trình biên dịch là một hàm c(), ở đâu c(source)=compiled codevà môi trường đang chạy như là r()ở đâu r(compiled code)=program output, thì để xác định đầu ra cho bất kỳ mã nguồn nào bạn phải tính giá trị của r(c(source code)). Nếu việc tính toán c()đòi hỏi kiến ​​thức về giá trị của r(c())bất kỳ đầu vào nào, thì không cần riêng biệt r()c(): bạn chỉ có thể lấy được một hàm i()từ c()đó i(source)=program output.


2

Những người khác đã bình luận về vấn đề tạm dừng và vv. Chúng thường áp dụng cho các phần của chức năng. Tuy nhiên, có thể khó / không thể biết liệu thậm chí toàn bộ một loại (lớp / vv) có được sử dụng hay không.

Trong .NET / Java / JavaScript và các môi trường điều khiển thời gian chạy khác, không có kiểu dừng nào được tải thông qua sự phản chiếu. Điều này là phổ biến với các khung tiêm phụ thuộc, và thậm chí còn khó hơn để giải thích khi đối mặt với quá trình khử lưu huỳnh hoặc tải mô-đun động.

Trình biên dịch không thể biết liệu các loại như vậy sẽ được tải. Tên của họ có thể đến từ các tập tin cấu hình bên ngoài trong thời gian chạy.

Bạn có thể muốn tìm kiếm xung quanh cây rung chuyển , đó là một thuật ngữ phổ biến cho các công cụ cố gắng loại bỏ một cách an toàn các sơ đồ mã không sử dụng.


Tôi không biết về Java và javascript, nhưng .NET thực sự có một plugin chia sẻ lại cho loại phát hiện DI đó (được gọi là Agent Mulder). Tất nhiên, nó sẽ không thể phát hiện các tệp cấu hình, nhưng nó có thể phát hiện confit trong mã (phổ biến hơn nhiều).
Ties

2

Thực hiện một chức năng

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

Bạn có thể chứng minh rằng actnumbersẽ không bao giờ được 2như vậy mà Action2()không bao giờ được gọi là ...?


7
Nếu bạn có thể phân tích những người gọi hàm, thì bạn có thể, vâng.
bỏ qua

2
@ableigh Nhưng trình biên dịch thường không thể phân tích tất cả các mã gọi. Dù sao, ngay cả khi có thể, phân tích đầy đủ có thể chỉ cần một mô phỏng của tất cả các luồng điều khiển có thể, điều này hầu như luôn luôn là không thể do tài nguyên và thời gian cần thiết. Vì vậy, ngay cả khi về mặt lý thuyết tồn tại một bằng chứng rằng ' Action2()sẽ không bao giờ được gọi', không thể chứng minh yêu cầu trong thực tế - không thể giải quyết hoàn toàn bằng trình biên dịch . Sự khác biệt giống như "tồn tại một số X" so với "chúng ta có thể viết số X theo số thập phân". Đối với một số X, điều sau sẽ không bao giờ xảy ra mặc dù điều trước là đúng.
CiaPan

Đây là một câu trả lời kém. các câu trả lời khác chứng minh rằng không thể biết được actnumber==2. Câu trả lời này chỉ đơn thuần khẳng định nó khó mà không nêu rõ sự phức tạp.
MSalters

1

Tôi không đồng ý về vấn đề tạm dừng. Tôi sẽ không gọi mã như vậy là chết mặc dù trong thực tế, nó sẽ không bao giờ đạt được.

Thay vào đó, hãy xem xét:

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();
}

(Bỏ qua lỗi loại và lỗi tràn) Mã chết?


2
Định lý cuối cùng của Fermat đã được chứng minh vào năm 1994. Vì vậy, việc thực hiện đúng phương pháp của bạn sẽ không bao giờ chạy FermatWasWrong. Tôi nghi ngờ việc triển khai của bạn sẽ chạy FermatWasWrong, bởi vì bạn có thể đạt đến giới hạn độ chính xác của phao.
Taemyr

@Taemyr Aha! Chương trình này không kiểm tra chính xác Định lý cuối cùng của Fermat; một ví dụ cho những gì nó kiểm tra là N = 3, A = 65536, B = 65536 (mang lại Test = 0)
user253751

@immibis Vâng, tôi đã bỏ lỡ rằng nó sẽ tràn int trước khi độ chính xác trên phao trở thành một vấn đề.
Taemyr

@immibis Lưu ý cuối bài viết của tôi: Bỏ qua lỗi loại và lỗi tràn. Tôi chỉ lấy những gì tôi nghĩ là một vấn đề chưa được giải quyết làm cơ sở cho một quyết định - tôi biết mã không hoàn hảo. Dù sao đó cũng là một vấn đề không thể bị ép buộc.
Loren Pechtel

-1

Nhìn vào ví dụ này:

public boolean isEven(int i){

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

Trình biên dịch không thể biết rằng một int chỉ có thể là chẵn hoặc lẻ. Do đó, trình biên dịch phải có khả năng hiểu ngữ nghĩa của mã của bạn. Làm thế nào điều này nên được thực hiện? Trình biên dịch không thể đảm bảo rằng lợi nhuận thấp nhất sẽ không bao giờ được thực hiện. Do đó trình biên dịch không thể phát hiện mã chết.


1
Ừm, thật sao? Nếu tôi viết điều đó trong C # + ReSharper, tôi sẽ nhận được một vài gợi ý. Theo họ cuối cùng cho tôi mã return i%2==0;.
Thomas Weller

10
Ví dụ của bạn quá đơn giản để có thể thuyết phục. Trường hợp cụ thể i % 2 == 0i % 2 != 0thậm chí không yêu cầu lý luận về giá trị của một số nguyên modulo một hằng số (vẫn dễ thực hiện), nó chỉ yêu cầu loại bỏ phổ biến phụ và nguyên tắc chung (chuẩn hóa, thậm chí) if (cond) foo; if (!cond) bar;có thể được đơn giản hóa if (cond) foo; else bar;. Tất nhiên "hiểu ngữ nghĩa" là một vấn đề rất khó, nhưng bài đăng này không cho thấy rằng nó cũng như cho thấy việc giải quyết vấn đề khó khăn này là cần thiết để phát hiện mã chết.

5
Trong ví dụ của bạn, một trình biên dịch tối ưu hóa sẽ phát hiện ra biểu hiện con chung i % 2và kéo nó ra thành một biến tạm thời. Sau đó, nó sẽ nhận ra rằng hai ifcâu lệnh là loại trừ lẫn nhau và có thể được viết dưới dạng if(a==0)...else..., và sau đó phát hiện ra rằng tất cả các đường dẫn thực thi có thể đi qua hai returncâu lệnh đầu tiên và do đó returncâu lệnh thứ ba là mã chết. (Một trình biên dịch tối ưu hóa tốt thậm chí còn mạnh mẽ hơn: GCC đã biến mã kiểm tra của tôi thành một cặp thao tác thao tác bit).
Đánh dấu

1
Ví dụ này là tốt cho tôi. Nó đại diện cho trường hợp khi một trình biên dịch không biết về một số trường hợp thực tế. Cũng vậy if (availableMemory()<0) then {dead code}.
Little Santi

1
@LittleSanti: Thật ra, GCC sẽ phát hiện ra rằng mọi thứ bạn viết đều có mã chết! Nó không chỉ là {dead code}một phần. GCC phát hiện ra điều này bằng cách chứng minh có tràn số nguyên đã ký không thể tránh khỏi. Do đó, tất cả mã trên cung đó trong biểu đồ thực thi là mã chết. GCC thậm chí có thể loại bỏ nhánh có điều kiện dẫn đến vòng cung đó.
MSalters
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.