Saya tahu bahwa mereka diimplementasikan dengan sangat tidak aman di C / C ++. Tidak bisakah mereka diimplementasikan dengan cara yang lebih aman? Apakah kerugian makro benar-benar cukup buruk untuk melebihi kekuatan besar yang mereka berikan?
programming-languages
macros
Casebash
sumber
sumber
WithEvents
pengubah Visual Basic ? Anda membutuhkan sesuatu seperti makro semantik.Jawaban:
Saya pikir alasan utamanya adalah bahwa makro adalah leksikal . Ini memiliki beberapa konsekuensi:
Kompiler tidak memiliki cara untuk memeriksa bahwa makro ditutup secara semantik, yaitu bahwa ia mewakili "unit makna" seperti fungsi. (Pertimbangkan
#define TWO 1+1
- apa yangTWO*TWO
sama dengan 3.)Makro tidak diketik seperti fungsi. Kompiler tidak dapat memeriksa apakah parameter dan tipe pengembalian masuk akal. Itu hanya dapat memeriksa ekspresi diperluas yang menggunakan makro.
Jika kode tidak dikompilasi, kompiler tidak memiliki cara untuk mengetahui apakah kesalahan ada di makro itu sendiri atau tempat di mana makro digunakan. Compiler akan melaporkan tempat yang salah separuh waktu, atau harus melaporkan keduanya meskipun salah satunya mungkin baik-baik saja. (Pertimbangkan
#define min(x,y) (((x)<(y))?(x):(y))
: Apa yang harus dilakukan kompiler jika jenisx
dany
tidak cocok atau tidak diterapkanoperator<
?)Alat otomatis tidak dapat bekerja dengannya dengan cara yang bermanfaat secara semantik. Secara khusus, Anda tidak dapat memiliki hal-hal seperti IntelliSense untuk makro yang berfungsi seperti fungsi tetapi diperluas ke ekspresi. (Sekali lagi,
min
contohnya.)Efek samping dari makro tidak sejelas mereka dengan fungsi, menyebabkan potensi kebingungan bagi programmer. (Pertimbangkan lagi
min
contohnya: dalam pemanggilan fungsi, Anda tahu bahwa ekspresi untukx
dievaluasi hanya sekali, tetapi di sini Anda tidak bisa tahu tanpa melihat makro.)Seperti yang saya katakan, ini semua adalah konsekuensi dari fakta bahwa makro adalah leksikal. Ketika Anda mencoba mengubahnya menjadi sesuatu yang lebih tepat, Anda berakhir dengan fungsi dan konstanta.
sumber
Tapi ya, makro bisa dirancang dan diimplementasikan lebih baik daripada di C / C ++.
Masalah dengan makro adalah bahwa mereka secara efektif mekanisme ekstensi sintaksis bahasa yang menulis ulang kode Anda menjadi sesuatu yang lain.
Dalam kasus C / C ++, tidak ada pemeriksaan kewarasan yang mendasar. Jika Anda berhati-hati, semuanya baik-baik saja. Jika Anda melakukan kesalahan, atau jika Anda menggunakan makro secara berlebihan, Anda bisa mendapatkan masalah besar.
Tambahkan ke ini bahwa banyak hal sederhana yang dapat Anda lakukan dengan makro (gaya C / C ++) dapat dilakukan dengan cara lain dalam bahasa lain.
Dalam bahasa lain seperti berbagai dialek Lisp, makro lebih baik diintegrasikan dengan sintaksis bahasa inti, tetapi Anda masih bisa mendapatkan masalah dengan deklarasi dalam "kebocoran" makro. Ini diatasi oleh makro higienis .
Konteks Sejarah Singkat
Makro (kependekan dari instruksi makro) pertama kali muncul dalam konteks bahasa assembly. Menurut Wikipedia , makro tersedia di beberapa perakit IBM pada 1950-an.
LISP asli tidak memiliki makro, tetapi mereka pertama kali diperkenalkan ke MacLisp pada pertengahan 1960-an: https://stackoverflow.com/questions/3065606/when-did-the-ide-of-macros-user-defined-code -transformasi-muncul . http://www.csee.umbc.edu/courses/331/resources/papers/Evolution-of-Lisp.pdf . Sebelum itu, "fexprs" menyediakan fungsionalitas seperti makro.
Versi C paling awal tidak memiliki makro ( http://cm.bell-labs.com/cm/cs/who/dmr/chist.html ). Ini ditambahkan sekitar tahun 1972-73 melalui preprocessor. Sebelum itu, C hanya mendukung
#include
dan#define
.Makro-preprocessor M4 berasal sekitar tahun 1977.
Bahasa yang lebih baru rupanya menerapkan makro di mana model operasi lebih sintaksis daripada tekstual.
Jadi ketika seseorang berbicara tentang keunggulan definisi khusus dari istilah "makro", penting untuk dicatat bahwa maknanya telah berkembang dari waktu ke waktu.
sumber
Makro dapat, seperti yang dicatat Scott , memungkinkan Anda menyembunyikan logika. Tentu saja, begitu juga fungsi, kelas, perpustakaan, dan banyak perangkat umum lainnya.
Tetapi sistem makro yang kuat dapat melangkah lebih jauh, memungkinkan Anda untuk merancang dan memanfaatkan sintaks dan struktur yang biasanya tidak ditemukan dalam bahasa tersebut. Ini memang bisa menjadi alat yang luar biasa: bahasa khusus domain, pembuat kode, dan banyak lagi, semua dalam kenyamanan lingkungan bahasa tunggal ...
Namun, itu bisa disalahgunakan. Ini dapat membuat kode lebih sulit untuk dibaca, dipahami, dan didebug, menambah waktu yang diperlukan bagi programmer baru untuk menjadi terbiasa dengan basis kode, dan menyebabkan kesalahan dan penundaan yang mahal.
Jadi untuk bahasa yang dimaksudkan untuk menyederhanakan pemrograman (seperti Java atau Python), sistem seperti itu adalah laknat.
sumber
Makro dapat diimplementasikan dengan sangat aman dalam beberapa keadaan - di Lisp misalnya, makro hanyalah fungsi yang mengembalikan kode yang diubah sebagai struktur data (s-ekspresi). Tentu saja, Lisp mendapat manfaat signifikan dari fakta bahwa itu homoikonik dan fakta bahwa "kode adalah data".
Contoh betapa mudahnya makro adalah contoh Clojure ini yang menentukan nilai default untuk digunakan dalam kasus pengecualian:
Bahkan di Lisps sekalipun, saran umum adalah "jangan gunakan makro kecuali Anda harus".
Jika Anda tidak menggunakan bahasa homoikonik, maka makro menjadi lebih rumit dan berbagai opsi lain semuanya memiliki beberapa jebakan:
Selain itu, segala sesuatu yang makro dapat lakukan pada akhirnya dapat dicapai dalam beberapa cara lain dalam bahasa lengkap turing (bahkan jika ini berarti menulis banyak boilerplate). Sebagai hasil dari semua trickiness ini, tidak mengherankan bahwa banyak bahasa memutuskan bahwa makro tidak benar-benar sepadan dengan semua upaya untuk diimplementasikan.
sumber
Untuk menjawab pertanyaan Anda, pikirkan makro apa yang dominan digunakan untuk (Peringatan: kode yang dikompilasi otak).
#define X 100
Ini dapat dengan mudah diganti dengan:
const int X = 100;
#define max(X,Y) (X>Y?X:Y)
Dalam bahasa apa pun yang mendukung kelebihan fungsi, ini dapat ditiru dengan cara yang jauh lebih aman dengan memiliki fungsi berlebih dari jenis yang benar, atau, dalam bahasa yang mendukung obat generik, dengan fungsi generik. Makro akan dengan senang hati mencoba membandingkan apa pun termasuk pointer atau string, yang mungkin dikompilasi, tetapi hampir pasti bukan yang Anda inginkan. Di sisi lain, jika Anda membuat macro-safe, mereka tidak menawarkan manfaat atau kenyamanan kelebihan fungsi.
#define p printf
Ini mudah diganti oleh fungsi
p()
yang melakukan hal yang sama. Ini cukup terlibat dalam C (mengharuskan Anda untuk menggunakanva_arg()
keluarga fungsi) tetapi dalam banyak bahasa lain yang mendukung sejumlah variabel argumen fungsi, itu jauh lebih sederhana.Mendukung fitur-fitur ini dalam bahasa daripada dalam bahasa makro khusus lebih sederhana, lebih sedikit kesalahan, dan jauh lebih membingungkan bagi orang lain yang membaca kode. Bahkan, saya tidak bisa memikirkan satu kasus penggunaan untuk makro yang tidak dapat dengan mudah diduplikasi dengan cara lain. Satu- satunya tempat di mana makro benar-benar berguna adalah ketika mereka terikat pada konstruksi kompilasi bersyarat seperti
#if
(dll.).Pada titik itu, saya tidak akan berdebat dengan Anda, karena saya percaya bahwa solusi non-preprosesor untuk kompilasi bersyarat dalam bahasa populer sangat rumit (seperti injeksi bytecode di Jawa). Tetapi bahasa seperti D telah datang dengan solusi yang tidak memerlukan preprosesor dan tidak lebih rumit daripada menggunakan kondisional preprosesor, sementara jauh lebih sedikit rawan kesalahan.
sumber
In fact, I can't think of a single use-case for macros that can't easily be duplicated in another way
: di C setidaknya, Anda tidak bisa membentuk pengidentifikasi (nama variabel) menggunakan token concatenation tanpa menggunakan macro.enum
untuk menentukan kondisi dan array string konstan untuk menentukan pesan dapat mengakibatkan masalah jika enum dan array keluar dari sinkronisasi. Menggunakan makro untuk mendefinisikan semua pasangan (enum, string) dan kemudian memasukkan makro itu dua kali dengan definisi yang tepat lainnya dalam lingkup setiap kali akan membiarkan satu menempatkan setiap nilai enum di sebelah stringnya.Masalah terbesar yang saya lihat dengan makro adalah bahwa ketika banyak digunakan mereka dapat membuat kode sangat sulit untuk dibaca dan dipelihara karena mereka memungkinkan Anda untuk menyembunyikan logika di makro yang mungkin atau mungkin tidak mudah ditemukan (dan mungkin atau mungkin tidak sepele) ).
sumber
Jangan mulai dengan mencatat bahwa MACRO di C / C ++ sangat terbatas, rawan kesalahan, dan tidak terlalu berguna.
MACRO seperti yang diimplementasikan dalam say LISP, atau bahasa assembler z / OS dapat diandalkan dan sangat berguna.
Tetapi karena penyalahgunaan fungsi terbatas dalam C mereka telah mengumpulkan reputasi buruk. Jadi tidak ada yang mengimplementasikan makro lagi, alih-alih Anda mendapatkan hal-hal seperti Template yang melakukan beberapa hal-hal sederhana yang biasa dilakukan makro dan hal-hal seperti penjelasan Java yang melakukan beberapa hal yang lebih rumit yang biasa dilakukan makro.
sumber