C Makro ve Fonksiyon


101

Her zaman makro kullanmanın işlev kullanmaktan daha iyi olduğu örnekler ve durumlar gördüm.

Biri bana bir makronun bir işleve kıyasla dezavantajını bir örnekle açıklayabilir mi?


21
Soruyu baş aşağı çevirin. Makro hangi durumda daha iyidir? Bir makronun daha iyi olduğunu gösteremediğiniz sürece gerçek bir işlev kullanın.
David Heffernan

Yanıtlar:


114

Makrolar, metin değiştirmeye dayandıklarından ve tür denetimi yapmadıklarından hataya açıktır. Örneğin, bu makro:

#define square(a) a * a

bir tamsayı ile kullanıldığında iyi çalışır:

square(5) --> 5 * 5 --> 25

ancak ifadelerle kullanıldığında çok tuhaf şeyler yapar:

square(1 + 2) --> 1 + 2 * 1 + 2 --> 1 + 2 + 2 --> 5
square(x++) --> x++ * x++ --> increments x twice

Bağımsız değişkenlerin etrafına parantez koymak yardımcı olur, ancak bu sorunları tamamen ortadan kaldırmaz.

Makrolar birden fazla ifade içerdiğinde, kontrol akışı yapılarında sorun yaşayabilirsiniz:

#define swap(x, y) t = x; x = y; y = t;

if (x < y) swap(x, y); -->
if (x < y) t = x; x = y; y = t; --> if (x < y) { t = x; } x = y; y = t;

Bunu düzeltmek için olağan strateji, ifadeleri bir "do {...} while (0)" döngüsünün içine koymaktır.

Aynı ada sahip ancak farklı anlambilimlere sahip bir alan içeren iki yapınız varsa, aynı makro her ikisinde de çalışabilir ve garip sonuçlar verebilir:

struct shirt 
{
    int numButtons;
};

struct webpage 
{
    int numButtons;
};

#define num_button_holes(shirt)  ((shirt).numButtons * 4)

struct webpage page;
page.numButtons = 2;
num_button_holes(page) -> 8

Son olarak, makrolarda hata ayıklamak zor olabilir ve anlaşılması için genişletmeniz gereken garip sözdizimi hataları veya çalışma zamanı hataları üretebilir (örneğin, gcc -E ile), çünkü hata ayıklayıcılar makrolar arasında adım atamazlar, bu örnekte olduğu gibi:

#define print(x, y)  printf(x y)  /* accidentally forgot comma */
print("foo %s", "bar") /* prints "foo %sbar" */

Satır içi işlevler ve sabitler, makrolarla ilgili bu sorunların çoğunu önlemeye yardımcı olur, ancak her zaman geçerli değildir. Makroların kasıtlı olarak polimorfik davranışı belirtmek için kullanıldığı durumlarda, kasıtsız polimorfizmden kaçınmak zor olabilir. C ++, makrolar kullanılmadan tip güvenli bir şekilde karmaşık polimorfik yapılar oluşturmaya yardımcı olacak şablonlar gibi bir dizi özelliğe sahiptir; Ayrıntılar için Stroustrup'un C ++ Programlama Dili'ne bakın.


43
C ++ reklamıyla ilgili ne var?
Pacerier

4
Katılıyorum, bu bir C sorusu, önyargı eklemeye gerek yok.
ideasman42

16
C ++, C'nin bu belirli sınırlamasını ele almayı amaçlayan (diğer şeylerin yanı sıra) özellikler ekleyen bir C uzantısıdır. C ++ hayranı değilim, ancak burada konuyla ilgili olduğunu düşünüyorum.
D Coetzee

1
Makrolar, satır içi işlevler ve şablonlar genellikle performansı artırmak amacıyla kullanılır. Aşırı kullanılırlar ve CPU komut önbelleğinin etkinliğini azaltan kod şişkinliği nedeniyle performansa zarar verme eğilimindedirler. Bu teknikleri kullanmadan C'de hızlı jenerik veri yapıları yapabiliriz.
Sam Watkins

1
ISO / IEC 9899: 1999 §6.5.1'e göre, "Önceki ve sonraki sıra noktası arasında bir nesnenin depolanan değeri, bir ifadenin değerlendirilmesiyle en fazla bir kez değiştirilmelidir." (Benzer ifadeler önceki ve sonraki C standartlarında mevcuttur.) Dolayısıyla ifadenin iki kez x++*x++arttığı söylenemez x; aslında tanımsız davranışı çağırır , yani derleyicinin istediği her şeyi yapmakta özgür olduğu anlamına gelir - xiki kez veya bir kez artabilir veya hiç artmayabilir; bir hata ile iptal edilebilir veya iblislerin burnunuzdan uçmasına neden olabilir .
Psychonaut

40

Makro özellikler :

  • Makro Önceden İşlendi
  • Tip Kontrolü Yok
  • Kod Uzunluğu Arttı
  • Makro kullanımı yan etkilere neden olabilir
  • Yürütme Hızı Daha Hızlı
  • Derleme makro adı makro değeri ile değiştirilmeden önce
  • Küçük kodun birçok kez göründüğü yerlerde kullanışlıdır
  • Makro yok değil Derleme Hataları Kontrol

Fonksiyon özellikleri :

  • Fonksiyon Derlendi
  • Tür Kontrolü Tamamlandı
  • Kod Uzunluğu Aynı Kalır
  • Yan etki yok
  • Yürütme Hızı Daha Yavaş
  • İşlev çağrısı sırasında, Kontrolün Devri gerçekleşir
  • Büyük kodun birçok kez göründüğü yerlerde kullanışlıdır
  • İşlev Denetimleri Derleme Hataları

2
"yürütme hızı daha hızlıdır" referansı gereklidir. Son on yılın bir dereceye kadar yetkin herhangi bir derleyicisi, bir performans avantajı sağlayacağını düşünürse, satır içi işlevler için yeterli olacaktır.
Voo

1
Bu, düşük seviyeli MCU (AVR'ler, yani ATMega32) bilgi işlem bağlamında, Makrolar, işlev çağrıları gibi çağrı yığınını büyütmedikleri için daha iyi bir seçim değil mi?
hardyVeles

1
@hardyVeles Öyle değil. Derleyiciler, bir AVR için bile, çok akıllı bir şekilde satır içi kod yazabilirler. İşte bir örnek: godbolt.org/z/Ic21iM
Edward

34

Yan etkiler büyüktür. İşte tipik bir durum:

#define min(a, b) (a < b ? a : b)

min(x++, y)

şu şekilde genişletilir:

(x++ < y ? x++ : y)

xaynı ifadede iki kez artırılır. (ve tanımlanmamış davranış)


Çok satırlı makrolar yazmak da bir sıkıntıdır:

#define foo(a,b,c)  \
    a += 10;        \
    b += 10;        \
    c += 10;

\Her satırın sonunda bir gerektirirler .


Tek bir ifade yapmadığınız sürece makrolar hiçbir şeyi "döndüremez":

int foo(int *a, int *b){
    side_effect0();
    side_effect1();
    return a[0] + b[0];
}

GCC'nin ifade ifadesini kullanmadığınız sürece bunu bir makroda yapamazsınız. (DÜZENLEME: Bir virgül operatörü kullanabilirsiniz ... bunu göz ardı ettim ... Ama yine de daha az okunabilir olabilir.)


Operasyon Emri: (@ouah'ın izniyle)

#define min(a,b) (a < b ? a : b)

min(x & 0xFF, 42)

şu şekilde genişletilir:

(x & 0xFF < 42 ? x & 0xFF : 42)

Ancak &daha düşük önceliğe sahiptir <. Yani 0xFF < 42önce değerlendirilir.


5
ve makro tanımında makro bağımsız değişkenleri olan parantez min(a & 0xFF, 42)
koymamak

Ah evet. Gönderiyi güncellerken yorumunuzu görmedim. Sanırım bundan da bahsedeceğim.
Mysticial

14

Örnek 1:

#define SQUARE(x) ((x)*(x))

int main() {
  int x = 2;
  int y = SQUARE(x++); // Undefined behavior even though it doesn't look 
                       // like it here
  return 0;
}

buna karşılık:

int square(int x) {
  return x * x;
}

int main() {
  int x = 2;
  int y = square(x++); // fine
  return 0;
}

Örnek 2:

struct foo {
  int bar;
};

#define GET_BAR(f) ((f)->bar)

int main() {
  struct foo f;
  int a = GET_BAR(&f); // fine
  int b = GET_BAR(&a); // error, but the message won't make much sense unless you
                       // know what the macro does
  return 0;
}

Nazaran:

struct foo {
  int bar;
};

int get_bar(struct foo *f) {
  return f->bar;
}

int main() {
  struct foo f;
  int a = get_bar(&f); // fine
  int b = get_bar(&a); // error, but compiler complains about passing int* where 
                       // struct foo* should be given
  return 0;
}

13

Şüpheniz olduğunda, işlevleri (veya satır içi işlevleri) kullanın.

Bununla birlikte, buradaki cevaplar, saçma kazaların olası olması nedeniyle makroların kötü olduğuna dair basit bir görüşe sahip olmak yerine, çoğunlukla makrolarla ilgili sorunları açıklamaktadır.
Tuzakların farkında olabilir ve onlardan kaçınmayı öğrenebilirsiniz. O zaman makroları yalnızca bunun için iyi bir neden olduğunda kullanın.

Makro kullanmanın avantajlarının olduğu bazı istisnai durumlar vardır, bunlar şunları içerir:

  • Genel işlevler, aşağıda belirtildiği gibi, farklı türlerdeki girdi bağımsız değişkenlerinde kullanılabilen bir makroya sahip olabilirsiniz.
  • Değişken sayıda argüman, C'leri kullanmak yerine farklı işlevlerle eşleşebilir va_args.
    örneğin: https://stackoverflow.com/a/24837037/432509 .
  • Bunlar olabilir isteğe böyle ayıklama dizeleri olarak yerel bilgi içerir:
    ( __FILE__, __LINE__, __func__). assertkodun yanlış kullanımda derlenmemesi için ön / son koşulları, başarısızlık durumunda ve hatta statik önermeleri kontrol edin (çoğunlukla hata ayıklama yapıları için yararlıdır).
  • Girdi bağımsız structdeğişkenlerini inceleyin , türlerini, boyutlarını , üyelerin dökümden önce mevcut olup olmadığını denetleme gibi girdi bağımsız değişkenleri üzerinde testler
    yapabilirsiniz (polimorfik türler için yararlı olabilir) .
    Veya bir dizinin bazı uzunluk koşullarını karşıladığını kontrol edin.
    bkz: https://stackoverflow.com/a/29926435/432509
  • İşlevlerin tür denetimi yaptığı not edilirken, C de değerleri zorlayacaktır (örneğin, ints / floats). Nadir durumlarda bu sorunlu olabilir. Girdi argümanları hakkında bir fonksiyondan daha titiz makrolar yazmak mümkündür. bkz: https://stackoverflow.com/a/25988779/432509
  • İşlevlere sarmalayıcı olarak kullanımları, bazı durumlarda kendinizi tekrar etmekten kaçınmak isteyebilirsiniz, örneğin ... func(FOO, "FOO");sizin için dizeyi genişleten bir makro tanımlayabilirsiniz.func_wrapper(FOO);
  • Arayanların yerel kapsamındaki değişkenleri işlemek istediğinizde, işaretçiyi bir işaretçiye geçirmek normal olarak iyi çalışır, ancak bazı durumlarda yine de bir makroyu kullanmak daha az zahmetlidir.
    (Piksel başına işlemler için birden çok değişkene atamalar, bir fonksiyon yerine bir makroyu tercih edebileceğiniz bir örnektir ... yine de çok fazla bağlama bağlıdır, çünkü inlinefonksiyonlar bir seçenek olabilir) .

Kuşkusuz, bunlardan bazıları standart C olmayan derleyici uzantılarına güveniyor. Yani, daha az taşınabilir kodla sonuçlanabilirsiniz veya ifdefbunlara girmek zorunda kalabilirsiniz , bu nedenle yalnızca derleyici desteklediğinde yararlanılır.


Birden fazla argüman somutlaştırmasından kaçınmak

Makrolardaki hataların en yaygın nedenlerinden biri olduğu için bunu not etmek ( x++örneğin, bir makronun birden çok kez artabileceği durumlarda geçiş ) .

Birden çok bağımsız değişken örneğiyle yan etkilerden kaçınan makrolar yazmak mümkündür.

C11 Genel

squareÇeşitli tiplerle çalışan ve C11 desteğine sahip bir makro sahibi olmak isterseniz bunu yapabilirsiniz ...

inline float           _square_fl(float a) { return a * a; }
inline double          _square_dbl(float a) { return a * a; }
inline int             _square_i(int a) { return a * a; }
inline unsigned int    _square_ui(unsigned int a) { return a * a; }
inline short           _square_s(short a) { return a * a; }
inline unsigned short  _square_us(unsigned short a) { return a * a; }
/* ... long, char ... etc */

#define square(a)                        \
    _Generic((a),                        \
        float:          _square_fl(a),   \
        double:         _square_dbl(a),  \
        int:            _square_i(a),    \
        unsigned int:   _square_ui(a),   \
        short:          _square_s(a),    \
        unsigned short: _square_us(a))

İfade ifadeleri

Bu, GCC, Clang, EKOPath ve Intel C ++ tarafından desteklenen bir derleyici uzantısıdır (ancak MSVC değil) ;

#define square(a_) __extension__ ({  \
    typeof(a_) a = (a_); \
    (a * a); })

Dolayısıyla, makroların dezavantajı, başlangıçta bunları kullanmayı bilmeniz ve yaygın olarak desteklenmedikleridir.

Yararlarından biri, bu durumda aynı squareişlevi birçok farklı tür için kullanabilmenizdir .


1
"... yaygın olarak destekleniyor .." Bahse girerim bahsettiğiniz ifade ifadesi cl.exe tarafından desteklenmiyor? (MS Derleyicisi)
gideon

1
@gideon, doğru düzenlenmiş yanıt, ancak bahsedilen her özellik için bazı derleyici özellik destek matrisine sahip olmanın gerekli olduğundan emin değil.
ideasman42

12

Parametrelerin ve kodun tür kontrolü tekrarlanmaz, bu da kod şişmesine neden olabilir. Makro sözdizimi, noktalı virgüllerin veya öncelik sırasının araya girebileceği herhangi bir sayıda garip uç duruma da yol açabilir. İşte bazı makro kötülükleri gösteren bir bağlantı


6

Makroların bir dezavantajı, hata ayıklayıcıların genişletilmiş makrolara sahip olmayan kaynak kodunu okumasıdır, bu nedenle bir makroda bir hata ayıklayıcı çalıştırmanın yararlı olması gerekmez. Söylemeye gerek yok, işlevlerde yaptığınız gibi bir makronun içinde bir kesme noktası ayarlayamazsınız.


Kırılma noktası burada çok önemli bir anlaşma, işaret ettiğiniz için teşekkürler.
Hans

6

Fonksiyonlar tip kontrolü yapar. Bu size ekstra bir güvenlik katmanı sağlar.


6

Bu yanıta ekleniyor ..

Makrolar önişlemci tarafından doğrudan programın içine yerleştirilir (çünkü bunlar temelde önişlemci yönergeleridir). Bu nedenle, kaçınılmaz olarak ilgili bir işlevden daha fazla bellek alanı kullanırlar. Öte yandan, bir işlevin çağrılması ve sonuçların döndürülmesi için daha fazla zaman gerekir ve bu ek yük makrolar kullanılarak önlenebilir.

Ayrıca makrolar, farklı platformlarda program taşınabilirliğine yardımcı olabilecek bazı özel araçlara sahiptir.

İşlevlerin aksine makrolara bağımsız değişkenleri için bir veri türü atanması gerekmez.

Genel olarak programlamada yararlı bir araçtırlar. Koşullara bağlı olarak hem makro talimatlar hem de işlevler kullanılabilir.


3

Yukarıdaki cevaplarda, fonksiyonların makrolara göre bir avantajının çok önemli olduğunu düşündüğüm bir avantajı olduğunu fark etmedim:

Fonksiyonlar bağımsız değişken olarak aktarılabilir, makrolar olamaz.

Somut örnek: Standart 'strpbrk' işlevinin, başka bir dizge içinde aranacak açık bir karakter listesi yerine, bir karakter olana kadar 0 döndüren bir (a'ya işaretçi) işlevini kabul edecek alternatif bir sürümünü yazmak istiyorsunuz. bazı testleri geçtiğini tespit etti (kullanıcı tanımlı). Bunu yapmak istemenizin bir nedeni, diğer standart kütüphane işlevlerinden yararlanabilmenizdir: noktalama işaretleriyle dolu açık bir dize sağlamak yerine, bunun yerine ctype.h'nin 'ispunct' ifadesini iletebilirsiniz, vb. Eğer 'ispunct' yalnızca bir makro, bu işe yaramaz.

Daha birçok örnek var. Örneğin, karşılaştırmanız işlevden ziyade makro ile yapılıyorsa, bunu stdlib.h'nin 'qsort'a aktaramazsınız.

Python'da benzer bir durum, sürüm 2'ye karşı sürüm 3'teki 'yazdırma'dır (pasif olmayan ifadeye karşı pasif fonksiyon).


1
Bu cevap için teşekkürler
Kyrol

1

Fonksiyonu makroya bir argüman olarak iletirseniz, her seferinde değerlendirilecektir. Örneğin, en popüler makrodan birini ararsanız:

#define MIN(a,b) ((a)<(b) ? (a) : (b))

bunun gibi

int min = MIN(functionThatTakeLongTime(1),functionThatTakeLongTime(2));

functionThatTakeLongTime 5 kez değerlendirilecek ve bu da performansı önemli ölçüde düşürebilir

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.