Thiết kế trình biên dịch - Môi trường thời gian chạy
Một chương trình như một mã nguồn chỉ đơn thuần là một tập hợp các văn bản (mã, câu lệnh, v.v.) và để làm cho nó tồn tại, nó đòi hỏi các hành động được thực hiện trên máy đích. Một chương trình cần tài nguyên bộ nhớ để thực hiện các lệnh. Một chương trình chứa tên cho các thủ tục, số nhận dạng, v.v., yêu cầu ánh xạ với vị trí bộ nhớ thực trong thời gian chạy.
Theo thời gian chạy, chúng tôi có nghĩa là một chương trình đang được thực thi. Môi trường thời gian chạy là một trạng thái của máy đích, có thể bao gồm các thư viện phần mềm, các biến môi trường, v.v., để cung cấp dịch vụ cho các tiến trình đang chạy trong hệ thống.
Hệ thống hỗ trợ thời gian chạy là một gói, hầu hết được tạo bằng chính chương trình thực thi và tạo điều kiện cho quá trình giao tiếp giữa quá trình và môi trường thời gian chạy. Nó đảm nhiệm việc cấp phát bộ nhớ và khử cấp phát trong khi chương trình đang được thực thi.
Cây kích hoạt
Chương trình là một chuỗi các lệnh được kết hợp thành một số thủ tục. Các hướng dẫn trong một thủ tục được thực hiện tuần tự. Một thủ tục có một dấu phân cách bắt đầu và kết thúc và mọi thứ bên trong nó được gọi là phần thân của thủ tục. Định danh thủ tục và chuỗi các lệnh hữu hạn bên trong nó tạo nên phần thân của thủ tục.
Việc thực hiện một thủ tục được gọi là sự kích hoạt của nó. Một bản ghi kích hoạt chứa tất cả các thông tin cần thiết cần thiết để gọi một thủ tục. Bản ghi kích hoạt có thể chứa các đơn vị sau (tùy thuộc vào ngôn ngữ nguồn được sử dụng).
Tạm thời | Lưu trữ các giá trị tạm thời và trung gian của một biểu thức. |
Dữ liệu cục bộ | Lưu trữ dữ liệu cục bộ của thủ tục được gọi. |
Tình trạng máy | Lưu trữ trạng thái máy như Thanh ghi, Bộ đếm chương trình, v.v., trước khi quy trình được gọi. |
Liên kết kiểm soát | Lưu trữ địa chỉ của hồ sơ kích hoạt của thủ tục người gọi. |
Truy cập liên kết | Lưu trữ thông tin dữ liệu nằm ngoài phạm vi cục bộ. |
Thông số thực tế | Lưu trữ các tham số thực, tức là các tham số được sử dụng để gửi đầu vào cho thủ tục được gọi. |
Giá trị trả lại | Cửa hàng trả về giá trị. |
Bất cứ khi nào một thủ tục được thực thi, bản ghi kích hoạt của nó được lưu trữ trên ngăn xếp, còn được gọi là ngăn xếp điều khiển. Khi một thủ tục gọi một thủ tục khác, việc thực thi trình gọi sẽ bị tạm dừng cho đến khi thủ tục được gọi kết thúc thực thi. Tại thời điểm này, bản ghi kích hoạt của thủ tục được gọi được lưu trữ trên ngăn xếp.
Chúng ta giả định rằng chương trình điều khiển chạy theo cách tuần tự và khi một thủ tục được gọi, điều khiển của nó được chuyển sang thủ tục được gọi. Khi một thủ tục được gọi được thực thi, nó sẽ trả lại quyền điều khiển cho người gọi. Loại luồng điều khiển này giúp dễ dàng biểu diễn một loạt các hoạt động dưới dạng cây, được gọi làactivation tree.
Để hiểu khái niệm này, chúng tôi lấy một đoạn mã làm ví dụ:
. . .
printf(“Enter Your Name: “);
scanf(“%s”, username);
show_data(username);
printf(“Press any key to continue…”);
. . .
int show_data(char *user)
{
printf(“Your name is %s”, username);
return 0;
}
. . .
Dưới đây là cây kích hoạt của mã được đưa ra.
Bây giờ chúng ta hiểu rằng các thủ tục được thực thi theo cách chuyên sâu, do đó, phân bổ ngăn xếp là hình thức lưu trữ phù hợp nhất để kích hoạt thủ tục.
Phân bổ lưu trữ
Môi trường thời gian chạy quản lý các yêu cầu bộ nhớ thời gian chạy cho các thực thể sau:
Code: Nó được gọi là phần văn bản của chương trình không thay đổi trong thời gian chạy. Yêu cầu bộ nhớ của nó đã được biết tại thời điểm biên dịch.
Procedures: Phần văn bản của chúng là tĩnh nhưng chúng được gọi theo cách ngẫu nhiên. Đó là lý do tại sao, lưu trữ ngăn xếp được sử dụng để quản lý các lệnh gọi và kích hoạt thủ tục.
Variables: Các biến chỉ được biết trong thời gian chạy, trừ khi chúng là toàn cục hoặc hằng số. Lược đồ cấp phát bộ nhớ Heap được sử dụng để quản lý cấp phát và cấp phát bộ nhớ cho các biến trong thời gian chạy.
Phân bổ tĩnh
Trong lược đồ cấp phát này, dữ liệu biên dịch được gắn vào một vị trí cố định trong bộ nhớ và nó không thay đổi khi chương trình thực thi. Vì yêu cầu bộ nhớ và vị trí lưu trữ đã được biết trước, nên không cần gói hỗ trợ thời gian chạy để cấp phát và hủy cấp phát bộ nhớ.
Phân bổ ngăn xếp
Các cuộc gọi thủ tục và kích hoạt của chúng được quản lý bằng cách cấp phát bộ nhớ ngăn xếp. Nó hoạt động theo phương pháp last-in-first-out (LIFO) và chiến lược phân bổ này rất hữu ích cho các cuộc gọi thủ tục đệ quy.
Phân bổ đống
Các biến cục bộ cho một thủ tục chỉ được cấp phát và hủy cấp phát trong thời gian chạy. Cấp phát đống được sử dụng để cấp phát động bộ nhớ cho các biến và yêu cầu nó trở lại khi các biến không còn yêu cầu nữa.
Ngoại trừ vùng bộ nhớ được cấp phát tĩnh, cả bộ nhớ ngăn xếp và bộ nhớ heap đều có thể phát triển và thu nhỏ một cách động và bất ngờ. Do đó, chúng không thể được cung cấp một lượng bộ nhớ cố định trong hệ thống.
Như trong hình trên, phần văn bản của mã được cấp phát một lượng bộ nhớ cố định. Bộ nhớ ngăn xếp và bộ nhớ heap được sắp xếp ở các cực của tổng bộ nhớ được cấp cho chương trình. Cả hai đều co lại và phát triển chống lại nhau.
Truyền tham số
Phương tiện giao tiếp giữa các thủ tục được gọi là truyền tham số. Giá trị của các biến từ một thủ tục gọi được chuyển sang thủ tục được gọi bởi một số cơ chế. Trước khi tiếp tục, trước tiên hãy xem qua một số thuật ngữ cơ bản liên quan đến các giá trị trong một chương trình.
giá trị r
Giá trị của một biểu thức được gọi là giá trị r của nó. Giá trị chứa trong một biến đơn lẻ cũng trở thành giá trị r nếu nó xuất hiện ở phía bên phải của toán tử gán. Giá trị r luôn có thể được gán cho một số biến khác.
giá trị l
Vị trí của bộ nhớ (địa chỉ) nơi một biểu thức được lưu trữ được gọi là giá trị l của biểu thức đó. Nó luôn xuất hiện ở bên trái của toán tử gán.
Ví dụ:
day = 1;
week = day * 7;
month = 1;
year = month * 12;
Từ ví dụ này, chúng ta hiểu rằng các giá trị không đổi như 1, 7, 12 và các biến như ngày, tuần, tháng và năm, tất cả đều có giá trị r. Chỉ các biến có giá trị l vì chúng cũng đại diện cho vị trí bộ nhớ được gán cho chúng.
Ví dụ:
7 = x + y;
là lỗi giá trị l, vì hằng số 7 không đại diện cho bất kỳ vị trí bộ nhớ nào.
Thông số chính thức
Các biến lấy thông tin được truyền bởi thủ tục người gọi được gọi là tham số chính thức. Các biến này được khai báo trong định nghĩa của hàm được gọi.
Thông số thực tế
Các biến có giá trị hoặc địa chỉ đang được chuyển đến thủ tục được gọi được gọi là tham số thực tế. Các biến này được chỉ định trong lệnh gọi hàm dưới dạng đối số.
Example:
fun_one()
{
int actual_parameter = 10;
call fun_two(int actual_parameter);
}
fun_two(int formal_parameter)
{
print formal_parameter;
}
Các tham số chính thức giữ thông tin của tham số thực, tùy thuộc vào kỹ thuật truyền tham số được sử dụng. Nó có thể là một giá trị hoặc một địa chỉ.
Vượt qua giá trị
Trong cơ chế truyền theo giá trị, thủ tục gọi chuyển giá trị r của các tham số thực tế và trình biên dịch đưa giá trị đó vào bản ghi kích hoạt của thủ tục được gọi. Các tham số chính thức sau đó giữ các giá trị được truyền bởi thủ tục gọi. Nếu các giá trị được giữ bởi các tham số chính thức bị thay đổi, nó sẽ không ảnh hưởng đến các tham số thực tế.
Chuyển qua tài liệu tham khảo
Trong cơ chế tham chiếu truyền, giá trị l của tham số thực được sao chép vào bản ghi kích hoạt của thủ tục được gọi. Bằng cách này, thủ tục được gọi bây giờ có địa chỉ (vị trí bộ nhớ) của tham số thực và tham số chính thức đề cập đến cùng một vị trí bộ nhớ. Do đó, nếu giá trị được trỏ bởi tham số chính thức bị thay đổi, tác động sẽ được nhìn thấy trên tham số thực vì chúng cũng phải trỏ đến cùng một giá trị.
Vượt qua sao chép-khôi phục
Cơ chế truyền tham số này hoạt động tương tự như 'truyền qua tham chiếu' ngoại trừ việc các thay đổi đối với tham số thực được thực hiện khi thủ tục được gọi kết thúc. Khi gọi hàm, các giá trị của các tham số thực được sao chép trong bản ghi kích hoạt của thủ tục được gọi. Các tham số chính thức nếu được thao tác sẽ không có tác dụng theo thời gian thực đối với các tham số thực (khi các giá trị l được truyền), nhưng khi thủ tục được gọi kết thúc, các giá trị l của các tham số chính thức được sao chép sang các giá trị l của tham số thực.
Example:
int y;
calling_procedure()
{
y = 10;
copy_restore(y); //l-value of y is passed
printf y; //prints 99
}
copy_restore(int x)
{
x = 99; // y still has value 10 (unaffected)
y = 0; // y is now 0
}
Khi hàm này kết thúc, giá trị l của tham số chính thức x được sao chép vào tham số thực tế y. Ngay cả khi giá trị của y được thay đổi trước khi thủ tục kết thúc, giá trị l của x được sao chép sang giá trị l của y khiến nó hoạt động giống như lệnh gọi bằng tham chiếu.
Chuyển qua tên
Các ngôn ngữ như Algol cung cấp một loại cơ chế truyền tham số mới hoạt động giống như bộ tiền xử lý trong ngôn ngữ C. Trong cơ chế pass by name, tên của thủ tục đang được gọi được thay thế bằng phần thân thực của nó. Pass-by-name thay thế bằng văn bản các biểu thức đối số trong một lệnh gọi thủ tục cho các tham số tương ứng trong phần thân của thủ tục để bây giờ nó có thể hoạt động trên các tham số thực tế, giống như kiểu truyền qua tham chiếu.