मैंने हमेशा उदाहरणों और मामलों को देखा, जहां मैक्रो का उपयोग फ़ंक्शन का उपयोग करने से बेहतर है।
किसी ने मुझे एक समारोह की तुलना में एक मैक्रो का नुकसान उदाहरण के साथ समझा सकता है?
मैंने हमेशा उदाहरणों और मामलों को देखा, जहां मैक्रो का उपयोग फ़ंक्शन का उपयोग करने से बेहतर है।
किसी ने मुझे एक समारोह की तुलना में एक मैक्रो का नुकसान उदाहरण के साथ समझा सकता है?
जवाबों:
मैक्रोज़ त्रुटि-प्रवण हैं, क्योंकि वे टेक्स्ट प्रतिस्थापन पर भरोसा करते हैं और टाइप-चेकिंग नहीं करते हैं। उदाहरण के लिए, यह मैक्रो:
#define square(a) a * a
पूर्णांक के साथ उपयोग किए जाने पर ठीक काम करता है:
square(5) --> 5 * 5 --> 25
लेकिन बहुत अजीब बातें करता है जब अभिव्यक्ति के साथ प्रयोग किया जाता है:
square(1 + 2) --> 1 + 2 * 1 + 2 --> 1 + 2 + 2 --> 5
square(x++) --> x++ * x++ --> increments x twice
तर्कों के चारों ओर कोष्ठक लगाने से मदद मिलती है, लेकिन इन समस्याओं को पूरी तरह से समाप्त नहीं करता है।
जब मैक्रोज़ में कई स्टेटमेंट होते हैं, तो आप कंट्रोल-फ्लो के निर्माण में परेशानी में पड़ सकते हैं:
#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;
इसे ठीक करने की सामान्य रणनीति यह है कि बयानों को "do {...} जबकि (0)" लूप के अंदर रखा जाए।
यदि आपके पास दो संरचनाएं हैं जो एक ही नाम के साथ एक फ़ील्ड में होती हैं लेकिन अलग-अलग शब्दार्थ हैं, तो एक ही मैक्रो दोनों पर काम कर सकता है, अजीब स्थिति के साथ:
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
अंत में, मैक्रोज़ को डीबग करना मुश्किल हो सकता है, अजीब सिंटैक्स त्रुटियों या रनटाइम त्रुटियों का निर्माण करना, जिन्हें आपको समझने के लिए विस्तार करना होगा (जैसे कि जीसीई-ई के साथ), क्योंकि डीबगर इस उदाहरण में मैक्रोज़ के माध्यम से कदम नहीं उठा सकते हैं:
#define print(x, y) printf(x y) /* accidentally forgot comma */
print("foo %s", "bar") /* prints "foo %sbar" */
इनलाइन कार्यों और स्थिरांक मैक्रो के साथ इन समस्याओं में से कई से बचने में मदद करते हैं, लेकिन हमेशा लागू नहीं होते हैं। जहां मैक्रोज़ का उपयोग जानबूझकर बहुरूपतापूर्ण व्यवहार को निर्दिष्ट करने के लिए किया जाता है, अनजाने में बहुरूपता से बचना मुश्किल हो सकता है। C ++ में कई विशेषताएं हैं जैसे कि मैक्रोज़ के उपयोग के बिना एक प्रकार से जटिल पॉलीमॉर्फिक निर्माणों को बनाने में मदद करने के लिए टेम्पलेट्स; विवरण के लिए स्ट्रॉस्ट्रुप की C ++ प्रोग्रामिंग भाषा देखें।
x++*x++
को x
दो बार बढ़ाने के लिए नहीं कहा जा सकता है ; यह वास्तव में अपरिभाषित व्यवहार का आह्वान करता है , जिसका अर्थ है कि संकलक ऐसा कुछ भी करने के लिए स्वतंत्र है - जो x
दो बार वृद्धि कर सकता है , या एक बार, या बिल्कुल नहीं; यह एक त्रुटि के साथ गर्भपात कर सकता है या यहां तक कि राक्षसों को आपकी नाक से बाहर निकाल सकता है ।
मैक्रो विशेषताएं :
समारोह विशेषताएं :
दुष्परिणाम एक बड़ा है। यहाँ एक विशिष्ट मामला है:
#define min(a, b) (a < b ? a : b)
min(x++, y)
इसका विस्तार किया जाता है:
(x++ < y ? x++ : y)
x
एक ही बयान में दो बार बढ़ जाता है। (और अपरिभाषित व्यवहार)
मल्टी-लाइन मैक्रोज़ लिखना भी एक दर्द है:
#define foo(a,b,c) \
a += 10; \
b += 10; \
c += 10;
उन्हें \
प्रत्येक पंक्ति के अंत में आवश्यकता होती है ।
मैक्रों तब तक कुछ भी "वापस" नहीं कर सकते जब तक आप इसे एक एकल अभिव्यक्ति नहीं बनाते हैं:
int foo(int *a, int *b){
side_effect0();
side_effect1();
return a[0] + b[0];
}
जब तक आप GCC के अभिव्यक्ति कथन का उपयोग नहीं करते तब तक मैक्रो में ऐसा नहीं कर सकते। (संपादित करें: आप एक अल्पविराम ऑपरेटर का उपयोग कर सकते हैं ... यह अनदेखी की गई है ... लेकिन यह अभी भी कम पठनीय हो सकता है।)
संचालन का क्रम: (@ouah के सौजन्य से)
#define min(a,b) (a < b ? a : b)
min(x & 0xFF, 42)
इसका विस्तार किया जाता है:
(x & 0xFF < 42 ? x & 0xFF : 42)
लेकिन &
पहले की तुलना में कम है <
। तो 0xFF < 42
पहले मूल्यांकन किया जाता है।
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;
}
जहाँ तक:
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;
}
की तुलना में:
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;
}
जब संदेह हो, तो फ़ंक्शंस (या इनलाइन फ़ंक्शंस) का उपयोग करें।
हालाँकि यहाँ उत्तर ज्यादातर मैक्रोज़ के साथ समस्याओं की व्याख्या करते हैं, इसके बजाय कुछ सरल दृष्टिकोण है कि मैक्रोज़ बुराई हैं क्योंकि मूर्खतापूर्ण दुर्घटनाएं संभव हैं।
आप नुकसान से अवगत हो सकते हैं और उनसे बचना सीख सकते हैं। तब मैक्रोज़ का उपयोग करें जब कोई अच्छा कारण हो।
कुछ विशिष्ट मामले हैं जहां मैक्रोज़ का उपयोग करने के फायदे हैं, इनमें शामिल हैं:
va_args
। __FILE__
, __LINE__
, __func__
)। पूर्व / पोस्ट की स्थिति के लिए जाँच करें, assert
विफलता पर, या यहां तक कि स्थैतिक-जोर देता है ताकि कोड अनुचित उपयोग (ज्यादातर डिबग बिल्ड के लिए उपयोगी) पर संकलित न हो।struct
सदस्य कास्टिंग से पहले मौजूद हैं func(FOO, "FOO");
, आप एक मैक्रो को परिभाषित कर सकते हैं जो आपके लिए स्ट्रिंग का विस्तार करता है।func_wrapper(FOO);
inline
फ़ंक्शन एक विकल्प हो सकता है) ।बेशक, इनमें से कुछ कंपाइलर एक्सटेंशन पर भरोसा करते हैं, जो मानक सी नहीं हैं। मतलब कि आप कम पोर्टेबल कोड के साथ समाप्त हो सकते हैं, या ifdef
उनमें हो सकते हैं, इसलिए वे केवल लाभ उठाते हैं जब कंपाइलर समर्थन करता है।
मैक्रोज़ में त्रुटियों के सबसे आम कारणों में से एक के बाद से यह नोट करना ( x++
उदाहरण के लिए गुजरना , जहां एक मैक्रो कई बार बढ़ सकता है) ।
मैक्रोज़ को लिखना संभव है जो तर्कों के कई पलणों के साथ दुष्प्रभावों से बचते हैं।
अगर आपको square
मैक्रो पसंद है जो विभिन्न प्रकारों के साथ काम करता है और C11 समर्थन है, तो आप ऐसा कर सकते हैं ...
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))
यह GCC, Clang, EKOPath & Intel C ++ (लेकिन MSVC नहीं) द्वारा समर्थित एक कंपाइलर एक्सटेंशन है ;
#define square(a_) __extension__ ({ \
typeof(a_) a = (a_); \
(a * a); })
तो मैक्रोज़ के साथ नुकसान यह है कि आपको इनका उपयोग करने के लिए जानने की आवश्यकता है, और वे व्यापक रूप से समर्थित नहीं हैं।
एक लाभ यह है, इस मामले में, आप square
कई अलग-अलग प्रकारों के लिए एक ही फ़ंक्शन का उपयोग कर सकते हैं ।
किसी भी प्रकार के मापदंडों और कोड को दोहराया नहीं जाता है जिससे कोड ब्लोट हो सकता है। मैक्रो सिंटैक्स किसी भी प्रकार के अजीब किनारे के मामलों को जन्म दे सकता है जहां अर्ध-कॉलोन या पूर्वता के आदेश रास्ते में मिल सकते हैं। यहाँ एक कड़ी है जो कुछ स्थूल बुराई को प्रदर्शित करती है
मैक्रोज़ के लिए एक दोष यह है कि डिबगर्स स्रोत कोड पढ़ते हैं, जिसमें मैक्रोज़ का विस्तार नहीं होता है, इसलिए मैक्रो में डीबगर चलाना आवश्यक नहीं है। कहने की जरूरत नहीं है, आप एक मैक्रो के अंदर एक ब्रेकपॉइंट सेट नहीं कर सकते हैं जैसे आप कार्यों के साथ कर सकते हैं।
कार्य प्रकार की जाँच करते हैं। यह आपको सुरक्षा की एक अतिरिक्त परत प्रदान करता है।
इस उत्तर में जोड़ना ।।
मैक्रोज़ को सीधे प्रोग्राम में प्रीप्रोसेसर द्वारा प्रतिस्थापित किया जाता है (क्योंकि वे मूल रूप से प्रीप्रोसेसर निर्देश हैं)। इसलिए वे अनिवार्य रूप से संबंधित फ़ंक्शन की तुलना में अधिक मेमोरी स्पेस का उपयोग करते हैं। दूसरी ओर, एक फ़ंक्शन को कॉल करने और परिणाम वापस करने के लिए अधिक समय की आवश्यकता होती है, और मैक्रोज़ का उपयोग करके इस ओवरहेड से बचा जा सकता है।
इसके अलावा मैक्रोज़ के पास कुछ विशेष उपकरण हैं जो विभिन्न प्लेटफार्मों पर प्रोग्राम पोर्टेबिलिटी की मदद कर सकते हैं।
Macros को फ़ंक्शन के विपरीत अपने तर्कों के लिए डेटा प्रकार असाइन करने की आवश्यकता नहीं है।
कुल मिलाकर वे प्रोग्रामिंग में एक उपयोगी उपकरण हैं। और मैक्रोइन्स्ट्रक्शंस और फ़ंक्शंस दोनों का उपयोग परिस्थितियों के आधार पर किया जा सकता है।
मैंने ऊपर दिए गए उत्तरों में, मैक्रोज़ पर किए गए फ़ंक्शंस का एक फायदा, जो मुझे लगता है कि बहुत महत्वपूर्ण है, पर ध्यान नहीं दिया:
कार्यों को तर्क के रूप में पारित किया जा सकता है, मैक्रोज़ नहीं कर सकते।
ठोस उदाहरण: आप मानक 'strpbrk' फ़ंक्शन का एक वैकल्पिक संस्करण लिखना चाहते हैं, जो किसी अन्य स्ट्रिंग के लिए खोज करने के लिए वर्णों की एक स्पष्ट सूची के बजाय स्वीकार करेगा, एक (सूचक) एक फ़ंक्शन के लिए 0 जो किसी वर्ण के होने तक वापस आ जाएगा पाया कि कुछ परीक्षा (उपयोगकर्ता द्वारा परिभाषित) पास करता है। ऐसा करने का एक कारण यह भी हो सकता है कि आप अन्य मानक पुस्तकालय कार्यों का दोहन कर सकते हैं: विराम चिह्न से भरा एक स्पष्ट स्ट्रिंग प्रदान करने के बजाय, आप इसके बजाय ctype.h का 'ispunct' पास कर सकते हैं, आदि यदि 'ispunct' केवल के रूप में लागू किया गया था। एक मैक्रो, यह काम नहीं करेगा।
अन्य कई उदाहरण हैं। उदाहरण के लिए, यदि आपकी तुलना फ़ंक्शन के बजाय मैक्रो द्वारा पूरी की जाती है, तो आप इसे stdlib.h के 'qsort' में पास नहीं कर सकते।
पायथन में एक अनुरूप स्थिति संस्करण 2 बनाम संस्करण 3 (गैर-निष्क्रिय बयान बनाम निष्क्रिय फ़ंक्शन) में 'प्रिंट' है।
यदि आप फ़ंक्शन को मैक्रो के तर्क के रूप में पास करते हैं तो इसका मूल्यांकन हर बार किया जाएगा। उदाहरण के लिए, यदि आप सबसे लोकप्रिय मैक्रो में से एक कहते हैं:
#define MIN(a,b) ((a)<(b) ? (a) : (b))
उसके जैसा
int min = MIN(functionThatTakeLongTime(1),functionThatTakeLongTime(2));
functionThatTakeLongTime का मूल्यांकन 5 बार किया जाएगा जो कि पूर्णता को गिरा सकता है