Saya telah membaca bahwa mengonversi fungsi pointer ke data pointer dan sebaliknya berfungsi pada sebagian besar platform tetapi tidak dijamin berfungsi. Mengapa demikian? Bukankah keduanya seharusnya hanya alamat ke memori utama dan karenanya kompatibel?
c++
c
pointers
function-pointers
gexicide
sumber
sumber
void
. Konversi dari pointer fungsi kevoid *
tidak akan mengubah representasi. Sebuahvoid *
nilai yang dihasilkan dari konversi tersebut dapat dikonversi kembali ke jenis pointer fungsi asli, menggunakan cast yang eksplisit, tanpa kehilangan informasi. Catatan : Standar ISO C tidak memerlukan ini, tetapi diperlukan untuk kesesuaian POSIX.dlsym()
- perhatikan bagian akhir 'Penggunaan Aplikasi' di mana dikatakan: Perhatikan bahwa konversi darivoid *
pointer ke pointer fungsi seperti pada:fptr = (int (*)(int))dlsym(handle, "my_function");
tidak ditentukan oleh standar ISO C. Standar ini mengharuskan konversi ini bekerja dengan benar pada penyesuaian implementasi.Jawaban:
Arsitektur tidak harus menyimpan kode dan data dalam memori yang sama. Dengan arsitektur Harvard, kode dan data disimpan dalam memori yang sama sekali berbeda. Sebagian besar arsitektur adalah arsitektur Von Neumann dengan kode dan data dalam memori yang sama tetapi C tidak membatasi dirinya hanya untuk jenis arsitektur tertentu jika memungkinkan.
sumber
CS != DS
).VirtualProtect
, yang memungkinkan Anda untuk menandai wilayah data sebagai yang dapat dieksekusi.Beberapa komputer memiliki (memiliki) ruang alamat yang terpisah untuk kode dan data. Pada perangkat keras seperti itu tidak berfungsi.
Bahasa dirancang tidak hanya untuk aplikasi desktop saat ini, tetapi untuk memungkinkannya diimplementasikan pada perangkat keras yang besar.
Sepertinya komite bahasa C tidak pernah dimaksudkan
void*
untuk berfungsi sebagai penunjuk, mereka hanya ingin penunjuk generik ke objek.The C99 Rationale mengatakan:
Catatan Tidak ada yang dikatakan tentang pointer ke fungsi di paragraf terakhir. Mereka mungkin berbeda dari petunjuk lain, dan panitia sadar akan hal itu.
sumber
void *
.sizeof(void*) == sizeof( void(*)() )
ruang akan terbuang dalam kasus di mana pointer fungsi dan pointer data ukuran yang berbeda. Ini adalah kasus umum di tahun 80-an, ketika standar C pertama ditulis.Bagi mereka yang ingat MS-DOS, Windows 3.1 dan yang lebih lama jawabannya cukup mudah. Semua ini digunakan untuk mendukung beberapa model memori yang berbeda, dengan beragam kombinasi karakteristik untuk penunjuk kode dan data.
Jadi misalnya untuk model Compact (kode kecil, data besar):
dan sebaliknya dalam model Medium (kode besar, data kecil):
Dalam hal ini Anda tidak memiliki penyimpanan terpisah untuk kode dan tanggal tetapi masih tidak dapat mengkonversi antara dua petunjuk (singkat menggunakan pengubah __near dan __far jauh non-standar).
Selain itu tidak ada jaminan bahwa meskipun pointer memiliki ukuran yang sama, bahwa mereka menunjuk ke hal yang sama - dalam model memori DOS Kecil, baik kode dan data yang digunakan dekat pointer, tetapi mereka menunjuk ke segmen yang berbeda. Jadi mengkonversi pointer fungsi ke data pointer tidak akan memberi Anda pointer yang memiliki hubungan dengan fungsi sama sekali, dan karenanya tidak ada gunanya untuk konversi seperti itu.
sumber
int*
kevoid*
memberi Anda sebuah pointer yang tidak dapat Anda lakukan apa-apa, tetapi tetap bermanfaat untuk dapat melakukan konversi. (Hal ini karenavoid*
dapat menyimpan setiap objek pointer, sehingga dapat digunakan untuk algoritma generik yang tidak perlu tahu apa jenis yang mereka pegang Hal yang sama dapat berguna untuk fungsi pointer juga, jika diizinkan..)int *
kevoid *
,void *
dijamin untuk setidaknya menunjuk ke objek yang sama seperti aslinyaint *
- jadi ini berguna untuk algoritma umum yang mengakses objek menunjuk-ke, sepertiint n; memcpy(&n, src, sizeof n);
. Dalam kasus di mana mengkonversi pointer fungsi kevoid *
tidak menghasilkan pointer menunjuk pada fungsi, itu tidak berguna untuk algoritma seperti itu - satu-satunya hal yang bisa Anda lakukan adalah mengonversivoid *
kembali ke pointer fungsi lagi, jadi Anda mungkin bisa baik hanya menggunakan yangunion
berisivoid *
dan fungsi pointer.void*
memang menunjuk ke fungsi, saya kira itu akan menjadi ide yang buruk bagi orang untuk meneruskannyamemcpy
. :-Pvoid
. Konversi dari pointer fungsi kevoid *
tidak akan mengubah representasi. Sebuahvoid *
nilai yang dihasilkan dari konversi tersebut dapat dikonversi kembali ke jenis pointer fungsi asli, menggunakan cast yang eksplisit, tanpa kehilangan informasi. Catatan : Standar ISO C tidak memerlukan ini, tetapi diperlukan untuk kesesuaian POSIX.Pointer untuk membatalkan seharusnya mampu mengakomodasi pointer ke semua jenis data - tetapi tidak harus pointer ke suatu fungsi. Beberapa sistem memiliki persyaratan berbeda untuk pointer ke fungsi daripada pointer ke data (misalnya, ada DSP dengan pengalamatan berbeda untuk data vs kode, model medium pada MS-DOS menggunakan pointer 32-bit untuk kode tetapi hanya pointer 16-bit untuk data) .
sumber
Selain apa yang sudah dikatakan di sini, menarik untuk melihat POSIX
dlsym()
:sumber
void*
dan kembali.void*
kompatibel dengan pointer fungsi, sedangkan POSIX melakukannya.C ++ 11 memiliki solusi untuk ketidakcocokan jangka panjang antara C / C ++ dan POSIX
dlsym()
. Satu dapat digunakanreinterpret_cast
untuk mengonversi penunjuk fungsi ke / dari penunjuk data selama implementasinya mendukung fitur ini.Dari standar, 5.2.10 para. 8, "mengubah pointer fungsi ke tipe pointer objek atau sebaliknya didukung oleh kondisi." 1.3.5 mendefinisikan "yang didukung secara kondisional" sebagai "program yang menyatakan bahwa suatu implementasi tidak diperlukan untuk mendukung".
sumber
-Werror
.). Solusi yang lebih baik (dan non-UB) adalah untuk mengambil pointer ke objek yang dikembalikan olehdlsym
(yaituvoid**
) dan mengubahnya menjadi pointer ke fungsi pointer . Masih implementasi yang ditentukan tetapi tidak lagi menyebabkan peringatan / kesalahan .dlsym
danGetProcAddress
mengkompilasi tanpa peringatan.-pedantic
) tidak memperingatkan. Sekali lagi, tidak ada spekulasi yang mungkin.Bergantung pada arsitektur target, kode dan data dapat disimpan di area memori yang secara fundamental tidak kompatibel dan berbeda secara fisik.
sumber
void *
cukup besar untuk menampung data pointer apa pun, tetapi tidak harus fungsi pointer apa pun.undefined tidak selalu berarti tidak diperbolehkan, itu dapat berarti bahwa implementator kompiler memiliki lebih banyak kebebasan untuk melakukannya seperti yang mereka inginkan.
Sebagai contoh, beberapa arsitektur mungkin tidak dimungkinkan - undefined memungkinkan mereka untuk tetap memiliki pustaka 'C' yang sesuai bahkan jika Anda tidak dapat melakukan ini.
sumber
Solusi lain:
Dengan asumsi POSIX menjamin fungsi dan pointer data memiliki ukuran dan representasi yang sama (saya tidak dapat menemukan teks untuk ini, tetapi contoh yang dikutip OP menyarankan mereka setidaknya bermaksud membuat persyaratan ini), berikut ini harus berfungsi:
Ini menghindari melanggar aturan aliasing dengan melalui
char []
representasi, yang diizinkan untuk alias semua tipe.Namun pendekatan lain:
Tetapi saya akan merekomendasikan
memcpy
pendekatan jika Anda ingin benar-benar 100% benar C.sumber
Mereka dapat menjadi tipe yang berbeda dengan kebutuhan ruang yang berbeda. Menetapkan ke salah satu dapat irreversible mengiris nilai pointer sehingga memberikan hasil yang berbeda.
Saya percaya mereka dapat jenis yang berbeda karena standar tidak ingin membatasi kemungkinan implementasi yang menghemat ruang saat tidak diperlukan atau ketika ukuran dapat menyebabkan CPU harus melakukan omong kosong tambahan untuk menggunakannya, dll ...
sumber
Satu-satunya solusi yang benar-benar portabel adalah tidak digunakan
dlsym
untuk fungsi, dan sebaliknya digunakandlsym
untuk mendapatkan pointer ke data yang berisi pointer fungsi. Misalnya, di perpustakaan Anda:dan kemudian di aplikasi Anda:
Kebetulan, ini adalah praktik desain yang baik, dan membuatnya mudah untuk mendukung pemuatan dinamis melalui
dlopen
dan statis menghubungkan semua modul pada sistem yang tidak mendukung tautan dinamis, atau di mana pengguna / integrator sistem tidak ingin menggunakan tautan dinamis.sumber
foo_module
strukturnya sendiri (dengan nama unik), Anda dapat membuat file tambahan dengan arraystruct { const char *module_name; const struct module *module_funcs; }
dan fungsi sederhana untuk mencari tabel ini untuk modul yang ingin Anda "muat" dan mengembalikan penunjuk yang benar, kemudian gunakan ini di tempatdlopen
dandlsym
.Contoh modern di mana fungsi pointer dapat berbeda ukurannya dari pointer data: pointer fungsi anggota kelas C ++
Dikutip langsung dari https://blogs.msdn.microsoft.com/oldnewthing/20040209-00/?p=40713/
tl; dr: Saat menggunakan banyak pewarisan, penunjuk ke fungsi anggota dapat (tergantung pada kompiler, versi, arsitektur, dll) sebenarnya disimpan sebagai
yang jelas lebih besar dari a
void *
.sumber
Pada sebagian besar arsitektur, pointer ke semua tipe data normal memiliki representasi yang sama, jadi casting di antara tipe-tipe pointer data adalah no-op.
Namun, bisa dibayangkan bahwa pointer fungsi mungkin memerlukan representasi yang berbeda, mungkin mereka lebih besar dari pointer lainnya. Jika void * dapat menyimpan fungsi pointer, ini berarti bahwa representasi void * harus berukuran lebih besar. Dan semua cast data pointer ke / dari void * harus melakukan salinan tambahan ini.
Sebagai seseorang yang disebutkan, jika Anda membutuhkan ini, Anda dapat mencapainya menggunakan serikat pekerja. Tetapi sebagian besar penggunaan void * hanya untuk data, jadi akan sulit untuk meningkatkan semua penggunaan memori mereka kalau-kalau pointer fungsi perlu disimpan.
sumber
Saya tahu bahwa ini belum mengomentari sejak 2012, tapi saya pikir itu akan berguna untuk menambahkan bahwa saya lakukan tahu arsitektur yang memiliki sangat pointer tidak kompatibel untuk data dan fungsi karena panggilan pada yang cek arsitektur hak istimewa dan membawa informasi tambahan. Tidak ada jumlah casting yang akan membantu. Itu The Mill .
sumber