Bagaimana cara menggabungkan dua kali dengan prepro C dan memperluas makro seperti pada “arg ## _ ## MACRO”?

152

Saya mencoba untuk menulis sebuah program di mana nama-nama beberapa fungsi tergantung pada nilai variabel makro tertentu dengan makro seperti ini:

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

int NAME(some_function)(int a);

Sayangnya, makro NAME()mengubahnya menjadi

int some_function_VARIABLE(int a);

daripada

int some_function_3(int a);

jadi ini jelas cara yang salah untuk melakukannya. Untungnya, jumlah nilai yang mungkin berbeda untuk VARIABEL kecil sehingga saya bisa melakukan #if VARIABLE == ndan membuat daftar semua kasus secara terpisah, tetapi saya bertanya-tanya apakah ada cara cerdas untuk melakukannya.

JJ.
sumber
3
Anda yakin tidak ingin menggunakan pointer fungsi saja?
György Andrasek
8
@Jily - Pointer fungsi berfungsi saat runtime, preprocessor bekerja pada waktu kompilasi (sebelum). Ada perbedaan, bahkan jika keduanya dapat digunakan untuk tugas yang sama.
Chris Lutz
1
Intinya adalah apa yang digunakan di dalamnya adalah pustaka geometri komputasi cepat .. yang didesain untuk dimensi tertentu. Namun, kadang-kadang seseorang ingin dapat menggunakannya dengan beberapa dimensi yang berbeda (katakanlah, 2 dan 3) sehingga seseorang akan membutuhkan cara mudah untuk menghasilkan kode dengan fungsi yang bergantung pada dimensi dan mengetikkan nama. Juga, kode ini ditulis dalam ANSI C sehingga hal-hal funky C ++ dengan template dan spesialisasi tidak berlaku di sini.
JJ.
2
Memilih untuk membuka kembali karena pertanyaan ini khusus tentang ekspansi makro rekursif dan stackoverflow.com/questions/216875/using-in-macros adalah generik "apa gunanya". Judul pertanyaan ini harus dibuat lebih tepat.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Saya berharap contoh ini diminimalkan: hal yang sama terjadi pada #define A 0 \n #define M a ## A: memiliki dua ##bukanlah kuncinya.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Jawaban:

223

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 xdiikuti 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_ 3namanya, 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.

Jonathan Leffler
sumber
1
Yap, ini menyelesaikan masalah. Saya tahu trik dengan dua tingkat rekursi - saya harus bermain dengan pengetatan setidaknya sekali - tetapi tidak tahu bagaimana melakukan ini.
JJ.
Apakah ada cara untuk melakukan ini dengan preprocessor C tradisional yang tidak memiliki operator tempel token ##?
Robert Rüger
1
@Robertger: itu menggandakan panjang jawaban, tetapi saya telah menambahkan informasi untuk menutupi cpp -traditional. Perhatikan bahwa tidak ada jawaban pasti - itu tergantung pada preprosesor yang Anda dapatkan.
Jonathan Leffler
Terima kasih banyak atas jawabannya. Ini benar-benar hebat! Sementara itu saya juga menemukan solusi lain yang sedikit berbeda. Lihat di sini . Ini juga memiliki masalah yang tidak bekerja dengan dentang sekalipun. Untungnya itu bukan masalah bagi aplikasi saya ...
Robert Rüger
32
#define VARIABLE 3
#define NAME2(fun,suffix) fun ## _ ## suffix
#define NAME1(fun,suffix) NAME2(fun,suffix)
#define NAME(fun) NAME1(fun,VARIABLE)

int NAME(some_function)(int a);

Jujur, Anda tidak ingin tahu mengapa ini berhasil. Jika Anda tahu mengapa itu berhasil, Anda akan menjadi pria di tempat kerja yang mengetahui hal semacam ini, dan semua orang akan datang menanyakan pertanyaan kepada Anda. =)

Sunting: jika Anda benar-benar ingin tahu mengapa itu bekerja, saya akan dengan senang hati mengirim penjelasan, dengan asumsi tidak ada yang mengalahkan saya untuk itu.

Stephen Canon
sumber
Bisakah Anda menjelaskan mengapa perlu dua tingkat tipuan. Saya punya jawaban dengan satu tingkat pengalihan tetapi saya menghapus jawabannya karena saya harus menginstal C ++ ke dalam Visual Studio saya dan kemudian tidak akan berhasil.
Cade Roux