Apa perilaku tidak terdefinisi dalam C dan C ++? Bagaimana dengan perilaku yang tidak ditentukan dan perilaku yang ditentukan implementasi? Apa perbedaan di antara mereka?
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:
#include <iostream>
int main()
{
char* p = "hello!\n"; // yes I know, deprecated conversion
p[0] = 'y';
p[5] = 'w';
std::cout << p;
}
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 :
Efek dari upaya untuk memodifikasi string literal 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 pintari++ + ++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 :
Deskripsi semantik dalam Standar Internasional ini mendefinisikan mesin abstrak nondeterministik yang diparameterisasi.
Aspek dan operasi tertentu dari mesin abstrak dijelaskan dalam Standar Internasional ini sebagai yang ditentukan implementasi (misalnya,
sizeof(int)
). Ini merupakan parameter dari mesin abstrak. Setiap implementasi harus mencakup dokumentasi yang menggambarkan karakteristik dan perilakunya dalam hal ini.Aspek dan operasi tertentu lainnya dari mesin abstrak dijelaskan dalam Standar Internasional ini sebagai tidak spesifik (misalnya, urutan evaluasi argumen untuk suatu fungsi). Jika memungkinkan, Standar Internasional ini menetapkan seperangkat perilaku yang diperbolehkan. Ini menentukan aspek nondeterministic dari mesin abstrak.
Operasi tertentu lainnya dijelaskan dalam Standar Internasional ini sebagai tidak terdefinisi (misalnya, efek dereferencing penunjuk nol). [ Catatan : Standar Internasional ini tidak menetapkan persyaratan pada perilaku program yang mengandung perilaku tidak terdefinisi. - catatan akhir ]
Secara khusus, bagian 1.3.24 menyatakan:
Perilaku yang tidak terdefinisi yang diizinkan berkisar dari mengabaikan situasi sepenuhnya dengan hasil yang tidak terduga , hingga berperilaku selama penerjemahan atau pelaksanaan program dengan cara yang terdokumentasi sebagai karakteristik lingkungan (dengan atau tanpa penerbitan pesan diagnostik), hingga penghentian terjemahan atau eksekusi (dengan penerbitan pesan diagnostik).
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.
int f(){int a; return a;}
: nilai a
dapat berubah di antara panggilan fungsi.
Nah, ini pada dasarnya adalah copy-paste langsung dari standar
3.4.1 1 perilaku yang ditentukan implementasi perilaku yang tidak ditentukan di mana setiap implementasi mendokumentasikan bagaimana pilihan itu dibuat
2 CONTOH Contoh perilaku yang ditentukan implementasi adalah penyebaran bit orde tinggi ketika integer yang ditandatangani bergeser ke kanan.
3.4.3 1 perilaku perilaku yang tidak terdefinisi , pada saat penggunaan konstruksi program yang tidak dapat diakses atau salah atau data yang salah, yang untuknya Standar Internasional ini tidak memerlukan persyaratan
2 CATATAN Kemungkinan perilaku yang tidak terdefinisi mulai dari mengabaikan situasi sepenuhnya dengan hasil yang tidak dapat diprediksi, hingga berperilaku selama penerjemahan atau pelaksanaan program dengan cara yang terdokumentasi yang menggambarkan karakteristik lingkungan (dengan atau tanpa penerbitan pesan diagnostik), hingga penghentian terjemahan atau eksekusi (dengan penerbitan pesan diagnostik).
3 CONTOH Contoh perilaku tidak terdefinisi adalah perilaku pada integer overflow.
3.4.4 1 penggunaan perilaku yang tidak ditentukan dari nilai yang tidak ditentukan, atau perilaku lain di mana Standar Internasional ini memberikan dua atau lebih kemungkinan dan tidak memaksakan persyaratan lebih lanjut yang dipilih dalam hal apa pun
2 CONTOH Contoh perilaku yang tidak ditentukan adalah urutan di mana argumen untuk fungsi dievaluasi.
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 ke launch_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 cocok char
. Bagaimana Anda memasukkan nilai itu char
? sebenarnya tidak mungkin! Apa pun bisa terjadi, tetapi hal yang paling masuk akal adalah mengambil byte pertama dari int itu dan memasukkannya ke dalam char
. 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?
void fun(int n, int m);
int fun1()
{
cout << "fun1";
return 1;
}
int fun2()
{
cout << "fun2";
return 2;
}
...
fun(fun1(), fun2()); // which one is executed first?
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 :)
karena
fun(fun1(), fun2());
bukankah perilaku "implementasi telah ditentukan"? Lagi pula, kompiler harus memilih satu atau yang lain?
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 itu sizeof(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 :
Dikatakan bahwa perbedaan antara apa yang dapat dihasilkan memberi kompiler kebebasan ini dan membutuhkan "evaluasi kiri-ke-kanan" bisa menjadi signifikan. Saya tidak yakin, tetapi dengan kompiler yang tak terhitung banyaknya "di luar sana" mengambil keuntungan dari kebebasan dan beberapa orang dengan penuh semangat mempertahankan kebebasan itu, perubahan akan sulit dan bisa memakan waktu puluhan tahun untuk menembus ke sudut yang jauh dari dunia C dan C ++. Saya kecewa karena tidak semua kompiler memperingatkan terhadap kode seperti ++ i + i ++. Demikian pula, urutan evaluasi argumen tidak ditentukan.
IMO terlalu banyak "hal" dibiarkan tidak terdefinisi, tidak ditentukan, implementasi-didefinisikan, dll. Namun, itu mudah untuk dikatakan dan bahkan untuk memberikan contoh, tetapi sulit untuk diperbaiki. Perlu juga dicatat bahwa tidak terlalu sulit untuk menghindari sebagian besar masalah dan menghasilkan kode portabel.
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 ini can
terjadi. Apakah benar, dengan kompiler yang kami gunakan hari ini?
Dari Dokumen Dasar Pemikiran C resmi
Istilah perilaku yang tidak ditentukan , perilaku yang tidak terdefinisi , dan perilaku yang ditentukan implementasi digunakan untuk mengkategorikan hasil dari program penulisan yang sifat-sifatnya tidak dijelaskan oleh Standar, atau tidak dapat, sepenuhnya dijelaskan. Tujuan mengadopsi kategorisasi ini adalah untuk memungkinkan variasi tertentu di antara implementasi yang memungkinkan kualitas implementasi menjadi kekuatan aktif di pasar serta untuk memungkinkan ekstensi populer tertentu, tanpa menghapus cap kesesuaian dengan Standar. Lampiran F to the Standard mengelompokkan perilaku yang termasuk dalam salah satu dari tiga kategori ini.
Perilaku yang tidak ditentukan memberikan keleluasaan pada implementor dalam menerjemahkan program. Garis lintang ini tidak sampai gagal menerjemahkan program.
Perilaku tidak terdefinisi memberikan lisensi implementor untuk tidak menangkap kesalahan program tertentu yang sulit didiagnosis. Ini juga mengidentifikasi bidang-bidang yang kemungkinan dapat disesuaikan dengan ekstensi bahasa: implementor dapat menambah bahasa dengan memberikan definisi tentang perilaku yang tidak terdefinisi secara resmi.
Perilaku yang ditentukan implementasi memberi pelaksana kebebasan untuk memilih pendekatan yang sesuai, tetapi mengharuskan pilihan ini dijelaskan kepada pengguna. Perilaku yang ditetapkan sebagai implementasi-didefinisikan umumnya perilaku di mana pengguna dapat membuat keputusan pengkodean yang bermakna berdasarkan definisi implementasi. Pelaksana harus memperhatikan kriteria ini ketika memutuskan seberapa luas definisi implementasi seharusnya. Seperti halnya perilaku yang tidak ditentukan, gagal menerjemahkan sumber yang mengandung perilaku yang ditentukan implementasi bukan respons yang memadai.
Perilaku Tidak Terdefinisi vs Perilaku Tidak Khusus memiliki deskripsi singkat tentang itu.
Ringkasan terakhir mereka:
Singkatnya, perilaku yang tidak ditentukan biasanya merupakan sesuatu yang tidak perlu Anda khawatirkan, kecuali perangkat lunak Anda diharuskan portabel. Sebaliknya, perilaku tidak terdefinisi selalu tidak diinginkan dan tidak boleh terjadi.
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:
int scaled_velocity(int v, unsigned char pow)
{
if (v > 250)
v = 250;
if (v < -250)
v = -250;
return v << pow;
}
implementasi dua-pelengkap tidak perlu mengeluarkan upaya apa pun untuk memperlakukan ekspresi v << pow
sebagai pergeseran dua-pelengkap tanpa memperhatikan apakah v
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 negatif v
. 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.
<<
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
perilaku, untuk program yang dibentuk dengan baik membangun dan mengoreksi data, yang tergantung pada implementasi dan bahwa setiap dokumen 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
perilaku dimana Standar Internasional ini tidak memaksakan persyaratan [Catatan: Perilaku tidak terdefinisi dapat diharapkan ketika Standar Internasional ini menghilangkan definisi perilaku yang eksplisit atau ketika suatu program menggunakan konstruksi yang salah atau data yang salah. Perilaku yang tidak terdefinisi yang diizinkan berkisar dari mengabaikan situasi sepenuhnya dengan hasil yang tidak terduga, hingga berperilaku selama penerjemahan atau pelaksanaan program dengan cara yang terdokumentasi sebagai karakteristik lingkungan (dengan atau tanpa penerbitan pesan diagnostik), hingga penghentian terjemahan atau eksekusi (dengan penerbitan pesan diagnostik). Banyak konstruksi program yang salah tidak menimbulkan perilaku tidak terdefinisi; mereka harus didiagnosis. - catatan akhir]
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
perilaku, untuk konstruksi program yang dibentuk dengan baik dan data yang benar, itu tergantung pada implementasi [Catatan: Implementasi tidak diperlukan untuk mendokumentasikan perilaku mana yang terjadi. Berbagai perilaku yang mungkin biasanya digambarkan oleh Standar Internasional ini. - catatan akhir]
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.
Implementasi didefinisikan-
Pelaksana berharap, harus didokumentasikan dengan baik, standar memberikan pilihan tetapi yakin untuk dikompilasi
Tidak ditentukan -
Sama seperti implementasi yang ditentukan tetapi tidak didokumentasikan
Tidak terdefinisi-
Apa pun bisa terjadi, rawatlah.
uint32_t s;
, mengevaluasi 1u<<s
kapan s
33 dapat diharapkan untuk menghasilkan 0 atau mungkin menghasilkan 2, tetapi tidak melakukan hal lain yang aneh. Kompiler yang lebih baru, bagaimanapun, mengevaluasi 1u<<s
dapat menyebabkan kompiler menentukan bahwa karena s
harus kurang dari 32 sebelumnya, kode apa pun sebelum atau setelah ekspresi yang hanya akan relevan jika s
telah 32 atau lebih besar dapat dihilangkan.