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, makropaste
menggabungkan dua argumennya:
#define paste(front, back) front ## back
jadi
paste(name, 1)
buat tokennyaname1
.
Bagaimana dan mengapa seseorang menggunakan ini di dunia nyata? Apa contoh praktis penggunaannya, dan adakah hal-hal yang perlu dipertimbangkan?
std::wstring BuildDate = WIDEN(__DATE__) L" " WIDEN(__TIME__);
dan secara implisit membangun seluruh string sekaligus.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:
#include <stdio.h> #define STRINGIFY2( x) #x #define STRINGIFY(x) STRINGIFY2(x) #define PASTE2( a, b) a##b #define PASTE( a, b) PASTE2( a, b) #define BAD_PASTE(x,y) x##y #define BAD_STRINGIFY(x) #x #define SOME_MACRO function_name int main() { printf( "buggy results:\n"); printf( "%s\n", STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__))); printf( "%s\n", BAD_STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__))); printf( "%s\n", BAD_STRINGIFY( PASTE( SOME_MACRO, __LINE__))); printf( "\n" "desired result:\n"); printf( "%s\n", STRINGIFY( PASTE( SOME_MACRO, __LINE__))); }
Hasil:
buggy results: SOME_MACRO__LINE__ BAD_PASTE( SOME_MACRO, __LINE__) PASTE( SOME_MACRO, __LINE__) desired result: function_name21
sumber
__LINE__
ini adalah nama makro khusus yang diganti oleh preprocessor dengan nomor baris saat ini dari file sumber.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:
#define STRINGIFY(x) #x #define PLUS(a, b) STRINGIFY(a##+##b) #define NS(a, b) STRINGIFY(a##::##b) printf("%s %s\n", PLUS(1,2), NS(std,vector));
Pada beberapa kompiler, ini akan menampilkan hasil yang diharapkan:
1+2 std::vector
Di kompiler lain, ini akan menyertakan spasi kosong yang tidak diinginkan:
1 + 2 std :: vector
Versi GCC yang cukup modern (> = 3.3 atau lebih) akan gagal untuk mengkompilasi kode ini:
foo.cpp:16:1: pasting "1" and "+" does not give a valid preprocessing token foo.cpp:16:1: pasting "+" and "2" does not give a valid preprocessing token foo.cpp:16:1: pasting "std" and "::" does not give a valid preprocessing token foo.cpp:16:1: pasting "::" and "vector" does not give a valid preprocessing token
Solusinya adalah dengan menghilangkan operator penempelan token saat menggabungkan token preprocessor ke operator C / C ++:
#define STRINGIFY(x) #x #define PLUS(a, b) STRINGIFY(a+b) #define NS(a, b) STRINGIFY(a::b) printf("%s %s\n", PLUS(1,2), NS(std,vector));
The GCC CPP dokumentasi bab tentang Rangkaian memiliki informasi yang lebih berguna pada operator token-paste.
sumber
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:#define LOAD_IMGLIB_FN(lib,func) { \ fn_##func = (void *) GetProcAddress (lib, #func); \ if (!fn_##func) return 0; \ }
Kami kemudian dapat menggunakannya:
Manfaatnya adalah tidak harus menulis keduanya
fn_XpmFreeAttributes
dan"XpmFreeAttributes"
(dan berisiko salah mengeja salah satunya).sumber
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);
const char *ColorStringTable[] = { "RED", "GREEN", "BLUE" };
Nama tabel string berasal dari menempelkan parameter makro (yaitu Warna) ke StringTable menggunakan operator ##. Aplikasi (trik?) Seperti ini adalah tempat operator # dan ## sangat berharga.
sumber
Anda dapat menggunakan penempelan token saat Anda perlu menggabungkan parameter makro dengan yang lain.
Ini dapat digunakan untuk template:
#define LINKED_LIST(A) struct list##_##A {\ A value; \ struct list##_##A *next; \ };
Dalam hal ini LINKED_LIST (int) akan memberi Anda
struct list_int { int value; struct list_int *next; };
Anda juga dapat menulis template fungsi untuk list traversal.
sumber
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:
STATUS activeCall_constructor( HANDLE *pInst ) STATUS activeCall_eventHandler( HANDLE *pInst, TOKEN *pEvent ); STATUS activeCall_destructor( HANDLE *pInst );
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".
sumber
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
Saya menggunakannya untuk home roll assert pada compiler C non-standar untuk embedded:
#define ASSERT(exp) if(!(exp)){ \ print_to_rs232("Assert failed: " ## #exp );\ while(1){} //Let the watchdog kill us
sumber
##
?Saya menggunakannya untuk menambahkan prefiks khusus ke variabel yang ditentukan oleh makro. Jadi sesuatu seperti:
berkembang menjadi:
void __testframework_test_name ()
sumber
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:
#define LIFECYCLE(name, func) (struct name x = name##_create(); name##_activate(x); func(x); name##_release())
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.
sumber
Salah satu kegunaan penting di WinCE:
#define BITFMASK(bit_position) (((1U << (bit_position ## _WIDTH)) - 1) << (bit_position ## _LEFTSHIFT))
Saat mendefinisikan deskripsi bit register, kami melakukan berikut ini:
#define ADDR_LEFTSHIFT 0 #define ADDR_WIDTH 7
Dan saat menggunakan BITFMASK, cukup gunakan:
sumber
Ini sangat berguna untuk logging. Anda dapat melakukan:
#define LOG(msg) log_msg(__function__, ## msg)
Atau, jika kompiler Anda tidak mendukung fungsi dan fungsi :
#define LOG(msg) log_msg(__file__, __line__, ## msg)
"Fungsi" di atas mencatat pesan dan menunjukkan dengan tepat fungsi mana yang mencatat pesan.
Sintaks C ++ saya mungkin kurang tepat.
sumber