Apa perilaku tidak terdefinisi dalam C dan C ++? Bagaimana dengan perilaku yang tidak ditentukan dan perilaku yang ditentukan implementasi? Apa perbedaan di antara mereka?
530
Apa perilaku tidak terdefinisi dalam C dan C ++? Bagaimana dengan perilaku yang tidak ditentukan dan perilaku yang ditentukan implementasi? Apa perbedaan di antara mereka?
Jawaban:
Perilaku tidak terdefinisi adalah salah satu aspek dari bahasa C dan C ++ yang dapat mengejutkan bagi programmer yang berasal dari bahasa lain (bahasa lain mencoba menyembunyikannya dengan lebih baik). Pada dasarnya, adalah mungkin untuk menulis program C ++ yang tidak berperilaku dengan cara yang dapat diprediksi, meskipun banyak kompiler C ++ tidak akan melaporkan kesalahan dalam program!
Mari kita lihat contoh klasik:
Variabel
p
menunjuk ke string literal"hello!\n"
, dan dua tugas di bawah ini mencoba untuk memodifikasi string literal itu. Apa yang dilakukan program ini? Menurut bagian 2.14.5 paragraf 11 dari standar C ++, ini memanggil perilaku yang tidak terdefinisi :Saya dapat mendengar orang-orang berteriak, "Tapi tunggu, saya bisa mengkompilasi ini tanpa masalah dan mendapatkan output
yellow
" atau "Apa maksud Anda, string literal disimpan dalam memori read-only, sehingga upaya penugasan pertama menghasilkan dump inti". Ini persis masalah dengan perilaku yang tidak terdefinisi. Pada dasarnya, standar ini memungkinkan apa pun terjadi setelah Anda mengaktifkan perilaku yang tidak terdefinisi (bahkan setan hidung). Jika ada perilaku "benar" menurut model mental bahasa Anda, model itu salah; Standar C ++ memiliki satu-satunya suara, titik.Contoh lain dari perilaku tidak terdefinisi termasuk mengakses array di luar batasnya, mendereferensi pointer nol , mengakses objek setelah masa hidup mereka berakhir atau menulis ekspresi yang diduga seperti pintar
i++ + ++i
.Bagian 1.9 dari standar C ++ juga menyebutkan dua saudara lelaki yang berperilaku tidak terdefinisi kurang berbahaya, perilaku yang tidak ditentukan dan perilaku yang didefinisikan implementasi :
Secara khusus, bagian 1.3.24 menyatakan:
Apa yang dapat Anda lakukan untuk menghindari perilaku tidak terdefinisi? Pada dasarnya, Anda harus membaca buku C ++ yang bagus oleh penulis yang tahu apa yang mereka bicarakan. Sekrup tutorial internet. Sekrup bullschildt.
sumber
int f(){int a; return a;}
: nilaia
dapat berubah di antara panggilan fungsi.Nah, ini pada dasarnya adalah copy-paste langsung dari standar
sumber
int foo(int x) { if (x >= 0) launch_missiles(); return x << 1; }
kompiler dapat menentukan bahwa karena semua cara menjalankan fungsi yang tidak meluncurkan rudal memanggil Perilaku Tidak Terdefinisi, itu dapat membuat panggilan kelaunch_missiles()
tanpa syarat.Mungkin kata-kata yang mudah bisa lebih mudah untuk dipahami daripada definisi standar yang ketat.
perilaku yang ditentukan implementasi
Bahasa ini mengatakan bahwa kita memiliki tipe data. Vendor penyusun menentukan ukuran apa yang akan mereka gunakan, dan memberikan dokumentasi tentang apa yang mereka lakukan.
perilaku tidak terdefinisi
Anda melakukan sesuatu yang salah. Misalnya, Anda memiliki nilai yang sangat besar dalam suatu
int
yang tidak cocokchar
. Bagaimana Anda memasukkan nilai ituchar
? sebenarnya tidak mungkin! Apa pun bisa terjadi, tetapi hal yang paling masuk akal adalah mengambil byte pertama dari int itu dan memasukkannya ke dalamchar
. Itu hanya salah untuk melakukan itu untuk menetapkan byte pertama, tetapi itulah yang terjadi di bawah tenda.perilaku yang tidak ditentukan
Fungsi manakah dari keduanya yang dijalankan pertama kali?
Bahasa tidak menentukan evaluasi, kiri ke kanan atau kanan ke kiri! Jadi perilaku yang tidak ditentukan mungkin atau tidak dapat mengakibatkan perilaku yang tidak ditentukan, tetapi tentu saja program Anda tidak boleh menghasilkan perilaku yang tidak ditentukan.
@ eSKay Saya pikir pertanyaan Anda layak untuk mengedit jawaban untuk menjelaskan lebih banyak :)
Perbedaan antara implementasi yang ditentukan dan tidak ditentukan, adalah bahwa kompiler seharusnya memilih perilaku dalam kasus pertama tetapi tidak harus dalam kasus kedua. Misalnya, suatu implementasi harus memiliki satu dan hanya satu definisi
sizeof(int)
. Jadi, tidak dapat mengatakan bahwa itusizeof(int)
adalah 4 untuk sebagian dari program dan 8 untuk yang lain. Tidak seperti perilaku yang tidak ditentukan, di mana kompiler dapat mengatakan OK saya akan mengevaluasi argumen ini dari kiri ke kanan dan argumen fungsi berikutnya dievaluasi dari kanan ke kiri. Itu bisa terjadi di program yang sama, itu sebabnya disebut tidak ditentukan . Bahkan, C ++ bisa dibuat lebih mudah jika beberapa perilaku yang tidak ditentukan ditentukan. Lihatlah di sini pada jawaban Dr. Stroustrup untuk itu :sumber
fun(fun1(), fun2());
bukankah perilakunya"implementation defined"
? Lagi pula, kompiler harus memilih satu atau yang lain?"I am gonna evaluate these arguments left-to-right and the next function's arguments are evaluated right-to-left"
saya mengerti inican
terjadi. Apakah benar, dengan kompiler yang kami gunakan hari ini?Dari Dokumen Dasar Pemikiran C resmi
sumber
Perilaku Tidak Terdefinisi vs Perilaku Tidak Khusus memiliki deskripsi singkat tentang itu.
Ringkasan terakhir mereka:
sumber
Secara historis, baik Perilaku yang Ditentukan Implementasi maupun Perilaku yang Tidak Terdefinisi mewakili situasi di mana penulis Standar berharap bahwa orang yang menulis implementasi kualitas akan menggunakan penilaian untuk memutuskan jaminan perilaku apa, jika ada, yang akan berguna untuk program dalam bidang aplikasi yang dimaksud yang berjalan pada target yang dituju. Kebutuhan kode angka-akhir tingkat tinggi sangat berbeda dari kode sistem tingkat rendah, dan baik UB maupun IDB memberikan fleksibilitas kepada penulis kompiler untuk memenuhi kebutuhan yang berbeda tersebut. Baik kategori mengamanatkan bahwa implementasi berperilaku dengan cara yang bermanfaat untuk tujuan tertentu, atau bahkan untuk tujuan apa pun. Implementasi kualitas yang mengklaim cocok untuk tujuan tertentu, bagaimanapun, harus berperilaku dengan cara yang sesuai dengan tujuan tersebutapakah Standar mengharuskannya atau tidak .
Satu-satunya perbedaan antara Perilaku yang Ditentukan Implementasi dan Perilaku yang Tidak Terdefinisi adalah bahwa yang pertama mengharuskan implementasi mendefinisikan dan mendokumentasikan perilaku yang konsisten bahkan dalam kasus di mana tidak ada implementasi yang mungkin bisa dilakukan akan berguna . Garis pemisah di antara mereka bukanlah apakah secara umum akan berguna bagi implementasi untuk mendefinisikan perilaku (penulis kompiler harus mendefinisikan perilaku yang berguna ketika praktis apakah Standar mengharuskan mereka atau tidak) tetapi apakah mungkin ada implementasi di mana mendefinisikan suatu perilaku secara simultan akan mahal. dan tidak berguna . Suatu penilaian bahwa implementasi semacam itu mungkin ada tidak dengan cara apa pun, membentuk, atau membentuk, menyiratkan penilaian apa pun tentang kegunaan mendukung perilaku yang didefinisikan pada platform lain.
Sayangnya, sejak pertengahan 1990-an penulis kompiler telah mulai menafsirkan kurangnya mandat perilaku sebagai penilaian bahwa jaminan perilaku tidak sebanding dengan biaya bahkan dalam bidang aplikasi di mana mereka sangat penting, dan bahkan pada sistem di mana mereka praktis tidak ada biaya. Alih-alih memperlakukan UB sebagai undangan untuk melakukan penilaian yang masuk akal, penulis kompiler mulai memperlakukannya sebagai alasan untuk tidak melakukannya.
Misalnya diberi kode berikut:
implementasi dua-pelengkap tidak perlu mengeluarkan upaya apa pun untuk memperlakukan ekspresi
v << pow
sebagai pergeseran dua-pelengkap tanpa memperhatikan apakahv
itu positif atau negatif.Filosofi yang lebih disukai di antara beberapa penulis kompiler hari ini, bagaimanapun, akan menyarankan bahwa karena
v
hanya bisa negatif jika program akan terlibat dalam Perilaku Tidak Terdefinisi, tidak ada alasan untuk memiliki program klip kisaran negatifv
. Meskipun pergeseran nilai-nilai negatif yang dulunya didukung pada setiap penyusun signifikansi tunggal, dan sejumlah besar kode yang ada bergantung pada perilaku itu, filsafat modern akan menafsirkan fakta bahwa Standar mengatakan bahwa nilai-nilai negatif pergeseran-kiri adalah UB sebagai menyiratkan bahwa penulis kompiler harus merasa bebas untuk mengabaikannya.sumber
<<
UB pada angka negatif adalah jebakan kecil yang tidak menyenangkan dan saya senang diingatkan akan hal itu!i+j>k
menghasilkan 1 atau 0 dalam kasus di mana penambahan melimpah, asalkan tidak memiliki efek samping lain , kompiler mungkin dapat membuat beberapa optimasi besar yang tidak akan mungkin jika programmer menulis kode sebagai(int)((unsigned)i+j) > k
.C ++ standar n3337 § 1.3.10 perilaku yang ditentukan implementasi
Kadang-kadang C ++ Standard tidak memaksakan perilaku tertentu pada beberapa konstruksi tetapi sebaliknya mengatakan bahwa perilaku tertentu, yang didefinisikan dengan baik harus dipilih dan dijelaskan oleh implementasi tertentu (versi perpustakaan). Jadi pengguna masih bisa tahu persis bagaimana program akan berperilaku meskipun Standar tidak menggambarkan ini.
C ++ standar n3337 § 1.3.24 perilaku tidak terdefinisi
Ketika program menemukan konstruk yang tidak didefinisikan menurut C ++ Standard, ia diizinkan untuk melakukan apa pun yang ingin dilakukannya (mungkin mengirim email kepada saya atau mungkin mengirim email kepada Anda atau mungkin mengabaikan kode sepenuhnya).
C ++ standar n3337 § 1.3.25 perilaku yang tidak ditentukan
C ++ Standard tidak memaksakan perilaku tertentu pada beberapa konstruksi tetapi sebaliknya mengatakan bahwa perilaku tertentu, yang didefinisikan dengan baik harus dipilih ( tidak perlu dijelaskan ) dengan implementasi tertentu (versi perpustakaan). Jadi dalam kasus ketika tidak ada deskripsi yang diberikan, mungkin sulit bagi pengguna untuk mengetahui dengan tepat bagaimana program akan berperilaku.
sumber
Implementasi didefinisikan-
Tidak ditentukan -
Tidak terdefinisi-
sumber
uint32_t s;
, mengevaluasi1u<<s
kapans
33 dapat diharapkan untuk menghasilkan 0 atau mungkin menghasilkan 2, tetapi tidak melakukan hal lain yang aneh. Kompiler yang lebih baru, bagaimanapun, mengevaluasi1u<<s
dapat menyebabkan kompiler menentukan bahwa karenas
harus kurang dari 32 sebelumnya, kode apa pun sebelum atau setelah ekspresi yang hanya akan relevan jikas
telah 32 atau lebih besar dapat dihilangkan.