Mengapa tidak mungkin membuat kompiler yang dapat menentukan apakah fungsi C ++ akan mengubah nilai variabel tertentu?

104

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 ...

Pemain kriket
sumber
24
Hal pertama yang terlintas dalam pikiran saya adalah perpustakaan tautan dinamis. Jika saya mengkompilasi kode pada mesin saya, dan Anda mengkompilasi kode pada mesin Anda, dan kami menautkannya pada waktu proses , bagaimana kompilator Anda tahu apakah saya memodifikasi variabel atau tidak?
Mooing Duck
4
@MooingDuck Persis ini. Lebih luas lagi, kompilator tidak mengkompilasi fungsi secara individual, tetapi mengkompilasinya sebagai bagian dari gambaran yang lebih luas yang mungkin tidak semuanya berada dalam cakupan kompiler.
disebut2voyage
3
"tidak mungkin" mungkin pernyataan yang berlebihan - "secara komputasi tidak layak" (seperti dalam NP-hard) mungkin merupakan karakterisasi yang lebih baik, tetapi sedikit lebih sulit untuk dipahami oleh siswa. Bayangkan daftar tertaut atau struktur data abstrak lainnya. Jika saya memanggil fungsi yang mengubah satu node dalam daftar / pohon / apa pun, bagaimana bisa kompiler berharap untuk membuktikan dengan tepat node mana yang dimodifikasi (dan mungkin yang lebih penting, mana yang tidak) tanpa pada dasarnya mensimulasikan program sepenuhnya dengan masukan yang diharapkan, sambil tidak mengambil 3 hari untuk mengkompilasi satu file sumber ...
twalberg
36
@twalberg Impossible bukanlah pernyataan yang berlebihan, masalah Halting berlaku di sini seperti yang dijelaskan beberapa jawaban. Tidaklah mungkin untuk sepenuhnya menganalisis program umum secara algoritme.
Fiktik
5
@twalberg Kompiler yang hanya mengkompilasi subset program yang valid tidak terlalu berguna.
Caleb

Jawaban:

139

Mengapa tidak mungkin membangun kompiler seperti itu?

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:

void foo() {
    if (bar() == 0) this->a = 1;
}

Bagaimana kompilator menentukan, hanya dengan melihat kode itu, apakah fooakan berubah a? Apakah itu terjadi atau tidak tergantung pada kondisi di luar fungsi, yaitu penerapan bar. 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.

Caleb
sumber
48
@mrsoltys, komputer kuantum "hanya" secara eksponensial lebih cepat untuk beberapa masalah, mereka tidak dapat memecahkan masalah yang tidak dapat diputuskan.
zch
8
@mrsoltys Algoritme yang sangat rumit secara eksponensial (seperti pemfaktoran) sangat cocok untuk komputer kuantum, tetapi masalah penghentian adalah dilema logis, tidak dapat dihitung apa pun jenis "komputer" yang Anda miliki.
user1032613
7
@mrsoltys, hanya untuk menjadi sok pintar, ya, itu akan berubah. Sayangnya, itu berarti algoritme dihentikan dan masih berjalan, sayangnya, Anda tidak dapat mengetahui yang mana tanpa mengamati secara langsung, yang dengannya Anda memengaruhi keadaan sebenarnya.
Nathan Ernst
9
@ ThorbjørnRavnAndersen: Oke, jadi, misalkan saya sedang menjalankan program. Bagaimana tepatnya saya menentukan apakah itu akan dihentikan?
ruakh
8
@ ThorbjørnRavnAndersen Tetapi jika Anda benar - benar menjalankan program, dan program tidak berhenti (mis. Loop tak terbatas), Anda tidak akan pernah tahu bahwa itu tidak berhenti ... Anda hanya terus mengeksekusi satu langkah lagi, karena bisa jadi yang terakhir ...
MaxAxeHax
124

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?

int variable = 0;

void f() {
    if (modifies_variable(f, variable)) {
        /* do nothing */
    } else {
        /* modify variable */
        variable = 1;
    }
}

int main(int argc, char **argv) {
    if (modifies_variable(f, variable)) {
        printf("Modifies variable\n");
    } else {
        printf("Does not modify variable\n");
    }

    return 0;
}
orlp
sumber
12
Bagus! The Saya paradoks pembohong yang ditulis oleh programmer.
Krumelur
28
Ini sebenarnya hanya adaptasi yang bagus dari bukti terkenal untuk ketidaktegasan masalah penghentian .
Konstantin Weitz
10
Dalam kasus konkret ini "modify_variable" harus mengembalikan nilai true: Setidaknya ada satu jalur eksekusi di mana variabel memang dimodifikasi. Dan jalur eksekusi tersebut dicapai setelah panggilan ke fungsi eksternal non-deterministik - jadi seluruh fungsi non-deterministik. Untuk 2 alasan ini, kompilator harus mengambil pandangan pesimistik dan memutuskan untuk memodifikasi variabel. Jika jalur untuk memodifikasi variabel dicapai setelah perbandingan deterministik (dapat diverifikasi oleh compiler) menghasilkan false (yaitu "1 == 1") maka compiler dapat dengan aman mengatakan bahwa fungsi tersebut tidak pernah mengubah variabel
Joe Pineda
6
@ JoePineda: Pertanyaannya adalah apakah fmemodifikasi variabel - bukan apakah ia dapat memodifikasi variabel. Jawaban ini benar.
Neil G
4
@JoePineda: tidak ada yang mencegah saya untuk menyalin / menempelkan kode dari modifies_variablesumber kompiler, benar-benar membatalkan argumen Anda. (dengan asumsi open-source, tetapi intinya harus jelas)
orlp
60

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.

BlueRaja - Danny Pflughoeft
sumber
5
Ini adalah jawaban terbaik, penting untuk membuat perbedaan itu.
UncleZeiv
"tidak mungkin"?
Kip
2
@Kip "sepele tidak mungkin memutuskan" mungkin berarti "tidak mungkin memutuskan, dan buktinya sepele".
fredoverflow
28

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:

void maybe(int& val) {
    cout << "Should I change value? [Y/N] >";
    string reply;
    cin >> reply;
    if (reply == "Y") {
        val = 42;
    }
}
dasblinkenlight
sumber
"Sangat mungkin untuk membangun kompiler yang memeriksa apakah fungsi C ++ dapat mengubah nilai variabel tertentu atau tidak." Tidak, tidak. Lihat jawaban Caleb. Agar kompilator mengetahui apakah foo () dapat mengubah a, ia harus mengetahui apakah mungkin bagi bar () untuk mengembalikan 0. Dan tidak ada fungsi komputasi yang dapat memberi tahu semua kemungkinan nilai kembalian dari setiap fungsi yang dapat dihitung. Jadi ada jalur kode sedemikian rupa sehingga kompilator tidak akan dapat mengetahui apakah mereka akan pernah tercapai. Jika variabel diubah hanya di jalur kode yang tidak dapat dijangkau, itu tidak akan berubah, tetapi kompiler tidak akan mendeteksinya
Martin Epsz
12
@MartinEpsz Dengan "bisa" maksud saya "diizinkan untuk berubah", bukan "mungkin bisa berubah". Saya percaya bahwa inilah yang ada dalam pikiran OP ketika berbicara tentang constpemeriksaan kesehatan.
dasblinkenlight
@dasblinkenlight Saya harus setuju bahwa saya yakin OP mungkin berarti yang pertama, "diperbolehkan atau tidak berubah", atau "mungkin atau mungkin tidak berubah" vs. "pasti tidak akan berubah". Tentu saja saya tidak bisa memikirkan skenario di mana ini akan menjadi masalah. Anda bahkan dapat memodifikasi kompilator untuk hanya menjawab "mungkin berubah" pada fungsi apa pun yang berisi baik pengenal atau panggilan ke fungsi yang memiliki atribut jawaban "mungkin berubah". Bisa dikatakan, C dan C ++ adalah bahasa yang mengerikan untuk dicoba, karena mereka memiliki definisi yang longgar. Saya pikir inilah mengapa ketidakstabilan akan menjadi masalah di C ++ sama sekali.
DDS
@MartinEpsz: "Dan tidak ada fungsi yang dapat dihitung yang dapat mengetahui semua kemungkinan nilai kembalian dari fungsi yang dapat dihitung". Saya berpikir bahwa memeriksa "semua nilai pengembalian yang mungkin" adalah pendekatan yang salah. Ada sistem matematika (maxima, mathlab) yang dapat menyelesaikan persamaan, yang artinya akan masuk akal untuk menerapkan pendekatan serupa pada fungsi. Yaitu memperlakukannya sebagai persamaan dengan beberapa hal yang tidak diketahui. Masalahnya adalah kontrol aliran + efek samping => situasi yang tidak dapat diselesaikan. IMO, tanpa itu (bahasa fungsional, tidak ada tugas / efek samping), akan mungkin untuk memprediksi program jalur mana yang akan diambil
SigTerm
16

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

int y;

int main(int argc, char *argv[]) {
   if (argc > 2) y++;
}

Bagaimana kompilator dapat memprediksi dengan pasti apakah yakan dimodifikasi?

LarsH
sumber
7

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.

kriss
sumber
1
Jika saya mengingatnya dengan benar, itulah inti dari pemrograman fungsional, bukan? Dengan hanya menggunakan deterministik murni, tanpa fungsi efek samping, kompiler bebas melakukan optimasi agresif, pra-eksekusi, pasca-eksekusi, memoisasi, dan bahkan eksekusi pada waktu kompilasi. Titik yang saya pikir banyak Penjawab mengabaikan (atau bingung tentang) adalah bahwa hal itu adalah memang mungkin untuk subset berkelakuan baik dari semua program . Dan tidak, bagian ini tidak sepele atau tidak menarik, sebenarnya ini sangat berguna. Tapi memang tidak mungkin untuk kasus umum absolut.
Joe Pineda
Overloading adalah konsep waktu kompilasi. Anda mungkin bermaksud "metode yang diganti".
fredoverflow
@FredOverflow: ya, maksud saya diganti. Overloading memang merupakan konsep waktu kompilasi. Terima kasih telah menemukannya (tentu saja jika implementasinya berasal dari unit kompilasi lain, kompilator masih dapat mengalami masalah dalam menganalisisnya, tetapi bukan itu yang saya maksudkan). Saya akan memperbaiki jawabannya.
kriss
6

Ada beberapa cara untuk menjelaskan hal ini, salah satunya adalah Masalah Menghentikan :

Dalam teori komputabilitas, masalah penghentian dapat dinyatakan sebagai berikut: "Diberikan deskripsi program komputer yang sewenang-wenang, putuskan apakah program selesai berjalan atau terus berjalan selamanya". Ini sama dengan masalah memutuskan, diberikan program dan masukan, apakah program pada akhirnya akan berhenti ketika dijalankan dengan masukan itu, atau akan berjalan selamanya.

Alan Turing membuktikan pada tahun 1936 bahwa algoritme umum untuk memecahkan masalah terputus-putus untuk semua kemungkinan pasangan masukan program tidak ada.

Jika saya menulis program yang terlihat seperti ini:

do tons of complex stuff
if (condition on result of complex stuff)
{
    change value of x
}
else
{
    do not change value of x
}

Apakah nilai xberubah? Untuk menentukan ini, pertama-tama Anda harus menentukan apakah do tons of complex stuffbagian tersebut menyebabkan kondisi menyala - atau bahkan lebih mendasar, apakah berhenti. Itu adalah sesuatu yang tidak bisa dilakukan oleh kompilator.

Timothy Shields
sumber
6

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:

foo(int x){
   if(x)
       y=1;
}

Sekarang, untuk program apa pun yang kita suka, mari kita tulis ulang sebagai:

int y;
main(){
    int x;
    ...
    run the program normally
    ...
    foo(x);
}

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.

John Doucette
sumber
Saya tidak yakin saya mengikuti alasan Anda, tentang mengapa program kami berhenti jika itu mengubah nilai y. Tampak bagi saya seperti foo()kembali dengan cepat, dan kemudian main()keluar. (Juga, Anda menelepon foo()tanpa argumen ... itu bagian dari kebingungan saya.)
LarsH
1
@ LarsH: Jika program yang dimodifikasi berhenti, fungsi terakhir yang dipanggil adalah f. Jika y dimodifikasi, f dipanggil (pernyataan lain tidak bisa mengubah y, karena hanya diperkenalkan oleh modifikasi). Oleh karena itu, jika y dimodifikasi, program akan berhenti.
MSalters
4

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":

 void foo(int& x)
 {
    ifstream f("f.dat", ifstream::binary);
    f.read((char *)&x, sizeof(x));
 }

dan kami memiliki ini di "bar.cpp":

void bar(int& x)
{
  foo(x);
}

Bagaimana kompiler "tahu" yang xtidak berubah (atau IS berubah, lebih tepat) bar?

Saya yakin kita bisa menemukan sesuatu yang lebih kompleks, jika ini tidak cukup rumit.

Mats Petersson
sumber
Kompilator dapat mengetahui bahwa x tidak berubah dalam bar jika bar x dilewatkan sebagai pass-by-reference-to-const, bukan?
Pemain kriket
Ya, tetapi jika saya menambahkan const_castin foo, itu masih akan membuat xperubahan - Saya melanggar kontrak yang mengatakan bahwa Anda tidak boleh mengubah constvariabel, tetapi karena Anda dapat mengonversi apa pun menjadi "more const", dan const_castada, para perancang bahasa pasti memiliki gagasan di benaknya bahwa terkadang ada alasan bagus untuk percaya bahwa constnilai mungkin perlu diubah.
Mats Petersson
@MatsPetersson: Saya percaya bahwa jika Anda const_cast, Anda dapat menyimpan semua bagian yang rusak karena kompiler mungkin, tetapi tidak harus mengimbanginya.
Zan Lynx
@ZanLynx: Ya, saya yakin itu benar. Tetapi pada saat yang sama, pemerannya memang ada, yang berarti bahwa seseorang yang mendesain bahasa itu memiliki semacam gagasan bahwa "kita mungkin memerlukan ini pada suatu saat" - yang berarti itu tidak dimaksudkan untuk tidak melakukan sesuatu yang berguna sama sekali.
Mats Petersson
1

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 ++.

Krumelur
sumber
2
Satu hal yang saya harap didukung dalam bahasa adalah perbedaan antara referensi (atau petunjuk) yang singkat, dapat dikembalikan, dan dapat dipertahankan. Referensi efemeral hanya dapat disalin ke referensi singkat lainnya, referensi yang dapat dikembalikan dapat disalin ke referensi singkat atau dapat dikembalikan, dan referensi tetap dapat disalin dengan cara apa pun. Nilai kembali dari suatu fungsi akan dibatasi oleh argumen paling ketat yang diteruskan sebagai parameter "dapat dikembalikan". Saya menganggap sangat disayangkan bahwa dalam banyak bahasa, ketika seseorang memberikan referensi tidak ada yang menunjukkan berapa lama itu dapat digunakan.
supercat
Itu pasti akan berguna. Tentu saja ada pola untuk ini, tetapi dalam C ++ (dan banyak bahasa lainnya) selalu memungkinkan untuk "curang".
Krumelur
Cara utama di mana .NET lebih unggul daripada Java adalah bahwa ia memiliki konsep referensi singkat, tetapi sayangnya tidak ada cara bagi objek untuk mengekspos properti sebagai referensi singkat (apa yang saya ingin lihat adalah cara kode mana yang menggunakan properti akan meneruskan referensi singkat ke kode (bersama dengan variabel sementara) yang harus digunakan untuk memanipulasi objek.
supercat
1

Untuk membuat pertanyaan lebih spesifik, saya menyarankan rangkaian kendala berikut mungkin ada dalam pikiran penulis buku:

  1. Asumsikan kompilator memeriksa perilaku fungsi tertentu sehubungan dengan konstanta variabel. Untuk kebenaran, kompilator harus mengasumsikan (karena aliasing seperti dijelaskan di bawah) jika fungsi memanggil fungsi lain, variabel diubah, jadi asumsi # 1 hanya berlaku untuk fragmen kode yang tidak melakukan panggilan fungsi.
  2. Asumsikan variabel tidak diubah oleh aktivitas asinkron atau bersamaan.
  3. Asumsikan compiler hanya menentukan apakah variabel dapat dimodifikasi, bukan apakah akan dimodifikasi. Dengan kata lain, kompilator hanya melakukan analisis statis.
  4. Asumsikan kompilator hanya mempertimbangkan kode yang berfungsi dengan benar (tidak mempertimbangkan overruns / underruns array, pointer buruk, dll.)

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.

Χpẘ
sumber
0

Bahkan jika sebuah variabel dideklarasikan const, tidak berarti beberapa kode yang ditulis dengan buruk dapat menimpanya.

//   g++ -o foo foo.cc

#include <iostream>
void const_func(const int&a, int* b)
{
   b[0] = 2;
   b[1] = 2;
}

int main() {
   int a = 1;
   int b = 3;

   std::cout << a << std::endl;
   const_func(a,&b);
   std::cout << a << std::endl;
}

keluaran:

1
2
Mark Lakata
sumber
Ini terjadi karena adan bmerupakan variabel tumpukan, dan b[1]kebetulan berada di lokasi memori yang sama dengan a.
Mark Lakata
1
-1. Perilaku Tidak Terdefinisi menghapus semua batasan pada perilaku kompiler.
MSalters
Tidak yakin dengan suara negatifnya. Ini hanyalah sebuah contoh yang menuju ke pertanyaan awal OP tentang mengapa kompilator tidak dapat mengetahui apakah sesuatu itu benar-benar constjika semuanya diberi label const. 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.
Mark Lakata
0

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:

static int global;

void foo()
{
}

"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:

static int global;

int foo()
{
    if ((rand() % 100) > 50)
    {
        global = 1;
    }
    return 1;

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.

El Zorko
sumber
apa yang Anda katakan itu benar, tetapi bahkan untuk program yang sangat sederhana yang semuanya diketahui pada waktu kompilasi Anda tidak akan dapat membuktikan apa pun, bahkan program itu tidak akan berhenti. Ini adalah masalah tersendat-sendat. Misalnya Anda dapat menulis program berdasarkan Hailstone Sequences en.wikipedia.org/wiki/Collatz_conjecture dan membuatnya kembali menjadi true jika digabungkan ke salah satu. Kompiler tidak akan dapat melakukannya (karena akan meluap dalam banyak kasus) dan bahkan ahli matematika tidak tahu apakah itu benar atau tidak.
kriss
Jika yang Anda maksud "ada beberapa program yang tampak sangat sederhana yang Anda tidak dapat membuktikan apa pun", saya sepenuhnya setuju. Tapi pembuktian Halting Problem klasik Turing pada dasarnya bergantung pada program itu sendiri yang mampu membedakan apakah itu berhenti untuk membuat kontradiksi. Karena ini matematika bukan implementasi. Tentu ada program yang sepenuhnya mungkin untuk menentukan secara statis pada waktu kompilasi apakah variabel tertentu akan dimodifikasi, dan apakah program akan berhenti. Ini mungkin tidak dapat dibuktikan secara matematis, tetapi secara praktis dapat dicapai dalam kasus tertentu.
El Zorko