Dalam banyak makro C / C ++ saya melihat kode makro dibungkus dalam apa yang tampak seperti do while
loop yang tidak berarti . Berikut ini contohnya.
#define FOO(X) do { f(X); g(X); } while (0)
#define FOO(X) if (1) { f(X); g(X); } else
Saya tidak bisa melihat apa yang do while
dilakukannya. Mengapa tidak menulis saja tanpa itu?
#define FOO(X) f(X); g(X)
c++
c
c-preprocessor
c++-faq
jfm3
sumber
sumber
void
tipe di akhir ... like ((void) 0) .do while
konstruk tidak kompatibel dengan pernyataan pengembalian, sehinggaif (1) { ... } else ((void)0)
konstruk tersebut memiliki lebih banyak penggunaan yang kompatibel dalam Standar C. Dan di GNU C, Anda akan lebih suka konstruk yang dijelaskan dalam jawaban saya.Jawaban:
The
do ... while
danif ... else
ada untuk membuatnya sehingga titik koma setelah makro Anda selalu berarti hal yang sama. Katakanlah Anda memiliki sesuatu seperti makro kedua Anda.Sekarang jika Anda menggunakannya
BAR(X);
dalam sebuahif ... else
pernyataan, di mana tubuh pernyataan if tidak dibungkus dengan kurung keriting, Anda akan mendapatkan kejutan buruk.Kode di atas akan berkembang menjadi
yang secara sintaksis salah, karena yang lain tidak lagi dikaitkan dengan if. Tidak membantu untuk membungkus barang-barang dengan kurung kurawal di dalam makro, karena titik koma setelah kurung kurawal secara sintaksis salah.
Ada dua cara untuk memperbaiki masalah. Yang pertama adalah menggunakan koma untuk mengurutkan pernyataan dalam makro tanpa merampasnya dari kemampuannya untuk bertindak seperti ekspresi.
Versi bar
BAR
di atas memperluas kode di atas menjadi apa yang berikut, yang secara sintaksis benar.Ini tidak berfungsi jika alih-alih
f(X)
Anda memiliki kumpulan kode yang lebih rumit yang perlu dimasukkan dalam bloknya sendiri, katakan misalnya untuk mendeklarasikan variabel lokal. Dalam kasus yang paling umum solusinya adalah menggunakan sesuatu sepertido ... while
menyebabkan makro menjadi pernyataan tunggal yang mengambil titik koma tanpa kebingungan.Anda tidak harus menggunakan
do ... while
, Anda bisa memasak sesuatu denganif ... else
baik, meskipun ketikaif ... else
mengembang di dalamif ... else
itu mengarah ke " menggantung lain ", yang bisa membuat masalah menggantung lain yang ada semakin sulit ditemukan, seperti dalam kode berikut .Intinya adalah untuk menggunakan titik koma dalam konteks di mana titik koma menggantung salah. Tentu saja, pada saat ini dapat (dan mungkin harus) diperdebatkan bahwa akan lebih baik untuk menyatakan
BAR
sebagai fungsi aktual, bukan makro.Singkatnya,
do ... while
apakah ada untuk mengatasi kekurangan preprocessor C. Ketika pemandu gaya C memberitahu Anda untuk memberhentikan preprosesor C, ini adalah jenis hal yang mereka khawatirkan.sumber
#define BAR(X) (f(X), g(X))
kalau bukan operator diutamakan dapat mengacaukan semantik.if
pernyataan, dll, dalam kode kami menggunakan kawat gigi, maka membungkus makro seperti ini adalah cara sederhana untuk menghindari masalah.if(1) {...} else void(0)
formulir lebih aman daripadado {...} while(0)
makro untuk yang parameternya adalah kode yang termasuk dalam ekspansi makro, karena tidak mengubah perilaku break atau melanjutkan kata kunci. Sebagai contoh:for (int i = 0; i < max; ++i) { MYMACRO( SomeFunc(i)==true, {break;} ) }
menyebabkan perilaku yang tidak terduga ketikaMYMACRO
didefinisikan sebagai#define MYMACRO(X, CODE) do { if (X) { cout << #X << endl; {CODE}; } } while (0)
karena istirahat mempengaruhi loop sementara makro daripada loop untuk di situs panggilan makro.void(0)
adalah kesalahan ketik, maksudku(void)0
. Dan saya percaya ini tidak menyelesaikan masalah "menggantung lain": perhatikan tidak ada titik koma setelah(void)0
. Yang lain tergantung dalam kasus itu (misalnyaif (cond) if (1) foo() else (void)0 else { /* dangling else body */ }
) memicu kesalahan kompilasi. Inilah contoh langsung yang menunjukkannyaMacro adalah copy / paste teks yang akan dimasukkan oleh pre-processor ke dalam kode asli; penulis makro berharap penggantian akan menghasilkan kode yang valid.
Ada tiga "tips" yang baik untuk berhasil dalam hal itu:
Bantu makro berperilaku seperti kode asli
Kode normal biasanya diakhiri dengan titik koma. Haruskah kode tampilan pengguna tidak memerlukan satu ...
Ini berarti pengguna mengharapkan kompiler untuk menghasilkan kesalahan jika titik koma tidak ada.
Tetapi alasan sebenarnya yang sangat bagus adalah bahwa pada suatu saat, penulis makro mungkin perlu mengganti makro dengan fungsi asli (mungkin diuraikan). Jadi makro harus benar - benar berperilaku seperti itu.
Jadi kita harus memiliki makro yang membutuhkan semi-colon.
Menghasilkan kode yang valid
Seperti yang ditunjukkan pada jawaban jfm3, terkadang makro berisi lebih dari satu instruksi. Dan jika makro digunakan di dalam pernyataan if, ini akan bermasalah:
Makro ini dapat diperluas sebagai:
The
g
fungsi akan dieksekusi terlepas dari nilaibIsOk
.Ini berarti bahwa kita harus menambahkan ruang lingkup ke makro:
Menghasilkan kode yang valid 2
Jika makro adalah sesuatu seperti:
Kami dapat memiliki masalah lain dalam kode berikut:
Karena itu akan berkembang sebagai:
Kode ini tidak akan dikompilasi, tentu saja. Jadi, sekali lagi, solusinya menggunakan cakupan:
Kode berperilaku dengan benar lagi.
Menggabungkan efek semi-colon + scope?
Ada satu idiom C / C ++ yang menghasilkan efek ini: Loop do / while:
Do / while dapat membuat ruang lingkup, dengan demikian merangkum kode makro, dan membutuhkan semi-colon pada akhirnya, sehingga berkembang menjadi kode yang membutuhkannya.
Bonusnya?
Compiler C ++ akan mengoptimalkan loop do / while, karena post-conditionnya salah diketahui pada waktu kompilasi. Ini berarti makro seperti:
akan berkembang dengan benar sebagai
dan kemudian dikompilasi dan dioptimalkan sebagai
sumber
void doSomething() { int i = 25 ; { int i = x + 1 ; f(i) ; } ; // was MY_MACRO(32) ; }
bukan ekspansi yang benar; yangx
dalam ekspansi harus 32. Sebuah masalah yang lebih kompleks adalah apa adalah perluasanMY_MACRO(i+7)
. Dan yang lainnya adalah perluasanMY_MACRO(0x07 << 6)
. Ada banyak hal yang baik, tetapi ada beberapa huruf 'undotted' dan 't unrossed.\_\_LINE\_\_
sebagai __LINE__. IMHO, lebih baik hanya menggunakan pemformatan kode untuk kode; misalnya,__LINE__
(yang tidak memerlukan penanganan khusus). PS Saya tidak tahu apakah ini benar pada 2012; mereka telah membuat beberapa perbaikan pada mesin sejak itu.inline
fungsi sebaris (seperti yang diizinkan oleh standar)@ jfm3 - Anda punya jawaban yang bagus untuk pertanyaan itu. Anda mungkin juga ingin menambahkan bahwa idiom makro juga mencegah perilaku yang mungkin lebih berbahaya (karena tidak ada kesalahan) dengan pernyataan 'jika' yang sederhana:
memperluas ke:
yang secara sintaksis benar sehingga tidak ada kesalahan kompiler, tetapi memiliki konsekuensi yang mungkin tidak diinginkan yaitu g () akan selalu dipanggil.
sumber
Jawaban di atas menjelaskan arti dari konstruksi ini, tetapi ada perbedaan yang signifikan antara keduanya yang tidak disebutkan. Bahkan, ada alasan untuk lebih memilih
do ... while
daripadaif ... else
membangun.Masalah
if ... else
konstruknya adalah bahwa itu tidak memaksa Anda untuk meletakkan titik koma. Seperti dalam kode ini:Meskipun kami meninggalkan titik koma (karena kesalahan), kode akan diperluas ke
dan akan mengkompilasi secara diam-diam (meskipun beberapa kompiler dapat mengeluarkan peringatan untuk kode yang tidak terjangkau). Namun
printf
pernyataan itu tidak akan pernah dieksekusi.do ... while
konstruk tidak memiliki masalah seperti itu, karena satu-satunya token yang valid setelahwhile(0)
adalah titik koma.sumber
FOO(1),x++;
yang lagi akan memberi kita positif palsu. Cukup gunakando ... while
dan hanya itu.do ... while (0)
itu lebih disukai, tetapi memiliki satu kelemahan: Abreak
ataucontinue
akan mengontroldo ... while (0)
loop, bukan loop yang berisi permintaan makro. Jadiif
triknya masih memiliki nilai.break
ataucontinue
yang akan dilihat sebagai di dalam macrodo {...} while(0)
pseudo-loop Anda. Bahkan dalam parameter makro itu akan membuat kesalahan sintaksis.do { ... } while(0)
alih-alihif whatever
membangun, adalah sifat idiomatis dari itu. Thedo {...} while(0)
konstruk luas, terkenal dan banyak digunakan oleh banyak programmer. Dasar pemikiran dan dokumentasinya mudah diketahui. Tidak demikian halnya denganif
konstruknya. Oleh karena itu dibutuhkan sedikit usaha untuk grok ketika melakukan peninjauan kode.#define CHECK(call, onerr) if (0 != (call)) { onerr } else (void)0
. Itu bisa digunakan sepertiCHECK(system("foo"), break;);
, di manabreak;
dimaksudkan untuk merujuk ke loop melampirkanCHECK()
doa.Meskipun diharapkan kompiler mengoptimalkan
do { ... } while(false);
loop, ada solusi lain yang tidak memerlukan konstruksi itu. Solusinya adalah menggunakan operator koma:atau bahkan lebih eksotis:
Meskipun ini akan bekerja dengan baik dengan instruksi terpisah, itu tidak akan bekerja dengan kasus-kasus di mana variabel dibangun dan digunakan sebagai bagian dari
#define
:Dengan ini akan dipaksa untuk menggunakan do / while build.
sumber
Peterson's Algorithm
.P99 preprocessor library Jens Gustedt (ya, fakta bahwa hal seperti itu ada di pikiranku juga!) Meningkatkan
if(1) { ... } else
konstruk dengan cara yang kecil tapi signifikan dengan mendefinisikan yang berikut:Alasan untuk ini adalah bahwa, tidak seperti
do { ... } while(0)
konstruk,break
dancontinue
masih bekerja di dalam blok yang diberikan, tetapi((void)0)
menciptakan kesalahan sintaksis jika titik koma dihilangkan setelah panggilan makro, yang sebaliknya akan melewati blok berikutnya. (Sebenarnya tidak ada masalah "menggantung yang lain" di sini, karenaelse
mengikat ke yang terdekatif
, yang merupakan masalah di makro.)Jika Anda tertarik pada hal-hal yang dapat dilakukan lebih atau kurang aman dengan preproses C, periksa perpustakaan itu.
sumber
break
(ataucontinue
) di dalam makro untuk mengontrol loop yang dimulai / berakhir di luar, itu hanya gaya yang buruk dan menyembunyikan titik keluar potensial.else ((void)0)
adalah seseorang mungkin menulisYOUR_MACRO(), f();
dan secara sintaksis valid, tetapi tidak pernah meneleponf()
. Dengando
while
kesalahan sintaks.else do; while (0)
?Untuk beberapa alasan saya tidak dapat mengomentari jawaban pertama ...
Beberapa dari Anda menunjukkan makro dengan variabel lokal, tetapi tidak ada yang menyebutkan bahwa Anda tidak bisa menggunakan nama apa pun di makro! Ini akan menggigit pengguna suatu hari! Mengapa? Karena argumen input diganti ke template makro Anda. Dan dalam contoh makro Anda, Anda telah menggunakan nama variabel i yang mungkin paling sering digunakan .
Misalnya saat makro berikut
digunakan dalam fungsi berikut
makro tidak akan menggunakan variabel i yang dimaksudkan, yang dideklarasikan di awal some_func, tetapi variabel lokal, yang dideklarasikan di do ... while di makro.
Jadi, jangan pernah menggunakan nama variabel umum dalam makro!
sumber
int __i;
.mylib_internal___i
atau serupa.Penjelasan
do {} while (0)
danif (1) {} else
memastikan bahwa makro diperluas menjadi hanya 1 instruksi. Jika tidak:akan berkembang ke:
Dan
g(X)
akan dieksekusi di luarif
pernyataan kontrol. Ini dihindari saat menggunakando {} while (0)
danif (1) {} else
.Alternatif yang lebih baik
Dengan ekspresi pernyataan GNU (bukan bagian dari standar C), Anda memiliki cara yang lebih baik daripada
do {} while (0)
danif (1) {} else
untuk menyelesaikannya, hanya dengan menggunakan({})
:Dan sintaks ini kompatibel dengan nilai pengembalian (perhatikan bahwa
do {} while (0)
tidak), seperti pada:sumber
Saya tidak berpikir itu disebutkan jadi pertimbangkan ini
akan diterjemahkan ke dalam
perhatikan bagaimana
i++
dievaluasi dua kali oleh makro. Ini dapat menyebabkan beberapa kesalahan menarik.sumber
do { int macroname_i = (i); f(macroname_i); g(macroname_i); } while (/* CONSTCOND */ 0)