Saya tidak yakin tetapi saya pikir jawabannya tidak, karena alasan yang agak halus. Saya bertanya pada Theoretical Computer Science beberapa tahun yang lalu dan tidak mendapatkan jawaban yang melampaui apa yang akan saya sajikan di sini.
Di sebagian besar bahasa pemrograman, Anda dapat mensimulasikan mesin Turing dengan:
- mensimulasikan otomat terbatas dengan program yang menggunakan jumlah memori terbatas;
- mensimulasikan rekaman dengan sepasang daftar bilangan bulat yang ditautkan, mewakili konten rekaman sebelum dan sesudah posisi saat ini. Memindahkan penunjuk berarti memindahkan kepala salah satu daftar ke daftar lainnya.
Implementasi konkret yang dijalankan pada komputer akan kehabisan memori jika rekaman terlalu lama, tetapi implementasi yang ideal dapat menjalankan program mesin Turing dengan setia. Ini dapat dilakukan dengan pena dan kertas, atau dengan membeli komputer dengan lebih banyak memori, dan kompiler yang menargetkan arsitektur dengan lebih banyak bit per kata dan seterusnya jika program tersebut kehabisan memori.
Ini tidak berfungsi di C karena tidak mungkin memiliki daftar tertaut yang dapat tumbuh selamanya: selalu ada batasan jumlah node.
Untuk menjelaskan alasannya, saya pertama-tama perlu menjelaskan apa itu implementasi C. C sebenarnya adalah keluarga bahasa pemrograman. Standar ISO C (lebih tepatnya, versi spesifik dari standar ini) mendefinisikan (dengan tingkat formalitas yang dimungkinkan oleh bahasa Inggris) sintaks dan semantik keluarga bahasa pemrograman. C memiliki banyak perilaku tidak terdefinisi dan perilaku implementasi-didefinisikan. "Implementasi" dari C mengkodifikasi semua perilaku implementasi-didefinisikan (daftar hal-hal yang dikodifikasikan ada dalam lampiran J untuk C99). Setiap implementasi C adalah bahasa pemrograman yang terpisah. Perhatikan bahwa arti kata "implementasi" agak aneh: apa artinya sebenarnya adalah varian bahasa, mungkin ada beberapa program kompiler berbeda yang menerapkan varian bahasa yang sama.
Dalam implementasi C yang diberikan, byte memiliki nilai yang mungkin. Semua data dapat direpresentasikan sebagai array byte: suatu tipe memiliki paling banyak
2 nilai CHAR_BIT × sizeof (t) . Jumlah ini bervariasi dalam implementasi C yang berbeda, tetapi untuk implementasi C yang diberikan, ini adalah konstan.2CHAR_BITt
2CHAR_BIT×sizeof(t)
Khususnya, pointer hanya dapat mengambil paling banyak . Ini berarti bahwa ada jumlah objek terbatas yang terbatas.2CHAR_BIT×sizeof(void*)
Nilai-nilai CHAR_BIT
dan sizeof(void*)
dapat diamati, jadi jika Anda kehabisan memori, Anda tidak bisa hanya melanjutkan menjalankan program Anda dengan nilai yang lebih besar untuk parameter-parameter itu. Anda akan menjalankan program di bawah bahasa pemrograman yang berbeda - implementasi C yang berbeda.
Jika program dalam suatu bahasa hanya dapat memiliki jumlah status terbatas, maka bahasa pemrograman tidak lebih ekspresif daripada automata terbatas. Fragmen C yang terbatas pada penyimpanan yang dapat dialamatkan hanya memungkinkan paling banyak menyatakan di mana n adalah ukuran pohon sintaksis abstrak dari program (mewakili keadaan aliran kontrol), oleh karena itu ini Program dapat disimulasikan oleh robot terbatas dengan banyak negara. Jika C lebih ekspresif, itu harus melalui penggunaan fitur lain.n×2CHAR_BIT×sizeof(void*)n
C tidak secara langsung memaksakan kedalaman rekursi maksimum. Implementasi diizinkan untuk memiliki maksimum, tetapi juga diizinkan untuk tidak memilikinya. Tetapi bagaimana kita berkomunikasi antara panggilan fungsi dan induknya? Argumen tidak baik jika dapat dialamatkan, karena itu secara tidak langsung akan membatasi kedalaman rekursi: jika Anda memiliki fungsi int f(int x) { … f(…) …}
maka semua kemunculan x
pada frame aktif f
memiliki alamat sendiri sehingga jumlah panggilan bersarang dibatasi oleh nomor tersebut. kemungkinan alamat untuk x
.
Program AC dapat menggunakan penyimpanan yang tidak dapat dialamatkan dalam bentuk register
variabel. Implementasi "Normal" hanya dapat memiliki sejumlah kecil, variabel terbatas yang tidak memiliki alamat, tetapi secara teori implementasi dapat memungkinkan jumlah register
penyimpanan tidak terbatas . Dalam implementasi seperti itu, Anda dapat membuat jumlah panggilan rekursif tanpa batas ke suatu fungsi, selama argumennya ada register
. Tetapi karena argumennya adalah register
, Anda tidak dapat membuat pointer ke mereka, dan karenanya Anda perlu menyalin data mereka di sekitar secara eksplisit: Anda hanya bisa memberikan jumlah data yang terbatas, bukan struktur data berukuran sewenang-wenang yang terbuat dari pointer.
Dengan kedalaman rekursi yang tidak terbatas, dan batasan bahwa suatu fungsi hanya bisa mendapatkan data dari penelepon langsungnya ( register
argumen) dan mengembalikan data ke penelepon langsungnya (nilai pengembalian fungsi), Anda mendapatkan kekuatan automata pushdown deterministic .
Saya tidak dapat menemukan cara untuk melangkah lebih jauh.
(Tentu saja Anda dapat membuat program menyimpan konten kaset secara eksternal, melalui fungsi input / output file. Tetapi kemudian Anda tidak akan bertanya apakah C adalah Turing-complete, tetapi apakah C plus sistem penyimpanan tak terbatas adalah Turing-complete, untuk yang jawabannya adalah "ya" yang membosankan. Anda mungkin juga mendefinisikan penyimpanan untuk menjadi oruring Turing - panggilan fopen("oracle", "r+")
, fwrite
isi rekaman awal untuk itu dan fread
kembali konten rekaman akhir.)