Apa aplikasi dari ## operator preprocessor dan gotcha yang perlu dipertimbangkan?

88

Seperti yang disebutkan dalam banyak pertanyaan saya sebelumnya, saya sedang mengerjakan K&R, dan saat ini sedang dalam preprocessor. Salah satu hal yang lebih menarik - sesuatu yang tidak pernah saya ketahui sebelumnya dari upaya saya sebelumnya untuk mempelajari C - adalah ##operator preprocessor. Menurut K&R:

Operator preprocessor ## menyediakan cara untuk menggabungkan argumen aktual selama ekspansi makro. Jika parameter dalam teks pengganti bersebelahan dengan a ##, parameter tersebut diganti dengan argumen aktual, ##spasi putih di sekitarnya akan dihapus, dan hasilnya dipindai ulang. Misalnya, makro paste menggabungkan dua argumennya:

#define paste(front, back) front ## back

jadi paste(name, 1)buat tokennya name1.

Bagaimana dan mengapa seseorang menggunakan ini di dunia nyata? Apa contoh praktis penggunaannya, dan adakah hal-hal yang perlu dipertimbangkan?

John Rudy
sumber

Jawaban:

47

CrashRpt: Menggunakan ## untuk mengonversi string multi-byte makro ke Unicode

Penggunaan yang menarik di CrashRpt (perpustakaan pelaporan kerusakan) adalah sebagai berikut:

Di sini mereka ingin menggunakan string dua byte alih-alih string satu byte per karakter. Ini mungkin terlihat seperti tidak ada gunanya, tetapi mereka melakukannya untuk alasan yang bagus.

Mereka menggunakannya dengan makro lain yang mengembalikan string dengan tanggal dan waktu.

Menempatkan di Lsebelah a __ DATE __akan memberi Anda kesalahan kompilasi.


Windows: Menggunakan ## untuk Unicode generik atau string multi-byte

Windows menggunakan sesuatu seperti berikut:

Dan _Tdigunakan di mana pun dalam kode


Berbagai pustaka, yang digunakan untuk nama pengakses dan pengubah bersih:

Saya juga melihatnya digunakan dalam kode untuk menentukan pengakses dan pengubah:

Anda juga dapat menggunakan metode yang sama ini untuk jenis pembuatan nama pintar lainnya.


Berbagai pustaka, menggunakannya untuk membuat beberapa deklarasi variabel sekaligus:

Brian R. Bondy
sumber
3
Karena Anda bisa menggabungkan string literal pada waktu kompilasi, Anda bisa mengurangi ekspresi BuildDate menjadi std::wstring BuildDate = WIDEN(__DATE__) L" " WIDEN(__TIME__); dan secara implisit membangun seluruh string sekaligus.
pengguna666412
49

Satu hal yang harus diperhatikan saat Anda menggunakan token-paste (' ##') atau #operator preprocessing stringizing (' ') adalah Anda harus menggunakan tingkat tipuan ekstra agar mereka berfungsi dengan baik di semua kasus.

Jika Anda tidak melakukan ini dan item yang diteruskan ke operator penempelan token adalah makro itu sendiri, Anda akan mendapatkan hasil yang mungkin bukan yang Anda inginkan:

Hasil:

Michael Burr
sumber
1
Untuk penjelasan tentang perilaku preprocessor ini, lihat stackoverflow.com/questions/8231966/…
Adam Davis
@MichaelBurr saya membaca jawaban Anda & saya ragu. Kenapa LINE ini mencetak nomor baris?
BANTUAN PLZ
3
@AbhimanyuAryan: Saya tidak yakin apakah ini yang Anda tanyakan, tetapi __LINE__ini adalah nama makro khusus yang diganti oleh preprocessor dengan nomor baris saat ini dari file sumber.
Michael Burr
Akan keren jika spesifikasi bahasa dapat dikutip / ditautkan, seperti di sini
Antonio
14

Inilah gotcha yang saya temui saat meningkatkan ke versi baru kompiler:

Penggunaan yang tidak perlu dari operator penempelan token ( ##) adalah non-portabel dan dapat menghasilkan spasi kosong, peringatan, atau kesalahan yang tidak diinginkan.

Jika hasil dari operator penempelan token bukanlah token praprosesor yang valid, operator penempelan token tidak diperlukan dan mungkin berbahaya.

Misalnya, seseorang mungkin mencoba membuat literal string pada waktu kompilasi menggunakan operator penempelan token:

Pada beberapa kompiler, ini akan menampilkan hasil yang diharapkan:

Di kompiler lain, ini akan menyertakan spasi kosong yang tidak diinginkan:

Versi GCC yang cukup modern (> = 3.3 atau lebih) akan gagal untuk mengkompilasi kode ini:

Solusinya adalah dengan menghilangkan operator penempelan token saat menggabungkan token preprocessor ke operator C / C ++:

The GCC CPP dokumentasi bab tentang Rangkaian memiliki informasi yang lebih berguna pada operator token-paste.

bk1e
sumber
Terima kasih - Saya tidak menyadarinya (tapi kemudian saya tidak menggunakan operator praproses ini terlalu banyak ...).
Michael Burr
3
Ini disebut operator "penempelan token" karena suatu alasan - tujuannya adalah untuk berakhir dengan satu token saat Anda selesai. Tulisan yang bagus.
Markus Tebusan
Jika hasil dari operator penempelan token bukan merupakan token praprosesor yang valid, perilakunya tidak ditentukan.
alecov
Perubahan bahasa seperti float heksadesimal, atau (dalam C ++) pemisah digit dan literal yang ditentukan pengguna, terus mengubah apa yang disebut sebagai "token praproses yang valid", jadi jangan pernah menyalahgunakannya seperti itu! Jika Anda harus memisahkan token (bahasa yang sesuai), harap mengejanya sebagai dua token terpisah, dan jangan mengandalkan interaksi yang tidak disengaja antara tata bahasa preprocessor dan bahasa yang sesuai.
Kerrek SB
6

Ini berguna dalam semua jenis situasi agar tidak mengulangi diri Anda tanpa perlu. Berikut ini adalah contoh dari kode sumber Emacs. Kami ingin memuat sejumlah fungsi dari perpustakaan. Fungsi "foo" harus ditetapkan fn_foo, dan seterusnya. Kami mendefinisikan makro berikut:

Kami kemudian dapat menggunakannya:

Manfaatnya adalah tidak harus menulis keduanya fn_XpmFreeAttributesdan "XpmFreeAttributes"(dan berisiko salah mengeja salah satunya).

Vebjorn Ljosa
sumber
4

Pertanyaan sebelumnya tentang Stack Overflow meminta metode yang mulus untuk menghasilkan representasi string untuk konstanta enumerasi tanpa banyak pengetikan ulang yang rawan kesalahan.

Tautan

Jawaban saya atas pertanyaan itu menunjukkan bagaimana menerapkan sihir preprocessor kecil memungkinkan Anda menentukan enumerasi Anda seperti ini (misalnya) ...;

... Dengan keuntungan bahwa perluasan makro tidak hanya mendefinisikan pencacahan (dalam file .h), itu juga mendefinisikan larik string yang cocok (dalam file .c);

Nama tabel string berasal dari menempelkan parameter makro (yaitu Warna) ke StringTable menggunakan operator ##. Aplikasi (trik?) Seperti ini adalah tempat operator # dan ## sangat berharga.

Bill Forster
sumber
3

Anda dapat menggunakan penempelan token saat Anda perlu menggabungkan parameter makro dengan yang lain.

Ini dapat digunakan untuk template:

Dalam hal ini LINKED_LIST (int) akan memberi Anda

Anda juga dapat menulis template fungsi untuk list traversal.

qrdl
sumber
2

Saya menggunakannya dalam program C untuk membantu menegakkan prototipe dengan benar untuk serangkaian metode yang harus sesuai dengan semacam konvensi pemanggilan. Di satu sisi, ini bisa digunakan untuk orientasi objek orang miskin di C lurus:

berkembang menjadi seperti ini:

Ini memberlakukan parameterisasi yang benar untuk semua objek "turunan" ketika Anda melakukan:

di atas dalam file header Anda, dll. Ini juga berguna untuk pemeliharaan jika Anda bahkan kebetulan ingin mengubah definisi dan / atau menambahkan metode ke "objek".

Jeff yang tinggi
sumber
2

SGlib menggunakan ## untuk memalsukan template di C. Karena tidak ada fungsi yang berlebihan, ## digunakan untuk merekatkan nama tipe ke dalam nama fungsi yang dihasilkan. Jika saya memiliki jenis daftar yang disebut list_t, maka saya akan mendapatkan fungsi bernama seperti sglib_list_t_concat, dan seterusnya.


sumber
2

Saya menggunakannya untuk home roll assert pada compiler C non-standar untuk embedded:

c0m4
sumber
3
Saya menganggap yang Anda maksud dengan 'non-standar' bahwa kompiler tidak melakukan penempelan string tetapi melakukan penempelan token - atau apakah itu akan berhasil bahkan tanpa ##?
PJTraill
1

Saya menggunakannya untuk menambahkan prefiks khusus ke variabel yang ditentukan oleh makro. Jadi sesuatu seperti:

berkembang menjadi:

John Millikin
sumber
1

Kegunaan utamanya adalah saat Anda memiliki konvensi penamaan dan ingin makro Anda memanfaatkan konvensi penamaan tersebut. Mungkin Anda memiliki beberapa keluarga metode: image_create (), image_activate (), dan image_release () juga file_create (), file_activate (), file_release (), dan mobile_create (), mobile_activate () dan mobile_release ().

Anda bisa menulis makro untuk menangani siklus hidup objek:

Tentu saja, semacam "versi minimal objek" bukan satu-satunya jenis konvensi penamaan yang berlaku untuk ini - hampir sebagian besar konvensi penamaan menggunakan sub-string umum untuk membentuk nama. Itu bisa saya nama fungsi (seperti di atas), atau nama bidang, nama variabel, atau apa pun.

mcherm.dll
sumber
1

Salah satu kegunaan penting di WinCE:

Saat mendefinisikan deskripsi bit register, kami melakukan berikut ini:

Dan saat menggunakan BITFMASK, cukup gunakan:

Keshava GN
sumber
0

Ini sangat berguna untuk logging. Anda dapat melakukan:

Atau, jika kompiler Anda tidak mendukung fungsi dan fungsi :

"Fungsi" di atas mencatat pesan dan menunjukkan dengan tepat fungsi mana yang mencatat pesan.

Sintaks C ++ saya mungkin kurang tepat.

ya23
sumber
1
Apa yang Anda coba lakukan dengan itu? Ini akan bekerja dengan baik tanpa "##", karena tidak perlu menempelkan token "," ke "msg". Apakah Anda mencoba merangkai pesan? Selain itu, FILE dan LINE harus dalam huruf besar, bukan huruf kecil.
bk1e
Kamu memang benar. Saya perlu menemukan skrip asli untuk melihat bagaimana ## digunakan. Malu padaku, tidak ada kue hari ini!
ya23