Tôi không chắc nhưng tôi nghĩ câu trả lời là không, vì những lý do khá tinh tế. Tôi đã hỏi về Khoa học máy tính lý thuyết một vài năm trước và không nhận được câu trả lời nào vượt xa những gì tôi sẽ trình bày ở đây.
Trong hầu hết các ngôn ngữ lập trình, bạn có thể mô phỏng máy Turing bằng cách:
- mô phỏng máy tự động hữu hạn với chương trình sử dụng một lượng bộ nhớ hữu hạn;
- mô phỏng băng với một cặp danh sách các số nguyên được liên kết, thể hiện nội dung của băng trước và sau vị trí hiện tại. Di chuyển con trỏ có nghĩa là chuyển đầu của một trong các danh sách sang danh sách khác.
Một triển khai cụ thể chạy trên máy tính sẽ hết bộ nhớ nếu băng quá dài, nhưng một triển khai lý tưởng có thể thực hiện chương trình máy Turing một cách trung thực. Điều này có thể được thực hiện bằng bút và giấy hoặc bằng cách mua một máy tính có nhiều bộ nhớ hơn và trình biên dịch nhắm mục tiêu vào một kiến trúc có nhiều bit hơn trên mỗi từ và cứ thế nếu chương trình hết bộ nhớ.
Điều này không hoạt động trong C vì không thể có một danh sách liên kết có thể phát triển mãi mãi: luôn có một số giới hạn về số lượng nút.
Để giải thích tại sao, trước tiên tôi cần giải thích việc triển khai C là gì. C thực sự là một gia đình của ngôn ngữ lập trình. Các tiêu chuẩn ISO C (chính xác hơn, một phiên bản cụ thể của tiêu chuẩn này) định nghĩa (với mức độ trịnh trọng rằng tiếng Anh cho phép) cú pháp và ngữ nghĩa một gia đình của các ngôn ngữ lập trình. C có rất nhiều hành vi không xác định và hành vi được xác định thực hiện. Một triển khai của CĐT của C của C mã hóa tất cả các hành vi được xác định thực hiện (danh sách những điều cần mã hóa nằm trong phụ lục J cho C99). Mỗi lần thực hiện C là một ngôn ngữ lập trình riêng. Lưu ý rằng ý nghĩa của từ triển khai trên ứng dụng, có một chút đặc biệt: ý nghĩa thực sự của nó là một biến thể ngôn ngữ, có thể có nhiều chương trình biên dịch khác nhau thực hiện cùng một biến thể ngôn ngữ.
2CHAR_BITt
2CHAR_BIT×sizeof(t)
2CHAR_BIT×sizeof(void*)
Các giá trị của CHAR_BIT
và sizeof(void*)
có thể quan sát được, vì vậy nếu bạn hết bộ nhớ, bạn không thể tiếp tục chạy chương trình của mình với các giá trị lớn hơn cho các tham số đó. Bạn sẽ chạy chương trình theo một ngôn ngữ lập trình khác - triển khai C khác.
n×2CHAR_BIT×sizeof(void*)n
C không trực tiếp áp đặt độ sâu đệ quy tối đa. Việc triển khai được phép có tối đa, nhưng cũng được phép không có. Nhưng làm thế nào để chúng ta liên lạc giữa một cuộc gọi chức năng và cha mẹ của nó? Các đối số sẽ không tốt nếu chúng có thể truy cập được, bởi vì điều đó sẽ gián tiếp giới hạn độ sâu của đệ quy: nếu bạn có một hàm int f(int x) { … f(…) …}
thì tất cả các lần xuất hiện x
trên các khung hoạt động f
đều có địa chỉ riêng và do đó số lượng các cuộc gọi lồng nhau bị giới hạn bởi số địa chỉ có thể cho x
.
Chương trình AC có thể sử dụng lưu trữ không có địa chỉ dưới dạng các register
biến. Việc triển khai Bình thường chỉ có thể có một số lượng nhỏ các biến hữu hạn không có địa chỉ, nhưng về mặt lý thuyết, việc triển khai có thể cho phép một lượng register
lưu trữ không giới hạn . Trong quá trình triển khai như vậy, bạn có thể thực hiện một số lượng các cuộc gọi đệ quy không giới hạn cho một hàm, miễn là đối số của nó là register
. Nhưng vì các đối số là register
, bạn không thể tạo một con trỏ cho chúng và vì vậy bạn cần sao chép dữ liệu của chúng xung quanh một cách rõ ràng: bạn chỉ có thể truyền xung quanh một lượng dữ liệu hữu hạn, không phải là cấu trúc dữ liệu có kích thước tùy ý được tạo từ con trỏ.
Với độ sâu đệ quy không giới hạn và hạn chế rằng một hàm chỉ có thể lấy dữ liệu từ người gọi trực tiếp ( register
đối số) và trả lại dữ liệu cho người gọi trực tiếp (giá trị trả về của hàm), bạn có được sức mạnh của tự động đẩy xuống xác định .
Tôi không thể tìm cách đi xa hơn.
(Tất nhiên bạn có thể làm cho chương trình lưu trữ nội dung băng từ bên ngoài, thông qua các chức năng nhập / xuất tệp. Nhưng sau đó bạn sẽ không hỏi liệu C đã hoàn thành Turing chưa, nhưng liệu C cộng với một hệ thống lưu trữ vô hạn có hoàn chỉnh không, Turing mà câu trả lời là nhàm chán, đúng vậy. Bạn cũng có thể định nghĩa bộ lưu trữ là một lời tiên tri Turing - gọi fopen("oracle", "r+")
, fwrite
nội dung băng ban đầu cho nó và fread
quay lại nội dung băng cuối cùng.)