Tiêu chuẩn C cung cấp cho trình biên dịch rất nhiều vĩ độ để thực hiện tối ưu hóa. Hậu quả của những tối ưu hóa này có thể gây ngạc nhiên nếu bạn giả sử một mô hình chương trình ngây thơ trong đó bộ nhớ chưa khởi tạo được đặt thành một số mẫu bit ngẫu nhiên và tất cả các hoạt động được thực hiện theo thứ tự chúng được viết.
Lưu ý: các ví dụ sau chỉ hợp lệ vì x
không bao giờ được sử dụng địa chỉ của nó, vì vậy nó là "giống như đăng ký". Chúng cũng sẽ hợp lệ nếu loại x
có biểu diễn bẫy; điều này hiếm khi xảy ra đối với các loại không có dấu (nó yêu cầu "lãng phí" ít nhất một bit dung lượng lưu trữ và phải được ghi lại) và không thể xảy ra unsigned char
. Nếu x
có kiểu có dấu, thì việc triển khai có thể xác định mẫu bit không phải là một số giữa - (2 n-1 -1) và 2 n-1 -1 như một biểu diễn bẫy. Hãy xem câu trả lời của Jens Gustedt .
Các trình biên dịch cố gắng gán các thanh ghi cho các biến, vì các thanh ghi nhanh hơn bộ nhớ. Vì chương trình có thể sử dụng nhiều biến hơn bộ xử lý có thanh ghi, trình biên dịch thực hiện cấp phát thanh ghi, điều này dẫn đến các biến khác nhau sử dụng cùng một thanh ghi vào những thời điểm khác nhau. Xem xét đoạn chương trình
unsigned x, y, z;
y = 0;
z = 4;
x = - x;
y = y + z;
x = y + 1;
Khi dòng 3 được đánh giá, x
chưa được khởi tạo, do đó (lý do là trình biên dịch) dòng 3 phải là một loại lỗi không thể xảy ra do các điều kiện khác mà trình biên dịch không đủ thông minh để tìm ra. Vì z
không được sử dụng sau dòng 4 và x
không được sử dụng trước dòng 5, nên cùng một thanh ghi có thể được sử dụng cho cả hai biến. Vì vậy, chương trình nhỏ này được biên dịch cho các hoạt động sau trên thanh ghi:
r1 = 0;
r0 = 4;
r0 = - r0;
r1 += r0;
r0 = r1;
Giá trị cuối cùng của x
là giá trị cuối cùng của r0
và giá trị cuối cùng của y
là giá trị cuối cùng của r1
. Các giá trị này là x = -3 và y = -4, không phải 5 và 4 như sẽ xảy ra nếu x
được khởi tạo đúng cách.
Để có một ví dụ phức tạp hơn, hãy xem xét đoạn mã sau:
unsigned i, x;
for (i = 0; i < 10; i++) {
x = (condition() ? some_value() : -x);
}
Giả sử rằng trình biên dịch phát hiện ra rằng condition
không có tác dụng phụ. Vì condition
không sửa đổi x
, trình biên dịch biết rằng lần đầu tiên chạy qua vòng lặp không thể được truy cập x
vì nó chưa được khởi tạo. Do đó, lần thực thi đầu tiên của phần thân vòng lặp tương đương với x = some_value()
, không cần phải kiểm tra điều kiện. Trình biên dịch có thể biên dịch mã này như thể bạn đã viết
unsigned i, x;
i = 0;
x = some_value();
for (i = 1; i < 10; i++) {
x = (condition() ? some_value() : -x);
}
Cách mà điều này có thể được mô hình hóa bên trong trình biên dịch là xem xét rằng bất kỳ giá trị nào tùy thuộc vào x
đều có bất kỳ giá trị nào thuận tiện miễn x
là chưa được khởi tạo. Bởi vì hành vi khi một biến chưa được khởi tạo là không được xác định, thay vì biến chỉ có một giá trị không xác định, trình biên dịch không cần theo dõi bất kỳ mối quan hệ toán học đặc biệt nào giữa các giá trị bất kỳ là thuận tiện. Do đó, trình biên dịch có thể phân tích đoạn mã trên theo cách này:
- trong lần lặp vòng lặp đầu tiên,
x
không được khởi tạo bởi thời gian -x
được đánh giá.
-x
có hành vi không xác định, vì vậy giá trị của nó là bất cứ điều gì-thuận-tiện.
- Quy tắc tối ưu hóa được áp dụng, vì vậy mã này có thể được đơn giản hóa thành .
condition ? value : value
condition; value
Khi đối mặt với mã trong câu hỏi của bạn, chính trình biên dịch này sẽ phân tích rằng khi nào x = - x
được đánh giá, giá trị của -x
là bất kỳ điều gì-thuận-tiện. Vì vậy, bài tập có thể được tối ưu hóa đi.
Tôi chưa tìm kiếm ví dụ về trình biên dịch hoạt động như được mô tả ở trên, nhưng đó là loại tối ưu hóa mà các trình biên dịch tốt cố gắng thực hiện. Tôi sẽ không ngạc nhiên khi gặp một người. Đây là một ví dụ ít hợp lý hơn về trình biên dịch mà chương trình của bạn gặp sự cố. (Có thể không quá đáng nếu bạn biên dịch chương trình của mình trong một số loại chế độ gỡ lỗi nâng cao.)
Trình biên dịch giả định này ánh xạ mọi biến trong một trang bộ nhớ khác nhau và thiết lập các thuộc tính của trang để việc đọc từ một biến chưa được khởi tạo gây ra một bẫy bộ xử lý gọi trình gỡ lỗi. Bất kỳ sự gán nào cho một biến trước tiên đảm bảo rằng trang bộ nhớ của nó được ánh xạ bình thường. Trình biên dịch này không cố gắng thực hiện bất kỳ tối ưu hóa nâng cao nào - nó đang ở chế độ gỡ lỗi, nhằm mục đích dễ dàng xác định các lỗi như các biến chưa được khởi tạo. Khi x = - x
được đánh giá, phía bên phải gây ra một cái bẫy và trình gỡ lỗi sẽ kích hoạt.