it-swarm-tr.com

Makrolarda neden açıkça anlamsız do-while ve if-else ifadeleri kullanılır?

Birçok C/C++ makrosunda, anlamsız bir do while döngüsüne benzeyen şeye sarılı makro kodunu görüyorum. İşte örnekler.

#define FOO(X) do { f(X); g(X); } while (0)
#define FOO(X) if (1) { f(X); g(X); } else

do while'nin ne yaptığını göremiyorum. Neden bunu sadece onsuz yazmıyorsunuz?

#define FOO(X) f(X); g(X)
693
jfm3

do ... while ve if ... else bunu yapmak için vardır, böylece makronuzdan sonraki bir.. Noktalı virgül her zaman aynı anlama gelir. Diyelim ki İkinci makronuzda olduğu gibi.

#define BAR(X) f(x); g(x)

Şimdi, eğer BAR(X); öğesini if ... else ifadesinde kullanacak olsaydınız, if ifadesinin gövdeleri küme parantezi içine alınmazsa, kötü bir sürprizle karşılaşırsınız.

if (corge)
  BAR(corge);
else
  gralt();

Yukarıdaki kod genişler

if (corge)
  f(corge); g(corge);
else
  gralt();

başka bir şey artık if ile ilişkilendirilmediğinden, bu sözdizimsel olarak yanlış. Makro içindeki küme parantezlerine bir şey sarılmaya yardımcı olmaz, çünkü diş tellerinden sonra bir noktalı virgül sözdizimsel olarak yanlıştır.

if (corge)
  {f(corge); g(corge);};
else
  gralt();

Sorunu çözmenin iki yolu vardır. Birincisi, makro içindeki ifadeleri bir ifade gibi davranma kabiliyetini soymadan sıralamak için virgül kullanmaktır.

#define BAR(X) f(X), g(X)

BAR çubuğunun yukarıdaki sürümü, yukarıdaki kodu, sözdizimsel olarak doğru olan, aşağıdakilere genişletir.

if (corge)
  f(corge), g(corge);
else
  gralt();

Örneğin, f(X) yerine kendi bloğuna girmesi gereken daha karmaşık bir kod gövdesine sahipseniz, örneğin yerel değişkenleri bildirmek için bu işe yaramaz. En genel durumda, çözüm, makronun karışıklığı olmayan bir noktalı virgül alan tek bir ifade olmasına neden olmak için do ... while gibi bir şey kullanmaktır.

#define BAR(X) do { \
  int i = f(X); \
  if (i > 4) g(i); \
} while (0)

do ... while kullanmak zorunda değilsiniz, if ... else ile bir şeyler de pişirebilirsiniz, ancak if ... else bir if ... else içinde genişlediğinde, sarkan başka bir sorunu daha da zorlaştıracak bir " sarkan başka bir " yol açar Aşağıdaki kodda olduğu gibi bulmak için.

if (corge)
  if (1) { f(corge); g(corge); } else;
else
  gralt();

Önemli olan, noktalı virgülün, sarkan noktalı virgülün hatalı olduğu bağlamlarda kullanılmasıdır. Tabii ki, bu noktada (ve muhtemelen de) bu noktada BAR işlevini makro olarak değil, gerçek bir işlev olarak ilan etmenin daha iyi olacağı tartışılabilir.

Özetle, do ... while, C ön işlemcisinin eksikliklerini gidermek için oradadır. Bu C tarzı kılavuzlar size C işlemcisini bırakmanızı söylerken, bu endişe ettikleri türden bir şey.

752
jfm3

Makrolar, ön işlemcinin orijinal koda koyacağı metin kopyalanır/yapıştırılır; Makro'nun yazarı, değiştirmenin geçerli bir kod üretmesini umuyor.

Bunu başarmak için üç iyi "ipucu" var:

Makronun orijinal kod gibi davranmasına yardımcı olun

Normal kod genellikle yarı-kolonla sona erer. Kullanıcı koduna gerek duymazsa ...

doSomething(1) ;
DO_SOMETHING_ELSE(2)  // <== Hey? What's this?
doSomethingElseAgain(3) ;

Bu, kullanıcının noktalı virgül yoksa derleyiciden bir hata üretmesini beklediği anlamına gelir.

Ancak asıl gerçek sebep, bir zamanlar makro yazarının makroyu orijinal bir işlevle (belki de satır içi) değiştirmesi gerekmesidir. Bu yüzden makro gerçekten gibi davranmalıdır.

Bu yüzden yarı-kolon gerektiren bir makroya sahip olmalıyız.

Geçerli bir kod üret

Jfm3'ün cevabında gösterildiği gibi, bazen makro birden fazla komut içerir. Makro bir if ifadesinde kullanılıyorsa, bu sorunlu olacaktır:

if(bIsOk)
   MY_MACRO(42) ;

Bu makro şu şekilde genişletilebilir:

#define MY_MACRO(x) f(x) ; g(x)

if(bIsOk)
   f(42) ; g(42) ; // was MY_MACRO(42) ;

g işlevi, bIsOk öğesinin değerine bakılmaksızın çalıştırılır.

Bu, makroya bir kapsam eklemek zorunda olduğumuz anlamına gelir:

#define MY_MACRO(x) { f(x) ; g(x) ; }

if(bIsOk)
   { f(42) ; g(42) ; } ; // was MY_MACRO(42) ;

Geçerli bir kod üret 2

Makro gibi bir şey ise:

#define MY_MACRO(x) int i = x + 1 ; f(i) ;

Aşağıdaki kodda başka bir problemimiz olabilir:

void doSomething()
{
    int i = 25 ;
    MY_MACRO(32) ;
}

Çünkü şu şekilde genişleyecekti:

void doSomething()
{
    int i = 25 ;
    int i = 32 + 1 ; f(i) ; ; // was MY_MACRO(32) ;
}

Bu kod elbette derlenmeyecek. Yani, yine, çözüm bir kapsam kullanıyor:

#define MY_MACRO(x) { int i = x + 1 ; f(i) ; }

void doSomething()
{
    int i = 25 ;
    { int i = 32 + 1 ; f(i) ; } ; // was MY_MACRO(32) ;
}

Kod tekrar doğru şekilde davranıyor.

Yarı-kolon + kapsam efektlerini birleştirmek?

Bu etkiyi oluşturan bir C/C++ deyimi vardır: do/while döngüsü:

do
{
    // code
}
while(false) ;

Do/while, bir makro oluşturabilir, böylece makro kodunu içine alır ve sonunda bir yarı-kolon gerektirir, böylece bir kod gerektiren bir kod haline gelir.

Bonus?

C++ derleyicisi, post-koşulu yanlış olduğu için derleme zamanında bilindiği için do/while döngüsünü en iyi duruma getirir. Bunun anlamı bir makro:

#define MY_MACRO(x)                                  \
do                                                   \
{                                                    \
    const int i = x + 1 ;                            \
    f(i) ; g(i) ;                                    \
}                                                    \
while(false)

void doSomething(bool bIsOk)
{
   int i = 25 ;

   if(bIsOk)
      MY_MACRO(42) ;

   // Etc.
}

doğru şekilde genişleyecektir

void doSomething(bool bIsOk)
{
   int i = 25 ;

   if(bIsOk)
      do
      {
         const int i = 42 + 1 ; // was MY_MACRO(42) ;
         f(i) ; g(i) ;
      }
      while(false) ;

   // Etc.
}

ve sonra derlenir ve en iyi duruma getirilir

void doSomething(bool bIsOk)
{
   int i = 25 ;

   if(bIsOk)
   {
      f(43) ; g(43) ;
   }

   // Etc.
}
140
paercebal

@ jfm3 - Soruya güzel bir cevabınız var. Makro deyimin, muhtemelen 'tehlikeli' ifadeleriyle istenmeyen (daha fazla hata olmadığından) istenmeyen davranışları önlediğini de eklemek isteyebilirsiniz:

#define FOO(x)  f(x); g(x)

if (test) FOO( baz);

için genişler:

if (test) f(baz); g(baz);

bu sözdizimsel olarak doğrudur, bu nedenle derleyici hatası yoktur, ancak g() 'nin her zaman çağrılması muhtemel istenmeyen bir sonucu vardır.

50
Michael Burr

Yukarıdaki cevaplar, bu yapıların anlamını açıklar, ancak belirtilmeyen ikisi arasında önemli bir fark vardır. Aslında, do ... while'u if ... else yapısına tercih etmek için bir neden vardır.

if ... else yapısının sorunu, noktalı virgül koymaya/yapmamasıdır. Bu koddaki gibi:

FOO(1)
printf("abc");

Noktalı virgül bırakmış olsak da (yanlışlıkla), kod

if (1) { f(X); g(X); } else
printf("abc");

ve sessizce derleyecektir (bazı derleyiciler erişilemez kod için bir uyarı verebilir). Ancak printf ifadesi asla yürütülmez.

do ... while yapısının böyle bir sorunu yoktur, çünkü while(0) öğesinden sonraki tek geçerli simge bir noktalı virgüldür.

23
ybungalobill

Derleyicilerin do { ... } while(false); döngülerini optimize etmesi beklenirken, bu yapıyı gerektirmeyen başka bir çözüm var. Çözüm, virgül operatörünü kullanmaktır:

#define FOO(X) (f(X),g(X))

veya daha dışsal olarak:

#define FOO(X) g((f(X),(X)))

Bu, ayrı talimatlarla iyi çalışacak olsa da, #define dosyasının bir parçası olarak değişkenlerin oluşturulduğu ve kullanıldığı durumlarda çalışmaz:

#define FOO(X) (int s=5,f((X)+s),g((X)+s))

Bununla bir do/while yapısını kullanmak zorunda kalacaksınız.

15
Marius

Jens Gustedt'ın P99 önişlemci kütüphanesi (evet, böyle bir şeyin var olması gerçeği de aklımı patlattı!) if(1) { ... } else yapısını küçük ama önemli bir şekilde aşağıdakileri tanımlayarak geliştirir:

#define P99_NOP ((void)0)
#define P99_PREFER(...) if (1) { __VA_ARGS__ } else
#define P99_BLOCK(...) P99_PREFER(__VA_ARGS__) P99_NOP

Bunun mantığı, do { ... } while(0) yapısının tersine, break ve continue öğelerinin hala verilen bloğun içinde çalışmaya devam etmesidir, ancak ((void)0), aksi takdirde bir sonraki bloğu atlayacak olan makro çağrısından sonra noktalı virgül atlanırsa sözdizimi hatası oluşturur. (Burada aslında "sarkan başka bir" problem yoktur, çünkü else makrodaki en yakın if'a bağlanır.) 

C önişlemcisiyle daha fazla veya daha az güvenli bir şekilde yapılabilecek şeyler ilginizi çekiyorsa, bu kitaplığa göz atın.

10

Bazı nedenlerden dolayı ilk cevap hakkında yorum yapamam ...

Bazılarınız yerel değişkenlere sahip makrolar gösterdi, ancak hiç kimse bir makroda herhangi bir isim kullanamayacağınızı söylemedi! Bir gün kullanıcıyı ısırır! Niye ya? Çünkü giriş argümanları makro şablonunuzla değiştirilir. Makro örneklerinde, muhtemelen en yaygın kullanılan değişken adını i kullanırsınız.

Örneğin aşağıdaki makro ne zaman

#define FOO(X) do { int i; for (i = 0; i < (X); ++i) do_something(i); } while (0)

aşağıdaki işlevde kullanılır

void some_func(void) {
    int i;
    for (i = 0; i < 10; ++i)
        FOO(i);
}

makro bazı_func'un başında bildirilen amaçlanan değişkeni i kullanmaz, fakat makroda döngü sırasında do ... 'da bildirilen yerel değişkendir.

Böylece, hiçbir zaman ortak değişken adlarını bir makroda kullanmayın!

6
Mike Meyer

Söylendiğini sanmıyorum, o yüzden bunu düşün

while(i<100)
  FOO(i++);

çevrilecek

while(i<100)
  do { f(i++); g(i++); } while (0)

i++ öğesinin makro tarafından nasıl iki kez değerlendirildiğini fark edin. Bu bazı ilginç hatalara yol açabilir.

3
John Nilsson

Bu numarayı çok faydalı buldum, belirli bir değeri sırayla işlemeniz gereken durumlarda. Her işlem düzeyinde, bir hata veya geçersiz durum meydana gelirse, daha fazla işlemden kaçınabilir ve erken bir şekilde ayrılabilirsiniz. Örneğin.

#define CALL_AND_RETURN(x)  if ( x() == false) break;
do {
     CALL_AND_RETURN(process_first);
     CALL_AND_RETURN(process_second);
     CALL_AND_RETURN(process_third);
     //(simply add other calls here)
} while (0);
2
pankaj

Açıklama

do {} while (0) ve if (1) {} else, makronun yalnızca 1 talimatla genişletildiğinden emin olmak içindir. Aksi takdirde:

if (something)
  FOO(X); 

genişler:

if (something)
  f(X); g(X); 

Ve g(X)__ if control ifadesinin dışında yürütülürdü. do {} while (0) ve if (1) {} else kullanılırken bu önlenir.


Daha iyi alternatif

GNU deyim ifadesi (standart C'nin bir parçası değil) ile, bunu çözmek için do {} while (0) ve if (1) {} else öğelerine göre daha iyi bir yönteminiz vardır, yalnızca ({}) kullanarak:

#define FOO(X) ({f(X); g(X);})

Ve bu sözdizimi, aşağıdaki gibi geri dönüş değerleriyle uyumludur (do {} while (0)'nin olmadığına dikkat edin):

return FOO("X");
2
Cœur

do {} while (0) işlevinin if (1) {} işlevinde kullanılmasının nedeni, başka bir tür blok olması için do {} while (0) makrosunu çağırmadan önce birisinin kodu değiştirmesinin mümkün olmamasıdır. Örneğin, if (1) {} benzeri bir makroyu çağırdıysanız:

else
  MACRO(x);

Bu aslında bir else if. İnce fark

0
ericcurtin