C önişlemcisi ile iki kez birleştirme ve bir makroyu "arg ## _ ## MACRO" da olduğu gibi genişletme?


152

Bazı işlevlerin adları böyle bir makro ile belirli bir makro değişkeninin değerine bağlı olduğu bir program yazmaya çalışıyorum:

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

int NAME(some_function)(int a);

Ne yazık ki, makro NAME()bunu

int some_function_VARIABLE(int a);

ziyade

int some_function_3(int a);

bu yüzden bu konuda yanlış bir yol var. Neyse ki, VARIABLE için farklı olası değerlerin sayısı azdır, bu yüzden #if VARIABLE == ntüm vakaları ayrı ayrı listeleyebilirim, ancak bunu yapmak için akıllı bir yol olup olmadığını merak ediyordum.


3
Bunun yerine işlev işaretçileri kullanmak istemediğinizden emin misiniz?
György Andrasek

8
@Jurily - İşlev işaretçileri çalışma zamanında çalışır, önişlemci derleme zamanında (önce) çalışır. Her ikisi de aynı görev için kullanılabilse bile bir fark vardır.
Chris Lutz

1
Mesele şu ki, içinde kullanılan şey belli bir boyut için kablo bağlantılı hızlı bir hesaplama geometrisi kütüphanesidir. Bununla birlikte, bazen birisi birkaç farklı boyutla (örneğin, 2 ve 3) kullanmak isteyebilir ve bu nedenle boyuta bağlı işlev ve tür adlarıyla kod oluşturmak için kolay bir yol gerekir. Ayrıca, kod ANSI C'de yazılır, bu nedenle şablonlar ve uzmanlık içeren funky C ++ şeyler burada geçerli değildir.
JJ.

2
Bu soru özyinelemeli makro genişletme ve stackoverflow.com/questions/216875/using-in-macros ile ilgili olduğu için yeniden açmaya oy vermek genel bir "ne için iyi" dir. Bu sorunun başlığı daha kesin yapılmalıdır.
Ciro Santilli 法轮功 23 病 六四 事件 法轮功

Keşke bu örnek en aza indirilmiş olsaydı: aynı şey olur #define A 0 \n #define M a ## A: iki tane ##olmak anahtar değildir.
Ciro Santilli 法轮功 15 病 六四 事件 法轮功

Yanıtlar:


223

Standart C Önişlemci

$ 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);
$

İki dolaylı seviye

Başka bir cevaba yaptığı açıklamada, Cade Roux bunun neden iki dolaylı indirime ihtiyaç duyduğunu sordu . İnatçı cevap, standardın bu şekilde çalışmasını gerektirmesidir; dize oluşturma operatörü ile de eşdeğer numaraya ihtiyacınız olduğunu bulma eğilimindesiniz.

C99 standardının 6.10.3 Bölümü, 'makro değiştirme'yi ve 6.10.3.1,' bağımsız değişken değiştirme'yi kapsar.

İşlev benzeri bir makronun çağrılmasına yönelik argümanlar belirlendikten sonra, argüman ikamesi gerçekleşir. Bir #veya ##önişleme belirtecinden önce veya ardından bir ##ön işlem belirteci (aşağıya bakın) gelmedikçe, değiştirme listesindeki bir parametre, içerdiği tüm makrolar genişletildikten sonra karşılık gelen argümanla değiştirilir. İkame edilmeden önce, her argümanın önişleme belirteçleri, önişleme dosyasının geri kalanını oluşturuyormuş gibi tamamen makro değiştirilir; başka ön işleme jetonu mevcut değildir.

Çağrıda NAME(mine)argüman 'benim'; tamamen benimkine genişletildi; daha sonra yedek dizgiyle değiştirilir:

EVALUATOR(mine, VARIABLE)

Şimdi EVALUATOR makrosu keşfedildi ve argümanlar 'benim' ve 'DEĞİŞKEN' olarak izole edildi; ikincisi daha sonra tamamen '3'e genişletilir ve ikame dizesine ikame edilir:

PASTER(mine, 3)

Bunun çalışması diğer kurallar tarafından kapsanmaktadır (6.10.3.3 '## operatörü'):

İşlev benzeri bir makronun değiştirme listesinde, bir parametrenin hemen öncesinde veya ardından bir ##önişleme belirteci varsa, parametre karşılık gelen bağımsız değişkenin önişleme belirteci dizisiyle değiştirilir; [...]

Hem nesne benzeri hem de işlev benzeri makro çağrıları için, değiştirme listesinin yerini alacak daha fazla makro adı için yeniden incelenmeden önce, değiştirme listesindeki her bir ##ön işleme belirtecinin her örneği ( bağımsız değişkenden değil) silinir ve önceki ön işleme belirteci birleştirilir aşağıdaki önişlem belirteci ile.

Yani, yedek liste içerir xizledi ##ve ayrıca ##takip y; Böylece sahibiz:

mine ## _ ## 3

ve ##jetonları ortadan kaldırmak ve jetonları her iki tarafta birleştirmek, 'mayın' ile '_' ve '3'ü birleştirir:

mine_3

İstenen sonuç budur.


Orijinal soruya bakarsak, kod ('some_function' yerine 'mine' kullanacak şekilde uyarlandı):

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

NAME(mine)

NAME'in argümanı açıkça 'benim' ve tamamen genişletildi.
6.10.3.3 kurallarına göre şunları buluyoruz:

mine ## _ ## VARIABLE

hangi zaman ##operatörler elenir, eşleştiren:

mine_VARIABLE

tam olarak soruda bildirildiği gibi.


Geleneksel C Önişlemcisi

Robert Rüger soruyor :

Belirteç yapıştırma operatörü olmayan geleneksel C ön işlemcisi ile bunu yapmanın bir yolu var mı ##?

Belki ve belki de değil - bu önişlemciye bağlıdır. Standart ön işlemcinin avantajlarından biri, güvenilir şekilde çalışan bu tesise sahipken standart öncesi ön işlemciler için farklı uygulamalar olmasıdır. Bir gereklilik, önişlemci bir yorumun yerini aldığında ANSI önişlemcisinin yapması gerektiği için boşluk oluşturmamasıdır. GCC (6.3.0) C Önişlemcisi bu gereksinimi karşılar; XCode 8.2.1'den Clang ön işlemcisi bunu yapmaz.

Çalıştığında, iş ( 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);

fun,Ve VARIABLE- arasında bir boşluk olmadığını unutmayın, çünkü bu önemliyse, çıktıya kopyalanır ve mine_ 3tabiki sözdizimsel olarak geçerli olmayan bir adla sonuçlanırsınız. (Şimdi lütfen saçımı geri alabilir miyim?)

GCC 6.3.0 (çalışıyor cpp -traditional x-paste.c) ile şunu elde ederim:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_3(char *x);

XCode 8.2.1'den Clang ile şunu elde ederim:

# 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);

Bu alanlar her şeyi mahvediyor. Her iki önişlemcinin de doğru olduğunu; farklı standart öncesi ön işlemciler, her iki davranışı da sergiledi; ##Gösterimle birlikte standart bunu kökten basitleştirir.

Bunu yapmanın başka yolları olabilir. Ancak, bu çalışmaz:

#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 şunu üretir:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_VARIABLE(char *x);

Kapat, ama zar yok. YMMV, elbette, kullandığınız standart öncesi ön işlemciye bağlı olarak. Açıkçası, işbirliği yapmayan bir önişlemci ile sıkışırsanız, standart öncesi olanın yerine standart C önişlemcisini kullanmak daha kolay olacaktır (derleyiciyi uygun şekilde yapılandırmanın bir yolu vardır) işi yapmanın bir yolunu bulmak için çok zaman harcayın.


1
Evet, bu sorunu çözdü. İki seviyeli özyineleme ile hileyi biliyordum - en az bir kez dizgi ile oynamak zorunda kaldım - ama bunu nasıl yapacağımı bilmiyordum.
JJ.

Jeton yapıştırma operatörü ## içermeyen geleneksel C ön işlemcisi ile bunun herhangi bir yolu var mı ?
Robert Rüger

1
@ RobertRüger: cevabın uzunluğunu iki katına çıkardı, ancak kapsayacak bilgiler ekledim cpp -traditional. Kesin bir cevap olmadığını unutmayın - bu, ön işlemciye bağlıdır.
Jonathan Leffler

Cevabınız için çok teşekkür ederim. Bu tamamen harika! Bu arada biraz farklı bir çözüm daha buldum. Buraya bakın . Ayrıca clang ile çalışmadığı sorunu da var. Neyse ki bu benim uygulama için bir sorun değil ...
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);

Dürüst olmak gerekirse, bunun neden işe yaradığını bilmek istemezsiniz. Neden işe yaradığını biliyorsanız , işte bu tür şeyleri bilen o adam olacaksınız ve herkes size sorular soracak. =)

Düzenleme: Eğer gerçekten neden işe yaradığını bilmek istiyorsanız, benimle kimsenin beni dövmediğini varsayarak mutlu bir açıklama gönderirim.


Neden iki dolaylı indirime ihtiyaç duyduğunu açıklar mısınız? Bir yönlendirme düzeyi ile bir cevabım vardı, ancak Visual Studio'uma C ++ yüklemem gerektiğinden cevabı sildim ve işe yaramadı.
Cade Roux
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.