Saya selalu melihat contoh dan kasus di mana menggunakan makro lebih baik daripada menggunakan fungsi.
Bisakah seseorang menjelaskan kepada saya dengan contoh kelemahan makro dibandingkan dengan suatu fungsi?
c
function
c-preprocessor
Kyrol
sumber
sumber
Jawaban:
Makro rawan kesalahan karena mereka mengandalkan substitusi tekstual dan tidak melakukan pemeriksaan tipe. Misalnya, makro ini:
berfungsi dengan baik saat digunakan dengan integer:
tetapi melakukan hal-hal yang sangat aneh saat digunakan dengan ekspresi:
Menempatkan tanda kurung di sekitar argumen membantu tetapi tidak sepenuhnya menghilangkan masalah ini.
Saat makro berisi beberapa pernyataan, Anda bisa mendapatkan masalah dengan konstruksi aliran kontrol:
Strategi yang biasa untuk memperbaiki ini adalah dengan meletakkan pernyataan di dalam loop "lakukan {...} while (0)".
Jika Anda memiliki dua struktur yang kebetulan berisi bidang dengan nama yang sama tetapi semantik berbeda, makro yang sama mungkin berfungsi pada keduanya, dengan hasil yang aneh:
Terakhir, makro bisa jadi sulit untuk di-debug, menghasilkan kesalahan sintaks yang aneh atau kesalahan waktu proses yang harus Anda perluas agar dapat dipahami (misalnya dengan gcc -E), karena debugger tidak dapat melewati makro, seperti dalam contoh ini:
Fungsi dan konstanta sebaris membantu menghindari banyak masalah dengan makro ini, tetapi tidak selalu berlaku. Jika makro sengaja digunakan untuk menentukan perilaku polimorfik, polimorfisme yang tidak disengaja mungkin sulit dihindari. C ++ memiliki sejumlah fitur seperti templat untuk membantu membuat konstruksi polimorfik yang kompleks dengan cara yang aman untuk mengetik tanpa menggunakan makro; lihat The C ++ Programming Language dari Stroustrup untuk detailnya.
sumber
x++*x++
tidak bisa dikatakan kenaikanx
dua kali; itu sebenarnya memanggil perilaku tidak terdefinisi , yang berarti bahwa kompilator bebas untuk melakukan apapun yang diinginkannya — ia bisa bertambahx
dua kali, atau sekali, atau tidak sama sekali; itu bisa membatalkan dengan suatu kesalahan atau bahkan membuat setan terbang keluar dari hidung Anda .Fitur makro :
Fitur fungsi :
sumber
Efek sampingnya sangat besar. Berikut kasus tipikal:
diperluas ke:
x
bertambah dua kali dalam pernyataan yang sama. (dan perilaku tidak terdefinisi)Menulis makro multi-baris juga merepotkan:
Mereka membutuhkan
\
di akhir setiap baris.Makro tidak dapat "mengembalikan" apa pun kecuali Anda menjadikannya sebagai ekspresi tunggal:
Tidak dapat melakukannya di makro kecuali Anda menggunakan pernyataan ekspresi GCC. (EDIT: Anda dapat menggunakan operator koma ... mengabaikan itu ... Tapi itu mungkin masih kurang terbaca.)
Perintah Operasi: (atas kebaikan @ouah)
diperluas ke:
Tetapi
&
memiliki prioritas lebih rendah dari<
. Jadi0xFF < 42
dievaluasi dulu.sumber
min(a & 0xFF, 42)
Contoh 1:
sedangkan:
Contoh 2:
Dibandingkan dengan:
sumber
Jika ragu, gunakan fungsi (atau fungsi sebaris).
Namun jawaban di sini sebagian besar menjelaskan masalah dengan makro, alih-alih memiliki pandangan sederhana bahwa makro itu jahat karena mungkin terjadi kecelakaan konyol.
Anda bisa menyadari jebakan dan belajar menghindarinya. Kemudian gunakan makro hanya jika ada alasan kuat untuk itu.
Ada beberapa kasus luar biasa tertentu di mana ada keuntungan menggunakan makro, ini termasuk:
va_args
.misalnya: https://stackoverflow.com/a/24837037/432509 .
(
__FILE__
,__LINE__
,__func__
). periksa kondisi pra / posting, jikaassert
gagal, atau bahkan statik-asserts sehingga kode tidak akan dikompilasi pada penggunaan yang tidak benar (sebagian besar berguna untuk build debug).struct
anggota periksa yang ada sebelum transmisi(dapat berguna untuk jenis polimorfik) .
Atau periksa array memenuhi beberapa kondisi panjang.
lihat: https://stackoverflow.com/a/29926435/432509
func(FOO, "FOO");
, Anda dapat menentukan makro yang memperluas string untuk Andafunc_wrapper(FOO);
(penugasan ke beberapa variabel, untuk operasi per piksel, adalah contoh Anda mungkin lebih memilih makro daripada fungsi ... meskipun itu masih sangat bergantung pada konteks, karena
inline
fungsi dapat menjadi pilihan) .Memang, beberapa di antaranya bergantung pada ekstensi compiler yang bukan standar C. Artinya, Anda mungkin akan mendapatkan kode yang kurang portabel, atau harus
ifdef
memasukkannya, jadi mereka hanya dimanfaatkan jika kompiler mendukung.Menghindari beberapa contoh argumen
Memperhatikan hal ini karena ini adalah salah satu penyebab kesalahan paling umum di makro (meneruskan
x++
misalnya, di mana makro mungkin bertambah beberapa kali) .mungkin untuk menulis makro yang menghindari efek samping dengan beberapa contoh argumen.
C11 Generik
Jika Anda ingin memiliki
square
makro yang berfungsi dengan berbagai jenis dan memiliki dukungan C11, Anda dapat melakukan ini ...Ekspresi pernyataan
Ini adalah ekstensi kompilator yang didukung oleh GCC, Clang, EKOPath & Intel C ++ (tetapi bukan MSVC) ;
Jadi kerugian dengan makro adalah Anda perlu tahu bagaimana menggunakannya untuk memulai, dan bahwa mereka tidak didukung secara luas.
Salah satu manfaatnya adalah, dalam hal ini, Anda dapat menggunakan
square
fungsi yang sama untuk berbagai jenis.sumber
Tidak ada jenis pemeriksaan parameter dan kode yang diulang yang dapat menyebabkan kode membengkak. Sintaks makro juga dapat menyebabkan sejumlah kasus tepi aneh di mana titik koma atau urutan prioritas dapat menghalangi. Berikut tautan yang menunjukkan beberapa kejahatan makro
sumber
satu kelemahan makro adalah debugger membaca kode sumber, yang tidak memiliki makro yang diperluas, jadi menjalankan debugger dalam makro belum tentu berguna. Tak perlu dikatakan, Anda tidak bisa mengatur breakpoint di dalam makro seperti yang Anda bisa dengan fungsi.
sumber
Fungsi melakukan pemeriksaan jenis. Ini memberi Anda lapisan keamanan ekstra.
sumber
Menambahkan jawaban ini ..
Makro diganti langsung ke dalam program oleh preprocessor (karena pada dasarnya mereka adalah arahan preprocessor). Jadi mereka pasti menggunakan lebih banyak ruang memori daripada fungsi masing-masing. Di sisi lain, sebuah fungsi membutuhkan lebih banyak waktu untuk dipanggil dan mengembalikan hasil, dan overhead ini dapat dihindari dengan menggunakan makro.
Juga makro memiliki beberapa alat khusus yang dapat membantu portabilitas program pada platform yang berbeda.
Makro tidak perlu diberi tipe data untuk argumennya berbeda dengan fungsi.
Secara keseluruhan mereka adalah alat yang berguna dalam pemrograman. Dan baik instruksi makro maupun fungsi dapat digunakan tergantung pada situasinya.
sumber
Saya tidak memperhatikan, dalam jawaban di atas, salah satu keunggulan fungsi dibandingkan makro yang menurut saya sangat penting:
Fungsi bisa diberikan sebagai argumen, makro tidak bisa.
Contoh konkrit: Anda ingin menulis versi alternatif dari fungsi 'strpbrk' standar yang akan menerima, daripada daftar karakter eksplisit untuk dicari dalam string lain, fungsi (penunjuk ke a) yang akan mengembalikan 0 hingga karakter ditemukan yang lulus beberapa tes (ditentukan pengguna). Salah satu alasan Anda mungkin ingin melakukan ini adalah agar Anda dapat mengeksploitasi fungsi pustaka standar lainnya: alih-alih memberikan string eksplisit yang penuh dengan tanda baca, Anda dapat meneruskan 'ispunct' ctype.h, dll. Jika 'ispunct' hanya diterapkan sebagai makro, ini tidak akan berhasil.
Ada banyak contoh lainnya. Misalnya, jika perbandingan Anda diselesaikan dengan makro daripada fungsi, Anda tidak dapat meneruskannya ke 'qsort' stdlib.h.
Situasi analog di Python adalah 'print' dalam versi 2 vs. versi 3 (pernyataan tidak dapat dilewati vs. fungsi yang dapat dilalui).
sumber
Jika Anda meneruskan fungsi sebagai argumen ke makro, itu akan dievaluasi setiap saat. Misalnya, jika Anda memanggil salah satu makro paling populer:
seperti itu
functionThatTakeLongTime akan dievaluasi 5 kali yang secara signifikan dapat menurunkan kinerja
sumber