Jika kita membaca buku (atau versi lain dari spesifikasi bahasa jika Anda mau), berapa banyak daya komputasi yang dapat dimiliki oleh implementasi C?
Perhatikan bahwa "implementasi C" memiliki arti teknis: itu adalah contoh khusus dari spesifikasi bahasa pemrograman C di mana perilaku yang ditentukan implementasi didokumentasikan. Implementasi AC tidak harus dapat berjalan di komputer yang sebenarnya. Itu memang harus mengimplementasikan seluruh bahasa, termasuk setiap objek yang memiliki representasi bit-string dan tipe yang memiliki ukuran implementasi yang ditentukan.
Untuk keperluan pertanyaan ini, tidak ada penyimpanan eksternal. Satu-satunya input / output yang dapat Anda lakukan adalah getchar
(untuk membaca input program) dan putchar
(untuk menulis output program). Juga setiap program yang memanggil perilaku tidak terdefinisi tidak valid: program yang valid harus memiliki perilakunya yang didefinisikan oleh spesifikasi C ditambah deskripsi implementasi dari perilaku yang didefinisikan implementasi yang tercantum dalam lampiran J (untuk C99). Perhatikan bahwa memanggil fungsi perpustakaan yang tidak disebutkan dalam standar adalah perilaku yang tidak ditentukan.
Reaksi awal saya adalah bahwa implementasi C tidak lebih dari otomat terbatas, karena memiliki batasan pada jumlah memori yang dapat dialamatkan (Anda tidak dapat menangani lebih dari sizeof(char*) * CHAR_BIT
bit penyimpanan, karena alamat memori yang berbeda harus memiliki pola bit yang berbeda ketika disimpan dalam byte pointer).
Namun saya pikir implementasi dapat melakukan lebih dari ini. Sejauh yang saya tahu, standar tidak menetapkan batasan pada kedalaman rekursi. Jadi, Anda dapat membuat panggilan fungsi rekursif sebanyak yang Anda suka, hanya semua kecuali sejumlah panggilan yang terbatas harus menggunakan register
argumen yang tidak dapat dialamatkan ( ). Dengan demikian implementasi C yang memungkinkan rekursi sewenang-wenang dan tidak memiliki batasan jumlah register
objek dapat mengkodekan automata pushdown deterministik.
Apakah ini benar? Bisakah Anda menemukan implementasi C yang lebih kuat? Apakah implementasi C lengkap-Turing ada?
sumber
Jawaban:
Seperti disebutkan dalam pertanyaan, standar C mensyaratkan bahwa ada nilai UCHAR_MAX sehingga setiap variabel tipeUCHAR_MAXsizeof(unsigned char∗) objek, tetapi dari perspektif teoritis keberadaan ikatan apa pun, tidak peduli seberapa besar, berarti ada sesuatu yang tidak terbatas.101010
unsigned char
akan selalu memiliki nilai antara 0 dan UCHAR_MAX, inklusif. Lebih jauh lagi mengharuskan setiap objek yang dialokasikan secara dinamis diwakili oleh urutan byte yang dapat diidentifikasi melalui pointer tipeunsigned char*
, dan bahwa ada konstansizeof(unsigned char*)
sehingga setiap pointer dari tipe tersebut dapat diidentifikasi oleh urutansizeof(unsigned char *)
nilai tipeunsigned char
. Jumlah objek yang dapat dialokasikan secara simultan secara dinamis dengan demikian terbatas pada . Tidak ada yang akan mencegah kompiler teoretis dari menetapkan nilai-nilai konstanta tersebut untuk mendukung lebih dari 10Suatu program dapat menyimpan jumlah informasi yang tidak terbatas pada stack jika tidak ada yang dialokasikan pada stack yang pernah diambil alamatnya ; dengan demikian seseorang dapat memiliki program C yang mampu melakukan beberapa hal yang tidak dapat dilakukan oleh robot berapapun dengan ukuran berapa pun. Jadi, meskipun (atau mungkin karena) akses ke variabel stack jauh lebih terbatas daripada akses ke variabel yang dialokasikan secara dinamis, C berubah dari otomat terbatas menjadi otomat push-down.
Namun, ada potensi kerutan lain: diperlukan bahwa jika suatu program memeriksa urutan nilai-nilai karakter tetap terkait panjang yang terkait dengan dua pointer ke objek yang berbeda, urutan tersebut harus unik. Karena hanya adaUCHAR_MAXsizeof(unsigned char∗) kemungkinan urutan nilai karakter, program apa pun yang membuat sejumlah pointer ke objek berbeda melebihi yang tidak dapat memenuhi standar C jika kode pernah memeriksa urutan karakter yang terkait dengan pointer tersebut . Akan tetapi, dalam beberapa kasus mungkin bagi kompiler untuk menentukan bahwa tidak ada kode yang akan memeriksa urutan karakter yang terkait dengan pointer. Jika setiap "char" benar-benar mampu menahan bilangan bulat terbatas, dan memori mesin adalah urutan bilangan bulat tak terhingga [diberikan mesin Turing tanpa pita, orang dapat meniru mesin seperti itu meskipun akan sangat lambat], kemudian memang mungkin untuk membuat bahasa C-Turing lengkap.
sumber
Dengan pustaka threading C11 (opsional), dimungkinkan untuk membuat implementasi Turing yang lengkap mengingat kedalaman rekursi yang tidak terbatas.
Membuat utas baru menghasilkan tumpukan kedua; dua tumpukan sudah cukup untuk kelengkapan Turing. Satu tumpukan mewakili apa yang ada di sebelah kiri kepala, tumpukan lainnya apa yang ada di sebelah kanan.
sumber
Saya pikir itu Turing lengkap : kita dapat menulis sebuah program yang mensimulasikan UTM menggunakan trik ini (saya dengan cepat menulis kode dengan tangan sehingga mungkin ada beberapa kesalahan sintaksis ... tapi saya harap tidak ada kesalahan (utama) dalam logika :-)
The
head
akan menjadi pointer kecell_t
strukturstacker
fungsi ketika membaca simbol terakhir dari pita input (menggunakan readchar)EDIT: setelah berpikir sedikit tentang itu, ada masalah dengan petunjuk ...
jika setiap panggilan fungsi rekursif
stacker
dapat mempertahankan pointer yang valid ke variabel yang ditentukan secara lokal di pemanggil maka semuanya baik-baik saja ; jika tidak, algoritme saya tidak dapat mempertahankan daftar tautan ganda yang valid pada rekursi tak terbatas (dan dalam hal ini a tidak melihat cara untuk menggunakan rekursi untuk mensimulasikan penyimpanan akses acak tak terbatas).sumber
stacker
newcell
stacker
sizeof(cell_t)
Selama Anda memiliki ukuran tumpukan panggilan yang tidak terbatas, Anda dapat menyandikan rekaman Anda di tumpukan panggilan, dan mengaksesnya secara acak dengan memutar penumpukan tumpukan tanpa kembali dari panggilan fungsi.Suntingan : Jika Anda hanya dapat menggunakan ram, yang terbatas, konstruksi ini tidak berfungsi lagi, jadi lihat di bawah.
Namun sangat dipertanyakan mengapa stack Anda bisa tidak terbatas tetapi ram intrinsiknya tidak.
Jadi sebenarnya saya akan mengatakan Anda bahkan tidak dapat mengenali semua bahasa biasa, karena jumlah negara dibatasi (jika Anda tidak menghitung trik tumpukan-mundur untuk mengeksploitasi tumpukan tak terbatas).Saya bahkan akan berspekulasi bahwa jumlah bahasa yang dapat Anda kenali adalah terbatas (bahkan jika bahasa itu sendiri bisa tak terbatas, misalnyaa*
tidak apa-apa, tetapib^k
hanya berfungsi untuk sejumlah terbatask
s).EDIT : Ini tidak benar, karena Anda dapat menyandikan status saat ini dalam fungsi tambahan, sehingga Anda benar-benar dapat mengenali SEMUA bahasa biasa.
Anda kemungkinan besar bisa mendapatkan semua bahasa Tipe-2 untuk alasan yang sama, tetapi saya tidak yakin apakah Anda bisa mengatur keduanya, state dan stack-constent pada call-stack. Tetapi pada catatan umum, Anda dapat secara efektif melupakan ram, karena Anda selalu dapat skala ukuran robot sehingga alfabet Anda melebihi kapasitas ram. Jadi jika Anda bisa mensimulasikan TM dengan hanya tumpukan, Tipe-2 akan sama dengan Tipe-0, bukan?
sumber
Saya memikirkan hal ini sekali, dan memutuskan untuk mencoba menerapkan bahasa bebas-konteks dengan menggunakan semantik yang diharapkan; bagian penting dari implementasi adalah fungsi berikut:
Setidaknya, saya pikir ini berhasil. Mungkin saya membuat beberapa kesalahan mendasar.
Versi tetap:
sumber
it = *it
harus diganti denganit = * (void **) it
, karena selain itu*it
adalah tipevoid
.Sejalan dengan jawaban @ supercat:
Klaim ketidaklengkapan C tampaknya berpusat di sekitar bahwa objek yang berbeda harus memiliki alamat yang berbeda, dan set alamat diasumsikan terbatas. Seperti @supercat menulis
unsigned char*
sizeof(unsigned char*)
sizeof(unsigned char*)
Pada titik ini, orang harus memeriksa bahwa standar C memang memungkinkan.
sizeof
sumber
uintptr_t p = (uintptr_t)sizeof(void*)
(menempatkan \ omega ke dalam sesuatu yang memegang bilangan bulat tak bertanda). Saya tidak tahu. Kami mungkin lolos dengan mendefinisikan hasil menjadi 0 (atau nomor lainnya).uintptr_t
harus menjadi tak terbatas juga. Pikiran Anda, tipe ini opsional - tetapi jika Anda memiliki jumlah nilai pointer berbeda yang tak terbatas makasizeof(void*)
harus juga tak terbatas, jadisize_t
harus tak terbatas. Keberatan saya tentang reduksi modulo tidak begitu jelas - itu hanya berlaku jika ada overflow, tetapi jika Anda mengizinkan tipe tak terbatas maka mereka mungkin tidak pernah overflow. Tetapi di sisi lain, masing-masing jenis memiliki nilai minimum dan maksimum, yang sejauh yang saya tahu menyiratkan bahwaUINT_MAX+1
harus meluap.size_t
dan tipe pointer menjadi to ∪ {ω}. Ini menghilangkan masalah min / max. Masalah dengan semantik melimpah masih tetap. Apa yang seharusnya menjadi semantikuint x = (uint)ω
tidak jelas bagi saya. Sekali lagi, kita dapat secara acak mengambil 0, tetapi itu terlihat agak jelek.