Tại sao không thể xây dựng một trình biên dịch có thể xác định xem một hàm C ++ có thay đổi giá trị của một biến cụ thể hay không?


104

Tôi đọc dòng này trong một cuốn sách:

Rõ ràng là không thể xây dựng một trình biên dịch thực sự có thể xác định xem một hàm C ++ có thay đổi giá trị của một biến cụ thể hay không.

Đoạn văn đã nói về lý do tại sao trình biên dịch thận trọng khi kiểm tra const-ness.

Tại sao không thể xây dựng một trình biên dịch như vậy?

Trình biên dịch luôn có thể kiểm tra xem một biến có được gán lại hay không, một hàm không phải const đang được gọi trên đó hoặc nếu nó đang được chuyển vào dưới dạng tham số không const ...


24
Điều đầu tiên tôi nghĩ đến là các thư viện liên kết động. Nếu tôi biên dịch mã trên máy của mình và bạn biên dịch mã trên máy của bạn và chúng tôi liên kết chúng vào lúc chạy , thì làm sao trình biên dịch của bạn có thể biết liệu tôi đã sửa đổi các biến hay không?
Vịt rên rỉ

4
@MooingDuck Chính xác là điều này. Nói rộng hơn, trình biên dịch không biên dịch chức năng riêng lẻ, nhưng biên dịch nó như một phần của bức tranh rộng hơn có thể không phải tất cả đều nằm trong phạm vi của trình biên dịch.
call2voyage

3
"không thể" có thể là một cách nói quá - "không khả thi về mặt tính toán" (như trong NP-hard) có thể là một đặc điểm tốt hơn, nhưng học sinh khó nắm bắt hơn một chút. Hãy tưởng tượng một danh sách được liên kết hoặc cấu trúc dữ liệu trừu tượng khác. Nếu tôi gọi một hàm thay đổi một nút trong danh sách / cây / bất cứ thứ gì đó, thì làm sao một trình biên dịch có thể hy vọng chứng minh chính xác nút nào đã được sửa đổi (và có thể quan trọng hơn, nút nào không) mà không mô phỏng đầy đủ chương trình về cơ bản đầu vào dự kiến, tất cả trong khi không dùng đến 3 ngày để biên dịch một file nguồn ...
twalberg

36
@twalberg Không thể không phải là một lời nói quá, vấn đề Tạm dừng áp dụng ở đây khi một số câu trả lời giải thích. Đơn giản là không thể phân tích đầy đủ về mặt thuật toán một chương trình chung.
Fiktik

5
@twalberg Trình biên dịch chỉ biên dịch một tập hợp con các chương trình hợp lệ không hữu ích lắm.
Caleb

Câu trả lời:


139

Tại sao không thể xây dựng một trình biên dịch như vậy?

Vì lý do tương tự mà bạn không thể viết một chương trình sẽ xác định xem bất kỳ chương trình nhất định nào sẽ kết thúc hay không. Đây được gọi là vấn đề tạm dừng , và đó là một trong những điều không thể tính toán được.

Để rõ ràng hơn, bạn có thể viết một trình biên dịch có thể xác định rằng một hàm có thay đổi biến trong một số trường hợp , nhưng bạn không thể viết một trình biên dịch cho bạn biết một cách đáng tin cậy rằng hàm sẽ hoặc sẽ không thay đổi biến (hoặc tạm dừng) đối với mọi chức năng có thể.

Đây là một ví dụ dễ hiểu:

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

Làm thế nào một trình biên dịch có thể xác định, chỉ từ việc nhìn vào đoạn mã đó, liệu có foobao giờ thay đổi akhông? Cho dù nó có hay không phụ thuộc vào các điều kiện bên ngoài hàm, cụ thể là việc thực hiện bar. Còn nhiều điều hơn thế bằng chứng rằng vấn đề tạm dừng không thể tính toán được, nhưng nó đã được giải thích độc đáo tại bài viết Wikipedia được liên kết (và trong mọi sách giáo khoa lý thuyết tính toán), vì vậy tôi sẽ không cố gắng giải thích nó một cách chính xác ở đây.


48
@mrsoltys, máy tính lượng tử "chỉ" nhanh hơn theo cấp số nhân đối với một số vấn đề, chúng không thể giải quyết các vấn đề không thể giải quyết được.
zch

8
@mrsoltys Những thuật toán phức tạp theo cấp số nhân (như bao thanh toán) là hoàn hảo cho máy tính lượng tử, nhưng vấn đề tạm dừng là một bài toán khó logic, nó không thể tính được cho dù bạn có "máy tính" nào đi chăng nữa.
user1032613

7
@mrsoltys, chỉ để trở thành một người thông minh, vâng, nó sẽ thay đổi. Thật không may, điều đó có nghĩa là thuật toán vừa bị kết thúc và vẫn đang chạy, thật không may, bạn không thể biết được điều nào nếu không trực tiếp quan sát, điều này bạn ảnh hưởng đến trạng thái thực tế.
Nathan Ernst

9
@ ThorbjørnRavnAndersen: Được rồi, giả sử tôi đang thực thi một chương trình. Làm cách nào để xác định chính xác liệu nó sẽ chấm dứt?
ruakh

8
@ ThorbjørnRavnAndersen Nhưng nếu bạn thực sự thực thi chương trình và nó không kết thúc (ví dụ: vòng lặp vô hạn), bạn sẽ không bao giờ phát hiện ra rằng nó không kết thúc ... bạn chỉ cần tiếp tục thực hiện thêm một bước nữa, bởi vì nó có thể người cuối cùng ...
MaxAxeHax

124

Hãy tưởng tượng trình biên dịch như vậy tồn tại. Cũng giả sử rằng để thuận tiện, nó cung cấp một hàm thư viện trả về 1 nếu hàm được truyền vào sửa đổi một biến nhất định và 0 khi hàm không sửa đổi. Sau đó, những gì chương trình này nên in?

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
Đẹp! Các Tôi là một nghịch lý kẻ nói dối như được viết bởi một lập trình viên.
Krumelur

28
Nó thực sự chỉ là một sự thích nghi tốt đẹp của bằng chứng nổi tiếng về tính không xác thực của vấn đề tạm dừng .
Konstantin Weitz,

10
Trong trường hợp cụ thể này, "modify_variable" phải trả về true: Có ít nhất một đường dẫn thực thi trong đó biến thực sự được sửa đổi. Và đường dẫn thực thi đó đạt được sau một lệnh gọi đến một hàm bên ngoài, không xác định - vì vậy toàn bộ hàm là không xác định. Vì 2 lý do này, trình biên dịch nên có quan điểm bi quan và quyết định nó có sửa đổi biến hay không. Nếu đường dẫn đến thay đổi biến được đạt được sau một so sánh xác định (có thể kiểm chứng bởi trình biên dịch) mang lại sai (tức là "1 == 1") sau đó trình biên dịch một cách an toàn có thể nói chức năng như vậy không bao giờ Sửa biến
Joe Pineda

6
@JoePineda: Câu hỏi đặt ra là liệu có fsửa đổi biến hay không - chứ không phải liệu nó có thể sửa đổi biến hay không. Câu trả lời này là chính xác.
Neil G

4
@JoePineda: không có gì ngăn cản tôi sao chép / dán mã modifies_variabletừ nguồn trình biên dịch, hoàn toàn vô hiệu hóa đối số của bạn. (giả sử là mã nguồn mở, nhưng vấn đề phải rõ ràng)
orlp

60

Đừng nhầm lẫn "sẽ hoặc sẽ không sửa đổi một biến với các đầu vào này" cho "có một đường dẫn thực thi sửa đổi một biến."

Điều trước đây được gọi là xác định vị từ không rõ ràng và không thể quyết định được - ngoài việc giảm thiểu vấn đề tạm dừng, bạn chỉ có thể chỉ ra các đầu vào có thể đến từ một nguồn không xác định (ví dụ: người dùng). Điều này đúng với tất cả các ngôn ngữ, không chỉ C ++.

Tuy nhiên, câu lệnh thứ hai có thể được xác định bằng cách xem xét cây phân tích cú pháp, đây là điều mà tất cả các trình biên dịch tối ưu hóa đều làm. Lý do họ làm là các hàm thuần túy (và các hàm trong suốt tham chiếu , đối với một số định nghĩa về minh bạch tham chiếu ) có tất cả các loại tối ưu hóa tốt có thể được áp dụng, như dễ nhập vào hoặc xác định giá trị của chúng tại thời điểm biên dịch; nhưng để biết một hàm có thuần túy hay không, chúng ta cần biết liệu nó có thể sửa đổi một biến hay không.

Vì vậy, những gì có vẻ là một tuyên bố đáng ngạc nhiên về C ++ thực sự là một tuyên bố tầm thường về tất cả các ngôn ngữ.


5
Đây là câu trả lời tốt nhất imho, điều quan trọng là phải phân biệt được điều đó.
UncleZeiv

"không thể tầm thường"?
Kip

2
@Kip "tầm thường không thể quyết định" có lẽ có nghĩa là "không thể quyết định, và bằng chứng là tầm thường".
fredoverflow

28

Tôi nghĩ từ khóa trong "liệu một hàm C ++ có thay đổi giá trị của một biến cụ thể hay không" là "will". Chắc chắn có thể xây dựng một trình biên dịch kiểm tra xem một hàm C ++ có được phép thay đổi giá trị của một biến cụ thể hay không, bạn không thể nói chắc chắn rằng thay đổi sẽ xảy ra:

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

"Chắc chắn có thể xây dựng một trình biên dịch để kiểm tra xem một hàm C ++ có thể thay đổi giá trị của một biến cụ thể hay không" Không, không phải vậy. Hãy xem câu trả lời của Caleb. Đối với một trình biên dịch để biết liệu foo () có thể thay đổi a hay không, nó sẽ phải biết liệu bar () có thể trả về 0. Và không có hàm tính toán nào có thể cho biết tất cả các giá trị trả về có thể có của bất kỳ hàm tính toán nào. Vì vậy, tồn tại các đường dẫn mã mà trình biên dịch sẽ không thể biết liệu chúng có bao giờ được truy cập hay không. Nếu một biến được thay đổi chỉ trong một đường dẫn mã mà không thể đạt được nó sẽ không thay đổi, nhưng một trình biên dịch sẽ không phát hiện nó
Martin Epsz

12
@MartinEpsz Bởi "có thể" Ý tôi là "được phép thay đổi", không phải "có thể thay đổi". Tôi tin rằng đây là những gì OP đã nghĩ đến khi nói về constkiểm tra -ness.
dasblinkenlight

@dasblinkenlight Tôi sẽ phải đồng ý rằng tôi tin rằng OP có thể có nghĩa là người đầu tiên, "được phép thay đổi", hoặc "có thể hoặc không thay đổi" so với "chắc chắn sẽ không thay đổi". Tất nhiên tôi không thể nghĩ ra một kịch bản mà đây sẽ là một vấn đề. Bạn thậm chí có thể sửa đổi trình biên dịch để chỉ cần trả lời "có thể thay đổi" trên bất kỳ hàm nào có chứa mã định danh hoặc lệnh gọi hàm có thuộc tính trả lời "có thể thay đổi". Điều đó nói rằng, C và C ++ là những ngôn ngữ khủng khiếp để thử điều này, vì chúng có định nghĩa lỏng lẻo về mọi thứ. Tôi nghĩ đây là lý do tại sao const-ness sẽ là một vấn đề trong C ++.
DDS

@MartinEpsz: "Và không có hàm tính toán nào có thể cho biết tất cả các giá trị trả về có thể có của bất kỳ hàm tính toán nào". Tôi nghĩ rằng việc kiểm tra "tất cả các giá trị trả về có thể có" là một cách tiếp cận không chính xác. Có những hệ thống toán học (maxa, mathlab) có thể giải các phương trình, có nghĩa là sẽ có ý nghĩa nếu áp dụng cách tiếp cận tương tự cho các hàm. Tức là coi nó như một phương trình với một số ẩn số. Vấn đề là kiểm soát dòng chảy + tác dụng phụ => tình huống nan giải. IMO, nếu không có những thứ đó (ngôn ngữ chức năng, không có nhiệm vụ / tác dụng phụ), có thể dự đoán chương trình sẽ đi theo con đường nào
SigTerm

16

Tôi không nghĩ rằng cần phải gọi vấn đề tạm dừng để giải thích rằng bạn không thể biết về mặt thuật toán tại thời điểm biên dịch liệu một hàm đã cho có sửa đổi một biến nhất định hay không.

Thay vào đó, đủ để chỉ ra rằng hành vi của một hàm thường phụ thuộc vào các điều kiện thời gian chạy mà trình biên dịch không thể biết trước. Ví dụ

int y;

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

Làm thế nào trình biên dịch có thể dự đoán một cách chắc chắn liệu ysẽ được sửa đổi?


7

Nó có thể được thực hiện và các trình biên dịch luôn làm việc đó cho một số chức năng , ví dụ như đây là một sự tối ưu hóa nhỏ cho các trình truy cập nội tuyến đơn giản hoặc nhiều chức năng thuần túy.

Điều gì là không thể là biết nó trong trường hợp chung.

Bất cứ khi nào có một lệnh gọi hệ thống hoặc một lệnh gọi hàm đến từ một mô-đun khác hoặc một lệnh gọi đến một phương thức có khả năng bị ghi đè, bất cứ điều gì có thể xảy ra, bao gồm cả việc tiếp quản thù địch từ việc sử dụng tràn ngăn xếp để thay đổi một biến không liên quan.

Tuy nhiên, bạn nên sử dụng const, tránh toàn cầu, ưu tiên tham chiếu đến con trỏ, tránh sử dụng lại các biến cho các tác vụ không liên quan, v.v. sẽ làm cho cuộc sống của trình biên dịch dễ dàng hơn khi thực hiện tối ưu hóa tích cực.


1
Nếu tôi nhớ lại nó một cách chính xác, đó là toàn bộ điểm của lập trình chức năng, phải không? Bằng cách chỉ sử dụng các hàm hoàn toàn xác định, không có tác dụng phụ, trình biên dịch có thể tự do tối ưu hóa tích cực, thực hiện trước, thực hiện sau, ghi nhớ và thậm chí thực thi tại thời điểm biên dịch. Vấn đề mà tôi nghĩ rằng rất nhiều các người trả lời được bỏ qua (hoặc nhầm lẫn về) là nó thực sự tốt cho một tập hợp con well-behaved của tất cả các chương trình . Và không, tập hợp con này không phải là tầm thường hoặc không thú vị, thực sự nó rất hữu ích. Nhưng nó thực sự không thể đối với trường hợp tổng quát tuyệt đối.
Joe Pineda

Quá tải là một khái niệm thời gian biên dịch. Bạn có thể có nghĩa là "phương pháp được ghi đè".
fredoverflow

@FredOverflow: vâng, ý tôi là ghi đè. Quá tải thực sự là một khái niệm thời gian biên dịch. Cảm ơn bạn đã phát hiện ra nó (tất nhiên nếu việc triển khai đến từ một đơn vị biên dịch khác, trình biên dịch vẫn có thể gặp khó khăn khi phân tích nó, nhưng đó không phải là ý của tôi). Tôi sẽ sửa câu trả lời.
kriss

6

Có nhiều cách để giải thích điều này, một trong số đó là Vấn đề Tạm dừng :

Trong lý thuyết tính toán, bài toán tạm dừng có thể được phát biểu như sau: "Cho một mô tả của một chương trình máy tính tùy ý, quyết định xem chương trình chạy xong hay tiếp tục chạy mãi mãi". Điều này tương đương với vấn đề quyết định, cho một chương trình và một đầu vào, liệu chương trình cuối cùng sẽ dừng lại khi chạy với đầu vào đó, hay sẽ chạy mãi mãi.

Alan Turing đã chứng minh vào năm 1936 rằng không thể tồn tại một thuật toán chung để giải quyết vấn đề tạm dừng cho tất cả các cặp đầu vào chương trình có thể có.

Nếu tôi viết một chương trình giống như sau:

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

Giá trị có xthay đổi không? Để xác định điều này, trước tiên bạn sẽ phải xác định xem bộ do tons of complex stuffphận gây ra tình trạng cháy - hoặc thậm chí cơ bản hơn, liệu nó có tạm dừng hay không. Đó là điều mà trình biên dịch không làm được.


6

Thực sự ngạc nhiên rằng không có câu trả lời nào trực tiếp sử dụng vấn đề tạm dừng! Có một sự giảm rất đơn giản từ vấn đề này thành vấn đề dừng.

Hãy tưởng tượng rằng trình biên dịch có thể cho biết liệu một hàm có thay đổi giá trị của một biến hay không. Sau đó, nó chắc chắn sẽ có thể biết liệu hàm sau có thay đổi giá trị của y hay không, giả sử rằng giá trị của x có thể được theo dõi trong tất cả các lệnh gọi trong suốt phần còn lại của chương trình:

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

Bây giờ, đối với bất kỳ chương trình nào chúng ta thích, hãy viết lại nó thành:

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

Lưu ý rằng, nếu và chỉ khi, chương trình của chúng ta thay đổi giá trị của y, thì nó mới kết thúc - foo () là điều cuối cùng nó thực hiện trước khi thoát. Điều này có nghĩa là chúng tôi đã giải quyết được vấn đề tạm dừng!

Những gì giảm ở trên cho chúng ta thấy là vấn đề xác định xem giá trị của một biến có thay đổi hay không ít nhất là khó như vấn đề tạm dừng. Vấn đề tạm dừng được biết là không thể thay đổi, vì vậy vấn đề này cũng phải như vậy.


Tôi không chắc tôi theo lý luận của bạn, về lý do tại sao chương trình của chúng tôi kết thúc iff nó thay đổi giá trị của y. Đối với tôi như foo()trở về nhanh chóng, và sau đó main()thoát ra. (Ngoài ra, bạn đang gọi điện thoại foo()mà không cần một cuộc tranh cãi ... đó là một phần của sự nhầm lẫn của tôi.)
LarsH

1
@LarsH: Tắt chương trình đã sửa đổi kết thúc, hàm cuối cùng mà nó gọi là f. Nếu y được sửa đổi, f được gọi (các câu lệnh khác không thể thay đổi y, vì nó chỉ được giới thiệu bởi sửa đổi). Do đó, nếu y được sửa đổi, chương trình sẽ kết thúc.
MSalters

4

Ngay khi một hàm gọi một hàm khác mà trình biên dịch không "nhìn thấy" nguồn của nó, nó sẽ phải giả định rằng biến đó đã bị thay đổi, hoặc mọi thứ có thể còn sai ở bên dưới. Ví dụ: giả sử chúng tôi có điều này trong "foo.cpp":

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

và chúng tôi có cái này trong "bar.cpp":

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

Làm thế nào trình biên dịch có thể "biết" xlà không thay đổi (hoặc IS đang thay đổi, thích hợp hơn) trong bar?

Tôi chắc rằng chúng ta có thể nghĩ ra một thứ gì đó phức tạp hơn, nếu điều này không đủ phức tạp.


Trình biên dịch có thể biết rằng x không thay đổi trong thanh nếu thanh x được chuyển dưới dạng pass-by-reference-to-const, phải không?
Cricketer

Có, nhưng nếu tôi thêm một const_casttrong foo, nó vẫn sẽ xthay đổi - tôi sẽ vi phạm hợp đồng quy định rằng bạn không được thay đổi constcác biến, nhưng vì bạn có thể chuyển đổi bất kỳ thứ gì thành "more const" và const_casttồn tại, các nhà thiết kế ngôn ngữ chắc chắn đã nghĩ rằng đôi khi có những lý do chính đáng để tin rằng constcác giá trị có thể cần thay đổi.
Mats Petersson

@MatsPetersson: Tôi tin rằng nếu bạn const_cast, bạn có thể giữ lại tất cả các phần bị vỡ vì trình biên dịch có thể, nhưng không phải bù đắp cho điều đó.
Zan Lynx

@ZanLynx: Vâng, tôi chắc chắn điều đó chính xác. Nhưng đồng thời, dàn diễn viên vẫn tồn tại, có nghĩa là ai đó thiết kế ngôn ngữ đã có một số ý tưởng rằng "chúng ta có thể cần điều này vào một lúc nào đó" - có nghĩa là nó không có nghĩa là không làm bất cứ điều gì hữu ích cả.
Mats Petersson

1

Không thể nói chung để cho trình biên dịch để xác định xem biến sẽ được thay đổi, như đã được chỉ ra.

Khi kiểm tra const-ness, câu hỏi quan tâm dường như là liệu biến có thể được thay đổi bởi một hàm hay không. Ngay cả điều này cũng khó trong các ngôn ngữ hỗ trợ con trỏ. Bạn không thể kiểm soát những gì mã khác thực hiện với một con trỏ, thậm chí nó có thể được đọc từ một nguồn bên ngoài (mặc dù không chắc). Trong các ngôn ngữ hạn chế quyền truy cập vào bộ nhớ, các loại đảm bảo này có thể có và cho phép tối ưu hóa tích cực hơn C ++.


2
Một điều tôi mong muốn được hỗ trợ trong các ngôn ngữ là sự phân biệt giữa các tham chiếu (hoặc con trỏ) tạm thời, có thể trả lại và bền vững. Các tham chiếu tạm thời chỉ có thể được sao chép sang các tham chiếu tạm thời khác, những tham chiếu có thể trả lại có thể được sao chép sang những tham chiếu tạm thời hoặc có thể trả lại, và những tham chiếu lâu dài có thể được sao chép theo bất kỳ cách nào. Giá trị trả về của một hàm sẽ bị hạn chế bởi đối số hạn chế nhất được truyền dưới dạng tham số "có thể trả về". Tôi cho rằng thật không may là trong nhiều ngôn ngữ, khi một tham chiếu chuyển qua thì không có gì để chỉ ra rằng nó có thể được sử dụng trong bao lâu.
supercat

Điều đó chắc chắn sẽ hữu ích. Tất nhiên có những mẫu cho điều này, nhưng trong C ++ (và nhiều ngôn ngữ khác), luôn có thể "gian lận".
Krumelur

Một cách chính mà .NET vượt trội hơn Java là nó có khái niệm về một tham chiếu tạm thời, nhưng tiếc là không có cách nào để các đối tượng hiển thị các thuộc tính dưới dạng tham chiếu tạm thời (những gì tôi thực sự muốn xem sẽ là một phương tiện mã nào sử dụng thuộc tính sẽ chuyển một tham chiếu tạm thời tới một mã (cùng với các biến tạm thời) sẽ được sử dụng để thao tác đối tượng.
supercat

1

Để làm cho câu hỏi cụ thể hơn, tôi đề xuất một loạt các ràng buộc sau đây có thể là điều mà tác giả cuốn sách có thể đã nghĩ đến:

  1. Giả sử trình biên dịch đang kiểm tra hành vi của một hàm cụ thể liên quan đến const-ness của một biến. Đối với tính đúng đắn, một trình biên dịch sẽ phải giả sử (vì răng cưa như được giải thích bên dưới) nếu hàm được gọi là một hàm khác mà biến bị thay đổi, vì vậy giả định # 1 chỉ áp dụng cho các đoạn mã không thực hiện lệnh gọi hàm.
  2. Giả sử biến không bị sửa đổi bởi hoạt động không đồng bộ hoặc đồng thời.
  3. Giả sử trình biên dịch chỉ xác định xem biến có thể được sửa đổi hay không, chứ không phải liệu nó có được sửa đổi hay không. Nói cách khác, trình biên dịch chỉ thực hiện phân tích tĩnh.
  4. Giả sử trình biên dịch chỉ đang xem xét mã hoạt động chính xác (không xem xét vượt quá / chạy dưới mảng, con trỏ xấu, v.v.)

Trong bối cảnh thiết kế trình biên dịch, tôi nghĩ rằng các giả định 1,3,4 có ý nghĩa hoàn hảo trong quan điểm của một người viết trình biên dịch trong bối cảnh tính đúng đắn của gen mã và / hoặc tối ưu hóa mã. Giả định 2 có ý nghĩa khi không có từ khóa biến động. Và những giả định này cũng tập trung câu hỏi đủ để làm cho việc đánh giá một câu trả lời được đề xuất chắc chắn hơn nhiều :-)

Với những giả định đó, lý do chính khiến không thể giả định const-ness là do răng cưa biến. Trình biên dịch không thể biết liệu một biến khác có trỏ đến biến const hay không. Việc đặt biệt hiệu có thể là do một chức năng khác trong cùng một đơn vị biên dịch, trong trường hợp đó, trình biên dịch có thể xem xét các chức năng và sử dụng cây lệnh gọi để xác định tĩnh rằng hiện tượng răng cưa có thể xảy ra. Nhưng nếu bí danh là do một thư viện hoặc mã ngoại lai khác, thì trình biên dịch không có cách nào để biết khi nhập hàm liệu các biến có phải là bí danh hay không.

Bạn có thể tranh luận rằng nếu một biến / đối số được đánh dấu là const thì nó sẽ không thể thay đổi thông qua bí danh, nhưng đối với một người viết trình biên dịch thì điều đó khá rủi ro. Một lập trình viên con người thậm chí có thể gặp rủi ro khi khai báo một biến const như một phần của dự án lớn, nơi anh ta không biết hành vi của toàn bộ hệ thống, hoặc hệ điều hành hoặc thư viện, để thực sự biết một biến đã thắng ' t thay đổi.


0

Ngay cả khi một biến được khai báo const, không có nghĩa là một số mã bị viết sai có thể ghi đè lên nó.

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

đầu ra:

1
2

Điều này xảy ra bởi vì ablà các biến ngăn xếp, và b[1]chỉ xảy ra ở cùng một vị trí bộ nhớ a.
Mark Lakata

1
-1. Hành vi không xác định loại bỏ tất cả các hạn chế đối với hành vi của trình biên dịch.
MSalters

Không chắc chắn về việc bỏ phiếu xuống. Đây chỉ là một ví dụ cho câu hỏi ban đầu của OP về việc tại sao một trình biên dịch không thể tìm ra liệu một cái gì đó thực sự là constnếu mọi thứ được gắn nhãn const. Đó là vì hành vi không xác định là một phần của C / C ++. Tôi đang cố gắng tìm một cách khác để trả lời câu hỏi của anh ấy thay vì đề cập đến vấn đề tạm dừng hoặc ý kiến ​​của con người bên ngoài.
Mark Lakata

0

Để mở rộng các nhận xét của tôi, nội dung cuốn sách đó không rõ ràng, điều gì làm xáo trộn vấn đề.

Như tôi đã nhận xét, cuốn sách đó đang cố gắng nói, "chúng ta hãy lấy vô số con khỉ để viết mọi hàm C ++ có thể tưởng tượng được mà có thể được viết. Sẽ có trường hợp nếu chúng ta chọn một biến (một số hàm cụ thể mà lũ khỉ đã viết) sử dụng, chúng tôi không thể tìm hiểu liệu hàm có thay đổi biến đó hay không. "

Tất nhiên đối với một số (thậm chí nhiều) chức năng trong bất kỳ ứng dụng nhất định nào, điều này có thể được xác định bởi trình biên dịch và rất dễ dàng. Nhưng không phải cho tất cả (hoặc nhất thiết là hầu hết).

Có thể dễ dàng phân tích chức năng này:

static int global;

void foo()
{
}

"foo" rõ ràng không sửa đổi "toàn cầu". Nó không sửa đổi bất cứ điều gì và một trình biên dịch có thể giải quyết vấn đề này rất dễ dàng.

Chức năng này không thể được phân tích như vậy:

static int global;

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

Vì các hành động của "foo" phụ thuộc vào một giá trị có thể thay đổi trong thời gian chạy , nó thường không thể được xác định tại thời điểm biên dịch liệu nó có sửa đổi "toàn cục" hay không.

Toàn bộ khái niệm này dễ hiểu hơn nhiều so với các nhà khoa học máy tính tạo ra nó. Nếu hàm có thể làm điều gì đó khác biệt dựa trên những thứ có thể thay đổi trong thời gian chạy, thì bạn không thể tìm ra nó sẽ làm gì cho đến khi nó chạy và mỗi lần nó chạy nó có thể làm điều gì đó khác. Dù có thể chứng minh là không thể hay không, thì rõ ràng là không thể.


những gì bạn nói là đúng, nhưng ngay cả đối với các chương trình rất đơn giản mà mọi thứ đã biết tại thời điểm biên dịch, bạn sẽ không thể thu được bất cứ thứ gì, thậm chí là chương trình sẽ dừng lại. Đây là vấn đề tạm dừng. Ví dụ, bạn có thể viết một chương trình dựa trên Hailstone Sequences en.wikipedia.org/wiki/Collatz_conjecture và làm cho nó trả về true nếu nó hội tụ thành một. Các trình biên dịch sẽ không thể làm điều đó (vì nó sẽ tràn trong nhiều trường hợp) và ngay cả các nhà toán học cũng không biết điều đó có đúng hay không.
kriss

Nếu bạn có nghĩa là "có một số chương trình trông rất đơn giản mà bạn không thể chứng minh bất cứ điều gì" tôi hoàn toàn đồng ý. Nhưng chứng minh Vấn đề Tạm dừng cổ điển của Turing chủ yếu dựa vào bản thân một chương trình có thể cho biết liệu nó có tạm dừng hay không để thiết lập một mâu thuẫn. Vì đây là toán học không thực hiện. Chắc chắn có những chương trình hoàn toàn có thể xác định tĩnh tại thời điểm biên dịch liệu một biến cụ thể có được sửa đổi hay không và liệu chương trình có tạm dừng hay không. Nó có thể không chứng minh được về mặt toán học, nhưng nó thực tế có thể đạt được trong một số trường hợp nhất định.
El Zorko
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.