Một số trình biên dịch C siêu hiện đại sẽ suy luận rằng nếu một chương trình sẽ gọi Hành vi không xác định khi được cung cấp một số đầu vào nhất định, thì các đầu vào đó sẽ không bao giờ được nhận. Do đó, bất kỳ mã nào không liên quan trừ khi nhận được đầu vào như vậy có thể bị loại bỏ.
Một ví dụ đơn giản, được đưa ra:
void foo(uint32_t);
uint32_t rotateleft(uint_t value, uint32_t amount)
{
return (value << amount) | (value >> (32-amount));
}
uint32_t blah(uint32_t x, uint32_t y)
{
if (y != 0) foo(y);
return rotateleft(x,y);
}
một trình biên dịch có thể suy ra rằng vì việc đánh giá value >> (32-amount)
sẽ mang lại Hành vi không xác định khi amount
bằng 0, nên hàm blah
sẽ không bao giờ được gọi y
bằng 0; foo
do đó, cuộc gọi đến có thể được thực hiện vô điều kiện.
Từ những gì tôi có thể nói, triết lý này dường như đã bị bắt giữ vào khoảng năm 2010. Bằng chứng sớm nhất tôi thấy về nguồn gốc của nó bắt đầu từ năm 2009, và nó đã được ghi nhận trong tiêu chuẩn C11, trong đó nêu rõ rằng nếu Hành vi không xác định xảy ra ở bất kỳ điểm trong quá trình thực thi chương trình, hành vi của toàn bộ chương trình trở nên không xác định.
Được quan điểm cho rằng trình biên dịch nên cố gắng sử dụng hành vi undefined để biện minh cho việc tối ưu ngược nhân quả (tức là hành vi undefined trong rotateleft
chức năng nên gây ra trình biên dịch cho rằng blah
phải được gọi với một tổ chức phi-zero y
, hay không bất cứ điều gì sẽ không bao giờ gây ra y
để giữ một giá trị khác không) được ủng hộ nghiêm túc trước năm 2009? Khi nào một điều như vậy lần đầu tiên được đề xuất nghiêm túc như là một kỹ thuật tối ưu hóa?
[Phụ lục]
Một số trình biên dịch, ngay cả trong Thế kỷ 20, bao gồm các tùy chọn để cho phép các loại suy luận nhất định về các vòng lặp và các giá trị được tính toán trong đó. Ví dụ, được đưa ra
int i; int total=0;
for (i=n; i>=0; i--)
{
doSomething();
total += i*1000;
}
một trình biên dịch, ngay cả khi không có các suy luận tùy chọn, có thể viết lại thành:
int i; int total=0; int x1000;
for (i=n, x1000=n*1000; i>0; i--, x1000-=1000)
{
doSomething();
total += x1000;
}
vì hành vi của mã đó sẽ khớp chính xác với bản gốc, ngay cả khi trình biên dịch đã chỉ định rằng int
các giá trị luôn bao bọc theo kiểu bổ sung của hai mod-65536 . Tùy chọn suy luận bổ sung sẽ cho phép trình biên dịch nhận ra rằng vì i
và x1000
nên vượt qua 0 cùng một lúc, biến trước đây có thể được loại bỏ:
int total=0; int x1000;
for (x1000=n*1000; x1000 > 0; x1000-=1000)
{
doSomething();
total += x1000;
}
Trên một hệ thống có int
các giá trị được bao bọc mod 65536, một nỗ lực để chạy một trong hai vòng đầu tiên n
bằng 33 sẽ dẫn đến doSomething()
việc được gọi 33 lần. Ngược lại, vòng lặp cuối cùng sẽ không gọi được doSomething()
, mặc dù lần gọi đầu tiên doSomething()
sẽ có trước bất kỳ tràn số học nào. Một hành vi như vậy có thể được coi là "phi nhân quả", nhưng các tác động bị hạn chế một cách hợp lý và có nhiều trường hợp hành vi đó sẽ vô hại (trong trường hợp một hàm được yêu cầu để mang lại một số giá trị khi được đưa vào bất kỳ đầu vào nào , nhưng giá trị có thể tùy ý nếu đầu vào không hợp lệ, có vòng lặp kết thúc nhanh hơn khi được cung cấp giá trị không hợp lệ làn
sẽ thực sự có lợi). Hơn nữa, tài liệu trình biên dịch có xu hướng xin lỗi vì thực tế là nó sẽ thay đổi hành vi của bất kỳ chương trình nào - ngay cả những chương trình tham gia vào UB.
Tôi quan tâm đến khi thái độ của các nhà biên dịch thay đổi khỏi ý tưởng rằng các nền tảng nên khi tài liệu thực tế có một số hạn chế về hành vi có thể sử dụng được ngay cả trong trường hợp không được Tiêu chuẩn bắt buộc, với ý tưởng rằng mọi cấu trúc sẽ dựa trên bất kỳ hành vi nào không được ủy quyền bởi Tiêu chuẩn phải được gắn nhãn bất hợp pháp ngay cả khi trên hầu hết các trình biên dịch hiện có, nó sẽ hoạt động tốt hoặc tốt hơn bất kỳ mã tuân thủ nghiêm ngặt nào đáp ứng các yêu cầu tương tự (thường cho phép tối ưu hóa không thể thực hiện được trong mã tuân thủ nghiêm ngặt).
shape->Is2D()
được gọi trên một đối tượng không xuất phát từ Shape2D
. Có một sự khác biệt rất lớn giữa việc tối ưu hóa mã chỉ có liên quan nếu Hành vi không xác định quan trọng đã xảy ra so với mã chỉ có liên quan trong trường hợp ...
Shape2D::Is2D
là thực sự tốt hơn so với chương trình xứng đáng.
int prod(int x, int y) {return x*y;}
sẽ có hiệu lực. Việc tuân thủ" không khởi động nukes "theo cách tuân thủ nghiêm ngặt, tuy nhiên, sẽ yêu cầu mã khó đọc hơn và hầu như sẽ đọc chắc chắn chạy chậm hơn nhiều trên nhiều nền tảng.