Saya membaca baris ini di sebuah buku:
Sangat tidak mungkin untuk membuat kompilator yang benar-benar dapat menentukan apakah fungsi C ++ akan mengubah nilai variabel tertentu atau tidak.
Paragraf tersebut membahas tentang mengapa kompilator bersikap konservatif saat memeriksa keonstanan.
Mengapa tidak mungkin membangun kompiler seperti itu?
Kompilator selalu dapat memeriksa apakah suatu variabel ditetapkan ulang, fungsi non-const sedang dipanggil di dalamnya, atau jika ia diteruskan sebagai parameter non-konst ...
c++
compiler-construction
Pemain kriket
sumber
sumber
Jawaban:
Untuk alasan yang sama bahwa Anda tidak dapat menulis program yang akan menentukan apakah program tertentu akan dihentikan. Ini dikenal sebagai masalah terputus - putus , dan itu salah satu hal yang tidak dapat dihitung.
Untuk lebih jelasnya, Anda dapat menulis kompilator yang dapat menentukan bahwa suatu fungsi memang mengubah variabel dalam beberapa kasus , tetapi Anda tidak dapat menulis kompiler yang dapat dipercaya memberi tahu Anda bahwa fungsi tersebut akan atau tidak akan mengubah variabel (atau berhenti) untuk setiap fungsi yang memungkinkan.
Berikut contoh mudahnya:
Bagaimana kompilator menentukan, hanya dengan melihat kode itu, apakah
foo
akan berubaha
? Apakah itu terjadi atau tidak tergantung pada kondisi di luar fungsi, yaitu penerapanbar
. Ada lebih dari itu untuk membuktikan bahwa masalah berhenti tidak dapat dihitung, tetapi sudah dijelaskan dengan baik di artikel Wikipedia yang ditautkan (dan di setiap buku teks teori komputasi), jadi saya tidak akan mencoba menjelaskannya dengan benar di sini.sumber
Bayangkan kompiler seperti itu ada. Mari asumsikan juga bahwa untuk kenyamanan ia menyediakan fungsi pustaka yang mengembalikan 1 jika fungsi yang diteruskan mengubah variabel tertentu dan 0 jika fungsi tidak. Lalu apa yang harus dicetak oleh program ini?
sumber
f
memodifikasi variabel - bukan apakah ia dapat memodifikasi variabel. Jawaban ini benar.modifies_variable
sumber kompiler, benar-benar membatalkan argumen Anda. (dengan asumsi open-source, tetapi intinya harus jelas)Jangan bingung "akan atau tidak akan mengubah variabel dengan masukan ini" karena "memiliki jalur eksekusi yang mengubah variabel."
Yang pertama disebut penentuan predikat buram , dan sangat tidak mungkin untuk diputuskan - selain pengurangan dari masalah penghentian, Anda bisa menunjukkan bahwa masukan mungkin berasal dari sumber yang tidak dikenal (misalnya pengguna). Ini berlaku untuk semua bahasa, tidak hanya C ++.
Pernyataan terakhir, bagaimanapun, dapat ditentukan dengan melihat pohon parse, yang merupakan sesuatu yang dilakukan oleh semua kompiler pengoptimalan. Alasan mereka melakukannya adalah karena fungsi murni (dan fungsi transparan referensial , untuk beberapa definisi transparan secara referensial ) memiliki semua jenis pengoptimalan bagus yang dapat diterapkan, seperti mudah menjadi inlinable atau nilainya ditentukan pada waktu kompilasi; tetapi untuk mengetahui apakah suatu fungsi murni, kita perlu mengetahui apakah fungsi tersebut dapat memodifikasi variabel.
Jadi, apa yang tampak sebagai pernyataan mengejutkan tentang C ++ sebenarnya adalah pernyataan sepele tentang semua bahasa.
sumber
Saya pikir kata kunci dalam "apakah fungsi C ++ atau tidak akan mengubah nilai variabel tertentu" adalah "akan". Sangat mungkin untuk membangun kompilator yang memeriksa apakah fungsi C ++ diizinkan atau tidak untuk mengubah nilai variabel tertentu, Anda tidak dapat mengatakan dengan pasti bahwa perubahan akan terjadi:
sumber
const
pemeriksaan kesehatan.Saya rasa tidak perlu memanggil masalah terputus-putus untuk menjelaskan bahwa Anda tidak dapat mengetahui secara algoritme pada waktu kompilasi apakah suatu fungsi akan mengubah variabel tertentu atau tidak.
Sebaliknya, cukup untuk menunjukkan bahwa perilaku fungsi sering kali bergantung pada kondisi waktu proses, yang tidak dapat diketahui kompilator sebelumnya. Misalnya
Bagaimana kompilator dapat memprediksi dengan pasti apakah
y
akan dimodifikasi?sumber
Ini dapat dilakukan dan compiler melakukannya sepanjang waktu untuk beberapa fungsi , ini misalnya pengoptimalan yang sepele untuk pengakses inline sederhana atau banyak fungsi murni.
Apa yang tidak mungkin adalah mengetahuinya dalam kasus umum.
Setiap kali ada panggilan sistem atau panggilan fungsi yang datang dari modul lain, atau panggilan ke metode yang berpotensi diganti, apa pun dapat terjadi, termasuk pengambilalihan yang tidak bersahabat dari penggunaan stack overflow oleh beberapa peretas untuk mengubah variabel yang tidak terkait.
Bagaimanapun Anda harus menggunakan const, menghindari global, lebih memilih referensi daripada pointer, menghindari penggunaan kembali variabel untuk tugas yang tidak terkait, dll. Yang akan membuat hidup compiler lebih mudah saat melakukan pengoptimalan agresif.
sumber
Ada beberapa cara untuk menjelaskan hal ini, salah satunya adalah Masalah Menghentikan :
Jika saya menulis program yang terlihat seperti ini:
Apakah nilai
x
berubah? Untuk menentukan ini, pertama-tama Anda harus menentukan apakahdo tons of complex stuff
bagian tersebut menyebabkan kondisi menyala - atau bahkan lebih mendasar, apakah berhenti. Itu adalah sesuatu yang tidak bisa dilakukan oleh kompilator.sumber
Sangat terkejut bahwa tidak ada jawaban yang menggunakan masalah terputus-putus secara langsung! Ada pengurangan yang sangat jelas dari masalah ini ke masalah penghentian.
Bayangkan bahwa kompilator dapat mengetahui apakah suatu fungsi mengubah nilai variabel atau tidak. Maka pasti akan dapat mengetahui apakah fungsi berikut mengubah nilai y atau tidak, dengan asumsi bahwa nilai x dapat dilacak di semua panggilan di seluruh program:
Sekarang, untuk program apa pun yang kita suka, mari kita tulis ulang sebagai:
Perhatikan bahwa, jika, dan hanya jika, program kita mengubah nilai y, apakah ia kemudian menghentikan - foo () adalah hal terakhir yang dilakukannya sebelum keluar. Ini berarti kami telah menyelesaikan masalah terputus-putus!
Apa yang ditunjukkan oleh pengurangan di atas kepada kita adalah bahwa masalah menentukan apakah nilai variabel berubah setidaknya sekeras masalah penghentian. Masalah tersendat-sendat dikenal tidak dapat dihitung, jadi yang ini harus juga.
sumber
y
. Tampak bagi saya sepertifoo()
kembali dengan cepat, dan kemudianmain()
keluar. (Juga, Anda meneleponfoo()
tanpa argumen ... itu bagian dari kebingungan saya.)Segera setelah suatu fungsi memanggil fungsi lain yang sumbernya tidak "dilihat" oleh compiler, ia juga harus berasumsi bahwa variabel telah diubah, atau mungkin ada yang salah di bawah ini. Misalnya, kita memiliki ini di "foo.cpp":
dan kami memiliki ini di "bar.cpp":
Bagaimana kompiler "tahu" yang
x
tidak berubah (atau IS berubah, lebih tepat)bar
?Saya yakin kita bisa menemukan sesuatu yang lebih kompleks, jika ini tidak cukup rumit.
sumber
const_cast
in foo, itu masih akan membuatx
perubahan - Saya melanggar kontrak yang mengatakan bahwa Anda tidak boleh mengubahconst
variabel, tetapi karena Anda dapat mengonversi apa pun menjadi "more const", danconst_cast
ada, para perancang bahasa pasti memiliki gagasan di benaknya bahwa terkadang ada alasan bagus untuk percaya bahwaconst
nilai mungkin perlu diubah.Secara umum tidak mungkin bagi kompilator untuk menentukan apakah variabel akan diubah, seperti yang telah ditunjukkan.
Saat memeriksa konstanta, pertanyaan yang menarik tampaknya adalah apakah variabel dapat diubah oleh suatu fungsi. Bahkan ini sulit dalam bahasa yang mendukung petunjuk. Anda tidak dapat mengontrol apa yang dilakukan kode lain dengan pointer, bahkan bisa dibaca dari sumber eksternal (meskipun tidak mungkin). Dalam bahasa yang membatasi akses ke memori, jenis jaminan ini dapat dimungkinkan dan memungkinkan pengoptimalan yang lebih agresif daripada C ++.
sumber
Untuk membuat pertanyaan lebih spesifik, saya menyarankan rangkaian kendala berikut mungkin ada dalam pikiran penulis buku:
Dalam konteks desain kompilator, saya pikir asumsi 1,3,4 sangat masuk akal dalam pandangan penulis kompilator dalam konteks kebenaran gen kode dan / atau pengoptimalan kode. Asumsi 2 masuk akal jika tidak ada kata kunci yang mudah menguap. Dan asumsi ini juga cukup memfokuskan pertanyaan untuk membuat penilaian terhadap jawaban yang diajukan jauh lebih pasti :-)
Dengan asumsi tersebut, alasan utama mengapa konstanta tidak dapat diasumsikan adalah karena variabel aliasing. Kompiler tidak dapat mengetahui apakah variabel lain menunjuk ke variabel const. Aliasing dapat disebabkan oleh fungsi lain dalam unit kompilasi yang sama, dalam hal ini kompilator dapat melihat seluruh fungsi dan menggunakan pohon panggilan untuk menentukan secara statis bahwa aliasing dapat terjadi. Tetapi jika aliasing disebabkan oleh sebuah pustaka atau kode asing lainnya, maka kompilator tidak memiliki cara untuk mengetahui pada entri fungsi apakah variabel adalah alias.
Anda dapat berargumen bahwa jika sebuah variabel / argumen ditandai dengan const maka itu tidak boleh berubah melalui aliasing, tetapi untuk penulis kompilator itu cukup berisiko. Bahkan dapat berisiko bagi pemrogram manusia untuk mendeklarasikan variabel const sebagai bagian dari, katakanlah proyek besar di mana dia tidak mengetahui perilaku seluruh sistem, atau OS, atau perpustakaan, untuk benar-benar mengetahui variabel menang ' t berubah.
sumber
Bahkan jika sebuah variabel dideklarasikan
const
, tidak berarti beberapa kode yang ditulis dengan buruk dapat menimpanya.keluaran:
sumber
a
danb
merupakan variabel tumpukan, danb[1]
kebetulan berada di lokasi memori yang sama dengana
.const
jika semuanya diberi labelconst
. Itu karena perilaku tidak terdefinisi adalah bagian dari C / C ++. Saya mencoba menemukan cara lain untuk menjawab pertanyaannya daripada menyebutkan masalah tersendat-sendat atau masukan eksternal manusia.Untuk memperluas komentar saya, teks buku itu tidak jelas yang mengaburkan masalah.
Saat saya berkomentar, buku itu mencoba mengatakan, "mari kita dapatkan monyet dalam jumlah tak terbatas untuk menulis setiap fungsi C ++ yang mungkin dapat ditulis. Akan ada kasus di mana jika kita memilih variabel yang (beberapa fungsi tertentu yang ditulis monyet) menggunakan, kami tidak dapat mengetahui apakah fungsi akan mengubah variabel itu. "
Tentu saja untuk beberapa (bahkan banyak) fungsi dalam aplikasi tertentu, ini dapat ditentukan oleh kompilator, dan dengan sangat mudah. Tetapi tidak untuk semua (atau sebagian besar).
Fungsi ini dapat dengan mudah dianalisis:
"foo" jelas tidak mengubah "global". Itu tidak mengubah apa pun sama sekali, dan kompiler dapat mengerjakannya dengan sangat mudah.
Fungsi ini tidak bisa begitu saja dianalisis:
Karena tindakan "foo" bergantung pada nilai yang dapat berubah saat runtime , ia tidak dapat ditentukan pada waktu kompilasi apakah ia akan memodifikasi "global".
Keseluruhan konsep ini jauh lebih sederhana untuk dipahami daripada yang dibayangkan oleh para ilmuwan komputer. Jika fungsi dapat melakukan sesuatu yang berbeda berdasarkan hal-hal yang dapat berubah saat runtime, maka Anda tidak dapat menentukan apa yang akan dilakukannya hingga berfungsi, dan setiap kali dijalankan, fungsi tersebut dapat melakukan sesuatu yang berbeda. Apakah itu terbukti tidak mungkin atau tidak, itu jelas tidak mungkin.
sumber