Saat ini saya sedang mempelajari petunjuk dan profesor saya memberikan potongan kode ini sebagai contoh:
//We cannot predict the behavior of this program!
#include <iostream>
using namespace std;
int main()
{
char * s = "My String";
char s2[] = {'a', 'b', 'c', '\0'};
cout << s2 << endl;
return 0;
}
Dia menulis di komentar bahwa kami tidak dapat memprediksi perilaku program. Apa sebenarnya yang membuatnya tidak dapat diprediksi? Saya tidak melihat ada yang salah dengan itu.
s
, program, jika diterima oleh beberapa kompilator, secara formal memiliki perilaku yang tidak dapat diprediksi.Jawaban:
Perilaku program tidak ada, karena bentuknya buruk.
Ini ilegal. Sebelum 2011, sudah tidak digunakan lagi selama 12 tahun.
Baris yang benar adalah:
Selain itu, programnya baik-baik saja. Profesor Anda harus minum lebih sedikit wiski!
sumber
Jawabannya adalah: itu tergantung pada standar C ++ yang Anda kompilasi. Semua kode dibentuk dengan sempurna di semua standar ‡ dengan pengecualian baris ini:
Sekarang, string literal memiliki tipe
const char[10]
dan kami mencoba untuk menginisialisasi pointer non-const untuk itu. Untuk semua jenis selainchar
keluarga string literal, inisialisasi seperti itu selalu ilegal. Sebagai contoh:Namun, di pra-C ++ 11, untuk string literal, ada pengecualian di §4.2 / 2:
Jadi di C ++ 03, kodenya baik-baik saja (meskipun usang), dan memiliki perilaku yang jelas dan dapat diprediksi.
Di C ++ 11, blok itu tidak ada - tidak ada pengecualian untuk string literal yang diubah menjadi
char*
, dan kode sama buruknya denganint*
contoh yang baru saja saya berikan. Kompilator berkewajiban untuk mengeluarkan diagnostik, dan idealnya dalam kasus seperti ini yang merupakan pelanggaran yang jelas dari sistem tipe C ++, kita mengharapkan kompilator yang baik tidak hanya menyesuaikan dalam hal ini (misalnya dengan mengeluarkan peringatan) tetapi gagal sekaligus.Kode idealnya tidak dapat dikompilasi - tetapi dilakukan pada gcc dan clang (saya berasumsi karena mungkin ada banyak kode di luar sana yang akan rusak dengan sedikit keuntungan, meskipun lubang sistem jenis ini tidak digunakan lagi selama lebih dari satu dekade). Kode tersebut berbentuk buruk, dan oleh karena itu tidak masuk akal untuk mempertimbangkan seperti apa perilaku kode tersebut. Tetapi mengingat kasus khusus ini dan riwayatnya sebelumnya diizinkan, saya tidak percaya itu menjadi peregangan yang tidak masuk akal untuk menafsirkan kode yang dihasilkan seolah-olah itu implisit
const_cast
, sesuatu seperti:Dengan itu, program lainnya baik-baik saja, karena Anda tidak pernah benar-benar menyentuhnya
s
lagi. Membaca sebuah diciptakan-const
objek melalui nonconst
pointer ini sangat OK. Menulis sebuah diciptakan-const
objek melalui pointer tersebut adalah perilaku undefined:Karena tidak ada modifikasi melalui
s
mana pun di kode Anda, program ini baik-baik saja di C ++ 03, seharusnya gagal dikompilasi di C ++ 11 tetapi tetap melakukannya - dan mengingat bahwa kompiler mengizinkannya, masih tidak ada perilaku yang tidak ditentukan di dalamnya † . Dengan kelonggaran bahwa kompiler masih [salah] menafsirkan aturan C ++ 03, saya tidak melihat apa pun yang akan menyebabkan perilaku "tidak dapat diprediksi". Tulislahs
, dan semua taruhan dibatalkan. Di C ++ 03 dan C ++ 11.† Meskipun, sekali lagi, menurut definisi kode yang tidak benar tidak menghasilkan harapan perilaku yang wajar
‡ Kecuali tidak, lihat jawaban Matt McNabb
sumber
Jawaban lain telah menutupi bahwa program ini salah format dalam C ++ 11 karena penugasan
const char
array ke achar *
.Namun program ini juga buruk bentuknya sebelum C ++ 11.
Ada
operator<<
kelebihan beban<ostream>
. Persyaratan untukiostream
disertakanostream
telah ditambahkan dalam C ++ 11.Secara historis, sebagian besar implementasi telah
iostream
menyertakanostream
, mungkin untuk kemudahan implementasi atau mungkin untuk memberikan QoI yang lebih baik.Tapi itu akan sesuai untuk
iostream
hanya mendefinisikanostream
kelas tanpa mendefinisikanoperator<<
kelebihan beban.sumber
Satu-satunya hal yang sedikit salah yang saya lihat dengan program ini adalah Anda tidak seharusnya menetapkan literal string ke
char
penunjuk yang bisa berubah , meskipun ini sering diterima sebagai ekstensi kompiler.Jika tidak, program ini tampak terdefinisi dengan baik bagi saya:
cout << s2
) didefinisikan dengan baik.operator<<
dengan achar*
(atau aconst char*
).#include <iostream>
termasuk<ostream>
, yang pada gilirannya menentukanoperator<<(ostream&, const char*)
, sehingga semuanya tampak pada tempatnya.sumber
Anda tidak dapat memprediksi perilaku kompilator, karena alasan yang disebutkan di atas. ( Seharusnya gagal untuk dikompilasi, tetapi mungkin tidak.)
Jika kompilasi berhasil, maka perilakunya didefinisikan dengan baik. Anda pasti bisa memprediksi perilaku program.
Jika gagal dikompilasi, tidak ada program. Dalam bahasa yang dikompilasi, program ini dapat dieksekusi, bukan kode sumbernya. Jika Anda tidak memiliki file yang dapat dieksekusi, Anda tidak memiliki program, dan Anda tidak dapat berbicara tentang perilaku sesuatu yang tidak ada.
Jadi menurut saya pernyataan prof Anda salah. Anda tidak dapat memprediksi perilaku kompilator ketika dihadapkan dengan kode ini, tetapi itu berbeda dari perilaku program . Jadi jika dia akan memilih telur kutu, dia sebaiknya memastikan dia benar. Atau, tentu saja, Anda mungkin salah mengutipnya dan kesalahan itu terletak pada terjemahan Anda atas apa yang dia katakan.
sumber
Seperti yang telah dicatat orang lain, kode tersebut tidak sah di bawah C ++ 11, meskipun itu valid di bawah versi sebelumnya. Akibatnya, compiler untuk C ++ 11 diperlukan untuk mengeluarkan setidaknya satu diagnostik, tetapi perilaku compiler atau sistem build lainnya tidak ditentukan di luar itu. Tidak ada dalam Standar yang akan melarang kompilator untuk keluar secara tiba-tiba sebagai tanggapan atas kesalahan, meninggalkan file objek yang ditulis sebagian yang mungkin dianggap valid oleh linker, menghasilkan file eksekusi yang rusak.
Meskipun kompilator yang baik harus selalu memastikan sebelum keluar bahwa file objek apa pun yang diharapkan dihasilkan akan valid, tidak ada, atau dikenali sebagai tidak valid, masalah seperti itu berada di luar yurisdiksi Standar. Meskipun secara historis ada (dan mungkin masih) beberapa platform di mana kompilasi yang gagal dapat menghasilkan file yang dapat dieksekusi yang tampak sah yang macet secara sewenang-wenang ketika dimuat (dan saya harus bekerja dengan sistem di mana kesalahan tautan sering memiliki perilaku seperti itu) , Saya tidak akan mengatakan bahwa konsekuensi kesalahan sintaks pada umumnya tidak dapat diprediksi. Pada sistem yang baik, upaya membangun umumnya akan menghasilkan executable dengan upaya terbaik kompiler pada pembuatan kode, atau tidak akan menghasilkan eksekusi sama sekali. Beberapa sistem akan meninggalkan eksekusi lama setelah gagal dibangun,
Preferensi pribadi saya adalah untuk sistem berbasis disk untuk mengganti nama file output, untuk memungkinkan kesempatan langka ketika eksekusi itu akan berguna sambil menghindari kebingungan yang dapat diakibatkan oleh secara keliru percaya bahwa seseorang menjalankan kode baru, dan untuk pemrograman tertanam sistem untuk memungkinkan programmer untuk menentukan untuk setiap proyek program yang harus dimuat jika executable yang valid tidak tersedia dengan nama normal [idealnya sesuatu yang dengan aman menunjukkan kurangnya program yang bisa digunakan]. Perangkat sistem tertanam umumnya tidak memiliki cara untuk mengetahui apa yang harus dilakukan oleh program semacam itu, tetapi dalam banyak kasus seseorang yang menulis kode "nyata" untuk suatu sistem akan memiliki akses ke beberapa kode pengujian perangkat keras yang dapat dengan mudah diadaptasi ke tujuan. Saya tidak tahu bahwa saya telah melihat perilaku penggantian nama, namun,
sumber