Preprosesor C Standar
$ cat xx.c
#define VARIABLE 3
#define PASTER(x,y) x ## _ ## y
#define EVALUATOR(x,y) PASTER(x,y)
#define NAME(fun) EVALUATOR(fun, VARIABLE)
extern void NAME(mine)(char *x);
$ gcc -E xx.c
# 1 "xx.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "xx.c"
extern void mine_3(char *x);
$
Dua tingkat tipuan
Dalam komentar untuk jawaban lain, Cade Roux bertanya mengapa ini membutuhkan dua tingkat tipuan. Jawaban kurang ajar adalah karena begitulah standar mengharuskannya bekerja; Anda cenderung menemukan bahwa Anda memerlukan trik yang setara dengan operator merangkai juga.
Bagian 6.10.3 dari standar C99 mencakup 'penggantian makro', dan 6.10.3.1 mencakup 'substitusi argumen'.
Setelah argumen untuk doa makro fungsi-seperti telah diidentifikasi, substitusi argumen terjadi. Sebuah parameter dalam daftar pengganti, kecuali didahului oleh #
atau ##
preprocessing tanda atau diikuti dengan ##
preprocessing tanda (lihat di bawah), digantikan oleh yang sesuai argumen setelah semua macro yang terkandung di dalamnya telah diperluas. Sebelum diganti, token preprocessing setiap argumen sepenuhnya makro diganti seolah-olah mereka membentuk sisa file preprocessing; tidak ada token preprocessing lain yang tersedia.
Dalam doa NAME(mine)
, argumennya adalah 'milikku'; itu sepenuhnya diperluas menjadi 'milikku'; itu kemudian diganti ke dalam string pengganti:
EVALUATOR(mine, VARIABLE)
Sekarang EVALUATOR makro ditemukan, dan argumen diisolasi sebagai 'milikku' dan 'VARIABEL'; yang terakhir ini kemudian sepenuhnya diperluas ke '3', dan diganti menjadi string pengganti:
PASTER(mine, 3)
Operasi ini dicakup oleh aturan lain (6.10.3.3 'Operator ##'):
Jika, dalam daftar penggantian makro seperti fungsi, parameter segera didahului atau diikuti oleh ##
token preprocessing, parameter digantikan oleh urutan token preprocessing argumen terkait; [...]
Untuk invokasi makro seperti objek dan fungsi, sebelum daftar penggantian diperiksa ulang untuk mengganti lebih banyak nama makro, setiap instance dari ##
token preprocessing dalam daftar pengganti (tidak dari argumen) dihapus dan token preprocessing sebelumnya digabungkan. dengan token preprocessing berikut.
Jadi, daftar pengganti berisi x
diikuti oleh ##
dan juga ##
diikuti oleh y
; jadi kita punya:
mine ## _ ## 3
dan menghilangkan ##
token dan menggabungkan token di kedua sisi menggabungkan 'milikku' dengan '_' dan '3' untuk menghasilkan:
mine_3
Ini adalah hasil yang diinginkan.
Jika kita melihat pertanyaan aslinya, kode itu (disesuaikan untuk menggunakan 'milikku' alih-alih 'fungsi_beberapa'):
#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE
NAME(mine)
Argumen untuk NAME jelas 'milikku' dan itu diperluas sepenuhnya.
Mengikuti aturan 6.10.3.3, kami menemukan:
mine ## _ ## VARIABLE
yang, ketika ##
operator dieliminasi, memetakan ke:
mine_VARIABLE
persis seperti yang dilaporkan dalam pertanyaan.
Preprosesor C Tradisional
Robert Rüger bertanya :
Apakah ada cara untuk melakukan ini dengan preprocessor C tradisional yang tidak memiliki operator tempel token ##
?
Mungkin, dan mungkin tidak - itu tergantung pada preprosesor. Salah satu kelebihan dari preprocessor standar adalah memiliki fasilitas ini yang bekerja dengan andal, sedangkan terdapat implementasi yang berbeda untuk preprosesor pra-standar. Salah satu persyaratan adalah bahwa ketika preprocessor mengganti komentar, ia tidak menghasilkan spasi seperti yang harus dilakukan preprocessor ANSI. GCC (6.3.0) C Preprocessor memenuhi persyaratan ini; preprosesor Dentang dari XCode 8.2.1 tidak.
Ketika berhasil, ini berhasil ( x-paste.c
):
#define VARIABLE 3
#define PASTE2(x,y) x/**/y
#define EVALUATOR(x,y) PASTE2(PASTE2(x,_),y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)
extern void NAME(mine)(char *x);
Perhatikan bahwa tidak ada spasi di antara fun,
dan VARIABLE
- itu penting karena jika ada, itu disalin ke output, dan Anda berakhir dengan mine_ 3
namanya, yang tentu saja tidak valid secara sintaksis. (Sekarang, bisakah saya mengembalikan rambut saya?)
Dengan GCC 6.3.0 (berjalan cpp -traditional x-paste.c
), saya mendapatkan:
# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"
extern void mine_3(char *x);
Dengan Dentang dari XCode 8.2.1, saya mendapatkan:
# 1 "x-paste.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 329 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "x-paste.c" 2
extern void mine _ 3(char *x);
Ruang-ruang itu merusak segalanya. Saya perhatikan bahwa kedua preprosesor sudah benar; preprosesor pra-standar yang berbeda memamerkan kedua perilaku tersebut, yang membuat token menempelkan proses yang sangat menjengkelkan dan tidak dapat diandalkan ketika mencoba memasukkan kode. Standar dengan ##
notasi secara radikal menyederhanakan itu.
Mungkin ada cara lain untuk melakukan ini. Namun, ini tidak berhasil:
#define VARIABLE 3
#define PASTER(x,y) x/**/_/**/y
#define EVALUATOR(x,y) PASTER(x,y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)
extern void NAME(mine)(char *x);
GCC menghasilkan:
# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"
extern void mine_VARIABLE(char *x);
Tutup, tapi tidak ada dadu. YMMV, tentu saja, tergantung pada preprosesor pra-standar yang Anda gunakan. Terus terang, jika Anda terjebak dengan preprocessor yang tidak bekerja sama, mungkin akan lebih mudah untuk mengatur untuk menggunakan preprocessor C standar di tempat yang pra-standar (biasanya ada cara untuk mengkonfigurasi kompilator dengan tepat) daripada menghabiskan banyak waktu mencoba mencari cara untuk melakukan pekerjaan itu.