Dalam arti yang paling ketat, ini bukan Perilaku Tidak Terdefinisi, tetapi implementasi-didefinisikan. Jadi, meskipun tidak disarankan jika Anda berencana untuk mendukung arsitektur non-mainstream, Anda mungkin dapat melakukannya.
Kutipan standar yang diberikan oleh interjay adalah yang baik, menunjukkan UB, tetapi itu hanya hit terbaik kedua menurut saya, karena berurusan dengan pointer-pointer aritmatika (lucu, satu secara eksplisit UB, sedangkan yang lain tidak). Ada paragraf yang membahas operasi dalam pertanyaan secara langsung:
[expr.post.incr] / [expr.pre.incr]
Operand harus [...] atau sebuah penunjuk ke tipe objek yang didefinisikan secara lengkap.
Oh, tunggu sebentar, tipe objek yang didefinisikan sepenuhnya? Itu saja? Maksudku, sungguh, ketik ? Jadi, Anda tidak perlu objek sama sekali?
Dibutuhkan sedikit bacaan untuk benar-benar menemukan petunjuk bahwa sesuatu di sana mungkin tidak begitu jelas. Karena sejauh ini, sepertinya Anda benar-benar diizinkan melakukannya, tidak ada batasan.
[basic.compound] 3
membuat pernyataan tentang apa jenis penunjuk yang mungkin dimiliki, dan karena tidak satu pun dari tiga penunjuk lainnya, hasil operasi Anda akan jelas berada di bawah 3.4: penunjuk tidak valid .
Namun itu tidak mengatakan bahwa Anda tidak diizinkan memiliki pointer yang tidak valid. Sebaliknya, ia mencantumkan beberapa kondisi normal yang sangat umum (misalnya, akhir durasi penyimpanan) di mana pointer secara teratur menjadi tidak valid. Jadi itu tampaknya hal yang diijinkan terjadi. Dan memang:
[basic.stc] 4
Tidak langsung melalui nilai penunjuk yang tidak valid dan meneruskan nilai penunjuk yang tidak valid ke fungsi deallokasi memiliki perilaku yang tidak terdefinisi. Penggunaan lain dari nilai pointer yang tidak valid memiliki perilaku yang ditentukan implementasi.
Kami melakukan "yang lain" di sana, jadi itu bukan Perilaku Tidak Terdefinisi, tetapi ditentukan oleh implementasi, dengan demikian umumnya diperbolehkan (kecuali jika implementasi secara eksplisit mengatakan sesuatu yang berbeda).
Sayangnya, itu bukan akhir dari cerita. Meskipun hasil bersih tidak berubah lagi dari sini, itu semakin membingungkan, semakin lama Anda mencari "pointer":
[basic.compound]
Nilai valid dari tipe pointer objek mewakili alamat byte dalam memori atau null pointer. Jika objek tipe T terletak di alamat A [...] dikatakan menunjuk ke objek itu, terlepas dari bagaimana nilai itu diperoleh .
[Catatan: Misalnya, alamat yang melewati ujung array akan dianggap menunjuk ke objek yang tidak terkait dari jenis elemen array yang mungkin terletak di alamat itu. [...]].
Baca sebagai: Oke, siapa peduli! Selama pointer menunjuk suatu tempat di memori , saya baik-baik saja?
[basic.stc.dynamic.safety] Nilai penunjuk adalah penunjuk yang diturunkan dengan aman [blah blah]
Baca sebagai: OK, diturunkan dengan aman, apa pun. Itu tidak menjelaskan apa ini, juga tidak mengatakan saya benar-benar membutuhkannya. Yang diturunkan dengan aman. Rupanya saya masih bisa memiliki pointer yang tidak aman dengan baik. Saya menduga bahwa melakukan dereferensi pada mereka mungkin bukan ide yang bagus, tetapi sangat memungkinkan untuk memilikinya. Itu tidak mengatakan sebaliknya.
Suatu implementasi mungkin memiliki keselamatan pointer yang santai, dalam hal ini validitas nilai pointer tidak tergantung pada apakah itu adalah nilai pointer yang diturunkan dengan aman.
Oh, jadi mungkin tidak masalah, hanya apa yang saya pikirkan. Tapi tunggu ... "mungkin tidak"? Itu artinya, mungkin juga . Bagaimana aku tahu?
Sebagai alternatif, suatu implementasi mungkin memiliki keamanan pointer yang ketat, dalam hal ini nilai pointer yang bukan nilai pointer yang diturunkan dengan aman adalah nilai pointer yang tidak valid kecuali objek lengkap yang dirujuk memiliki durasi penyimpanan dinamis dan sebelumnya telah dinyatakan dapat dijangkau
Tunggu, jadi mungkin saja saya perlu memanggil declare_reachable()
setiap pointer? Bagaimana aku tahu?
Sekarang, Anda dapat mengonversi ke intptr_t
, yang terdefinisi dengan baik, memberikan representasi integer dari pointer yang diturunkan dengan aman. Untuk yang, tentu saja, sebagai bilangan bulat, itu sah dan terdefinisi dengan baik untuk menambahkannya sesuka Anda.
Dan ya, Anda dapat mengonversi intptr_t
kembali ke sebuah pointer, yang juga terdefinisi dengan baik. Hanya saja, tidak menjadi nilai asli, itu tidak lagi dijamin bahwa Anda memiliki pointer yang diturunkan dengan aman (jelas). Namun, secara keseluruhan, untuk surat standar, sementara sedang didefinisikan implementasi, ini adalah hal yang 100% sah untuk dilakukan:
[expr.reinterpret.cast] 5
Nilai tipe integral atau tipe enumerasi dapat secara eksplisit dikonversi ke pointer. Pointer dikonversi menjadi integer dengan ukuran yang cukup [...] dan kembali ke tipe pointer yang sama [...] nilai asli; pemetaan antara pointer dan integer jika tidak ditentukan implementasi.
Tangkapan
Pointer hanyalah bilangan bulat biasa, hanya Anda yang menggunakannya sebagai pointer. Oh andai saja itu benar!
Sayangnya, ada arsitektur di mana itu tidak benar sama sekali, dan hanya menghasilkan pointer yang tidak valid (tidak men-dereferensinya, hanya dengan memasukkannya ke register pointer) akan menyebabkan jebakan.
Jadi itulah dasar "implementasi didefinisikan". Itu, dan fakta bahwa incrementing pointer setiap kali Anda inginkan, tolong bisa saja menyebabkan overflow, yang standar tidak ingin berurusan dengan. Akhir ruang alamat aplikasi mungkin tidak sesuai dengan lokasi overflow, dan Anda bahkan tidak tahu apakah ada yang namanya overflow untuk pointer pada arsitektur tertentu. Semua dalam semua itu berantakan mimpi buruk tidak dalam kaitannya dengan manfaat yang mungkin.
Berhubungan dengan kondisi objek satu-lampau di sisi lain, mudah: Implementasinya harus memastikan tidak ada objek yang pernah dialokasikan sehingga byte terakhir di ruang alamat ditempati. Jadi itu didefinisikan dengan baik karena berguna dan mudah untuk dijamin.