Saya baru-baru ini menjawab pertanyaan tentang perilaku yang tidak ditentukan dalam melakukan p < q
C kapan p
dan menunjuk q
ke objek / array yang berbeda. Itu membuat saya berpikir: C ++ memiliki perilaku yang sama (tidak terdefinisi) <
dalam kasus ini, tetapi juga menawarkan templat pustaka standar std::less
yang dijamin untuk mengembalikan hal yang sama seperti <
ketika pointer dapat dibandingkan, dan mengembalikan beberapa urutan yang konsisten ketika mereka tidak bisa.
Apakah C menawarkan sesuatu dengan fungsi serupa yang akan memungkinkan membandingkan pointer yang sewenang-wenang (dengan jenis yang sama)? Saya mencoba melihat melalui standar C11 dan tidak menemukan apa pun, tetapi pengalaman saya di C adalah urutan besarnya lebih kecil daripada di C ++, jadi saya bisa dengan mudah melewatkan sesuatu.
sumber
Jawaban:
Pada implementasi dengan model memori datar (pada dasarnya semuanya), casting untuk
uintptr_t
Just Work.(Tetapi lihat apakah perbandingan pointer ditandatangani atau tidak ditandatangani dalam 64-bit x86? Untuk diskusi apakah Anda harus memperlakukan pointer sebagai ditandatangani atau tidak, termasuk masalah pembentukan pointer di luar objek yang merupakan UB dalam C.)
Tapi sistem dengan model memori non-datar lakukan ada, dan berpikir tentang mereka dapat membantu menjelaskan situasi saat ini, seperti C ++ memiliki spesifikasi yang berbeda untuk
<
vsstd::less
.Bagian dari titik
<
pada pointer ke objek yang terpisah menjadi UB di C (atau setidaknya tidak ditentukan dalam beberapa revisi C ++) adalah untuk memungkinkan mesin aneh, termasuk model memori non-flat.Contoh terkenal adalah mode real x86-16 di mana pointer adalah segmen: offset, membentuk alamat linear 20-bit via
(segment << 4) + offset
. Alamat linear yang sama dapat diwakili oleh beberapa kombinasi seg: off yang berbeda.C ++
std::less
pada pointer pada ISA aneh mungkin perlu mahal , misalnya "menormalkan" segmen: offset pada x86-16 untuk memiliki offset <= 15. Namun, tidak ada cara portabel untuk mengimplementasikan ini. Manipulasi yang diperlukan untuk menormalkan suatuuintptr_t
(atau objek-representasi dari objek pointer) adalah implementasi khusus.Tetapi bahkan pada sistem di mana C ++
std::less
harus mahal,<
tidak harus. Misalnya, dengan mengasumsikan model memori "besar" di mana objek cocok dalam satu segmen,<
cukup bandingkan bagian offset dan bahkan tidak repot dengan bagian segmen. (Pointer di dalam objek yang sama akan memiliki segmen yang sama, dan sebaliknya itu UB dalam C. C ++ 17 diubah menjadi hanya "tidak ditentukan", yang mungkin masih memungkinkan melompati normalisasi dan hanya membandingkan offset.) Ini mengasumsikan semua pointer ke bagian mana pun suatu benda selalu menggunakan nilai yang samaseg
, tidak pernah dinormalisasi. Ini yang Anda harapkan dari ABI untuk model memori "besar" dan bukan "besar". (Lihat diskusi dalam komentar ).(Model memori semacam itu mungkin memiliki ukuran objek maksimal 64kiB misalnya, tetapi ruang alamat total maks jauh lebih besar yang memiliki ruang untuk banyak objek berukuran maksimal tersebut. ISO C memungkinkan implementasi memiliki batas ukuran objek yang lebih rendah dari nilai maks (tidak ditandai)
size_t
dapat mewakiliSIZE_MAX
,. Misalnya, bahkan pada sistem model memori datar, GNU C membatasi ukuran objek maksPTRDIFF_MAX
sehingga perhitungan ukuran dapat mengabaikan limpahan yang ditandatangani.) Lihat jawaban dan diskusi ini dalam komentar.Jika Anda ingin mengizinkan objek yang lebih besar dari suatu segmen, Anda memerlukan model memori "besar" yang harus dikhawatirkan meluap bagian offset dari pointer ketika melakukan
p++
perulangan melalui array, atau ketika melakukan aritmatika pengindeksan / penunjuk. Ini mengarah ke kode yang lebih lambat di mana-mana, tetapi mungkin berarti hal itup < q
akan bekerja untuk pointer ke objek yang berbeda, karena implementasi yang menargetkan model memori "besar" biasanya akan memilih untuk menjaga semua pointer dinormalisasi sepanjang waktu. Lihat Apa yang dekat, jauh dan petunjuk besar? - beberapa kompiler C nyata untuk mode real x86 memang memiliki opsi untuk dikompilasi untuk model "besar" di mana semua pointer default ke "besar" kecuali dinyatakan sebaliknya.Segmentasi x86 real-mode bukan satu-satunya model memori non-flat mungkin , itu hanya contoh konkret yang berguna untuk menggambarkan bagaimana itu ditangani oleh implementasi C / C ++. Dalam kehidupan nyata, implementasi diperpanjang ISO C dengan konsep
far
vsnear
pointer, yang memungkinkan programmer untuk memilih kapan mereka bisa pergi dengan hanya menyimpan / melewati bagian offset 16-bit, relatif terhadap beberapa segmen data umum.Tetapi implementasi ISO C murni harus memilih antara model memori kecil (semuanya kecuali kode dalam 64kiB yang sama dengan pointer 16-bit) atau besar atau besar dengan semua pointer menjadi 32-bit. Beberapa loop dapat dioptimalkan dengan menambah hanya bagian offset, tetapi objek pointer tidak dapat dioptimalkan menjadi lebih kecil.
Jika Anda tahu apa manipulasi sihir untuk implementasi yang diberikan, Anda bisa menerapkannya dalam C murni . Masalahnya adalah bahwa sistem yang berbeda menggunakan pengalamatan yang berbeda dan detailnya tidak diparameterisasi oleh makro portabel apa pun.
Atau mungkin tidak: itu mungkin melibatkan melihat sesuatu dari tabel segmen khusus atau sesuatu, misalnya seperti mode terproteksi x86, bukan mode nyata di mana bagian segmen dari alamat adalah indeks, bukan nilai yang dibiarkan bergeser. Anda dapat mengatur segmen yang tumpang tindih sebagian dalam mode terproteksi, dan bagian pemilih segmen alamat bahkan tidak perlu dipesan dalam urutan yang sama dengan alamat basis segmen yang sesuai. Mendapatkan alamat linear dari pointer seg: off dalam mode terproteksi x86 mungkin melibatkan pemanggilan sistem, jika GDT dan / atau LDT tidak dipetakan ke halaman yang dapat dibaca dalam proses Anda.
(Tentu saja OS mainstream untuk x86 menggunakan model memori datar sehingga basis segmen selalu 0 (kecuali untuk penggunaan
fs
ataugs
segmen penyimpanan thread-lokal ), dan hanya bagian "offset" 32-bit atau 64-bit yang digunakan sebagai penunjuk .)Anda dapat secara manual menambahkan kode untuk berbagai platform tertentu, misalnya secara default menganggap datar, atau
#ifdef
sesuatu untuk mendeteksi mode real x86 dan membaginyauintptr_t
menjadi dua bagian 16-bit untukseg -= off>>4; off &= 0xf;
kemudian menggabungkan bagian-bagian itu kembali ke angka 32-bit.sumber
p < q
apakah UB dalam C jika mereka menunjuk ke objek yang berbeda, bukan? Saya tahup - q
itu.seg
nilai objek itu dan offset yang> = offset dalam segmen tempat objek itu dimulai. C membuatnya UB untuk melakukan banyak hal antara pointer ke objek yang berbeda, termasuk hal-hal sepertitmp = a-b
dan kemudianb[tmp]
mengaksesa[0]
. Diskusi tentang aliasing pointer tersegmentasi ini adalah contoh yang baik mengapa pilihan-desain itu masuk akal.Saya pernah mencoba untuk menemukan cara mengatasi ini dan saya memang menemukan solusi yang berfungsi untuk objek yang tumpang tindih dan dalam kebanyakan kasus dengan asumsi kompiler melakukan hal yang "biasa".
Pertama-tama Anda dapat menerapkan saran dalam Bagaimana menerapkan memmove dalam standar C tanpa salinan perantara? dan kemudian jika itu tidak berhasil dilemparkan ke
uintptr
(tipe pembungkus untuk salah satuuintptr_t
atauunsigned long long
tergantung pada apakahuintptr_t
tersedia) dan mendapatkan hasil yang paling akurat (walaupun mungkin tidak masalah):sumber
Tidak
Pertama mari kita hanya mempertimbangkan pointer objek . Pointer fungsi membawa seluruh rangkaian masalah lainnya.
2 pointer
p1, p2
dapat memiliki penyandian yang berbeda dan menunjuk ke alamat yang sama sehinggap1 == p2
meskipunmemcmp(&p1, &p2, sizeof p1)
tidak 0. Arsitektur seperti itu jarang terjadi.Namun konversi dari pointer ini ke
uintptr_t
tidak memerlukan hasil integer yang sama yang mengarah ke(uintptr_t)p1 != (uinptr_t)p2
.(uintptr_t)p1 < (uinptr_t)p2
itu sendiri adalah kode hukum yang baik, oleh mungkin tidak menyediakan fungsionalitas yang diharapkan.Jika kode benar-benar perlu membandingkan pointer yang tidak terkait, bentuk fungsi pembantu
less(const void *p1, const void *p2)
dan lakukan kode spesifik platform di sana.Mungkin:
sumber