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?
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?
Yanıtlar:
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.
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 - x
iki kez veya bir kez artabilir veya hiç artmayabilir; bir hata ile iptal edilebilir veya iblislerin burnunuzdan uçmasına neden olabilir .
Makro özellikler :
Fonksiyon özellikleri :
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)
x
aynı 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.
min(a & 0xFF, 42)
#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;
}
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;
}
Şü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:
va_args
. __FILE__
, __LINE__
, __func__
). assert
kodun 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).struct
değ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 func(FOO, "FOO");
sizin için dizeyi genişleten bir makro tanımlayabilirsiniz.func_wrapper(FOO);
inline
fonksiyonlar 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 ifdef
bunlara girmek zorunda kalabilirsiniz , bu nedenle yalnızca derleyici desteklediğinde yararlanılır.
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.
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))
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ı square
işlevi birçok farklı tür için kullanabilmenizdir .
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ı
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.
Fonksiyonlar tip kontrolü yapar. Bu size ekstra bir güvenlik katmanı sağlar.
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.
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).
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