C ++ कोड के लिए मैक्रो नुकसान

परिभाषित

सी ++ भाषा मैक्रोज़ के बिना करने के लिए विशाल संभावनाएं प्रदान करती है। तो आइए मैक्रोज़ का उपयोग यथासंभव कम करने का प्रयास करें!

तुरंत एक आरक्षण करें कि मैं कट्टर नहीं हूं और आदर्शवादी कारणों से मैक्रों को छोड़ने का आग्रह नहीं करता। उदाहरण के लिए, जब मैन्युअल रूप से एक ही प्रकार का कोड बनाने की बात आती है, तो मैं मैक्रोज़ के लाभों को पहचान सकता हूं और उनके साथ आ सकता हूं। उदाहरण के लिए, मैं MFC का उपयोग करके लिखे गए पुराने कार्यक्रमों में मैक्रोज़ के बारे में शांत हूं। यह इस तरह से कुछ के साथ लड़ने के लिए कोई मतलब नहीं है:

BEGIN_MESSAGE_MAP(efcDialog, EFCDIALOG_PARENT ) //{{AFX_MSG_MAP(efcDialog) ON_WM_CREATE() ON_WM_DESTROY() //}}AFX_MSG_MAP END_MESSAGE_MAP() 

ऐसे मैक्रोज़ हैं, और ठीक है। वे वास्तव में प्रोग्रामिंग को सरल बनाने के लिए बनाए गए थे।

मैं अन्य मैक्रो के बारे में बात कर रहा हूं, जिसके साथ वे एक पूर्ण फ़ंक्शन के कार्यान्वयन से बचने या किसी फ़ंक्शन के आकार को कम करने का प्रयास करते हैं। इस तरह के मैक्रोज़ से बचने के लिए कई उद्देश्यों पर विचार करें।

नोट। यह पाठ सरलीकृत C ++ ब्लॉग के लिए अतिथि पोस्ट के रूप में लिखा गया था। मैंने लेख के रूसी संस्करण को यहां प्रकाशित करने का निर्णय लिया। दरअसल, मैं इस नोट को असंगत पाठकों के एक सवाल से बचने के लिए लिख रहा हूं कि लेख को "अनुवाद" :) के रूप में चिह्नित क्यों नहीं किया गया है। और यहाँ, वास्तव में, अंग्रेजी में एक अतिथि पोस्ट: " सी ++ कोड में मैक्रो ईविल "।

पहला: मैक्रो कोड बग्स को आकर्षित करता है


मैं नहीं जानता कि दार्शनिक दृष्टिकोण से इस घटना के कारणों को कैसे समझा जाए, लेकिन यह है। इसके अलावा, मैक्रो-संबंधित बग अक्सर एक कोड की समीक्षा करते समय स्पॉट करना बहुत मुश्किल होता है।

मैंने अपने लेखों में ऐसे मामलों का बार-बार वर्णन किया है। उदाहरण के लिए, इस स्थूल के साथ आइसस्पेस फ़ंक्शन को प्रतिस्थापित करना :

 #define isspace(c) ((c)==' ' || (c) == '\t') 

जो प्रोग्रामर isspace का उपयोग करता था, वह मानता था कि वह एक वास्तविक फ़ंक्शन का उपयोग कर रहा है, जो न केवल रिक्त स्थान और टैब को व्हॉट्सएप मानता है, बल्कि LF, CR, आदि भी है। नतीजा यह है कि शर्तों में से एक हमेशा सच होती है और कोड इरादा के अनुसार काम नहीं करता है। आधी रात के कमांडर से यह त्रुटि यहां वर्णित है

या आप इस std को फंक्शन std :: printf लिखने के लिए कैसे पसंद करते हैं?

 #define sprintf std::printf 

मुझे लगता है कि पाठक यह अनुमान लगाते हैं कि यह एक असफल मैक्रो था। यह पाया गया, वैसे, StarEngine प्रोजेक्ट में। इसके बारे में यहाँ और पढ़ें।

कोई यह तर्क दे सकता है कि प्रोग्रामर इन त्रुटियों के लिए दोषी हैं, मैक्रोज़ नहीं। ऐसा है। स्वाभाविक रूप से, प्रोग्रामर हमेशा त्रुटियों के लिए दोषी होते हैं :)।

यह महत्वपूर्ण है कि मैक्रोज़ त्रुटियों का कारण बनें। यह पता चला है कि मैक्रोज़ का उपयोग बढ़ी हुई सटीकता के साथ किया जाना चाहिए या बिल्कुल नहीं।

मैं लंबे समय तक मैक्रोज़ के उपयोग से जुड़े दोषों का उदाहरण दे सकता हूं, और यह अच्छा नोट एक वजनदार बहु-पृष्ठ दस्तावेज़ में बदल जाएगा। बेशक, मैं ऐसा नहीं करूंगा, लेकिन मैं आश्वस्त करने के लिए कुछ अन्य मामलों को दिखाऊंगा।

ATL लाइब्रेरी स्ट्रिंग्स को कनवर्ट करने के लिए मैक्रोज़ जैसे A2W, T2W, इत्यादि प्रदान करती है । हालांकि, कम ही लोग जानते हैं कि ये मैक्रोज़ लूप के अंदर इस्तेमाल करने के लिए बहुत खतरनाक हैं। मैक्रो के अंदर, एलोका फ़ंक्शन कहा जाता है, जो लूप के प्रत्येक पुनरावृत्ति पर बार-बार स्टैक पर मेमोरी आवंटित करेगा। एक कार्यक्रम सही ढंग से काम करने का दिखावा कर सकता है। जैसे ही कार्यक्रम लंबी लाइनों को संसाधित करना शुरू करता है या लूप में पुनरावृत्तियों की संख्या बढ़ जाती है, स्टैक सबसे अप्रत्याशित क्षण में ले और समाप्त हो सकता है। आप इस मिनी-बुक में इसके बारे में अधिक पढ़ सकते हैं (अध्याय "लूप के अंदर अलोक () फ़ंक्शन को कॉल न करें") देखें।

ए 2 डब्ल्यू जैसे मैक्रोज़ बुराई को छिपाते हैं। वे कार्यों की तरह दिखते हैं, लेकिन, वास्तव में, साइड इफेक्ट्स होते हैं जो नोटिस करना मुश्किल है।

मैं मैक्रो का उपयोग करके कोड को कम करने के समान पिछले प्रयास नहीं कर सकता:

 void initialize_sanitizer_builtins (void) { .... #define DEF_SANITIZER_BUILTIN(ENUM, NAME, TYPE, ATTRS) \ decl = add_builtin_function ("__builtin_" NAME, TYPE, ENUM, \ BUILT_IN_NORMAL, NAME, NULL_TREE); \ set_call_expr_flags (decl, ATTRS); \ set_builtin_decl (ENUM, decl, true); #include "sanitizer.def" if ((flag_sanitize & SANITIZE_OBJECT_SIZE) && !builtin_decl_implicit_p (BUILT_IN_OBJECT_SIZE)) DEF_SANITIZER_BUILTIN (BUILT_IN_OBJECT_SIZE, "object_size", BT_FN_SIZE_CONST_PTR_INT, ATTR_PURE_NOTHROW_LEAF_LIST) .... } 

केवल मैक्रो की पहली पंक्ति, यदि कथन को संदर्भित करती है। शेष लाइनों को हालत की परवाह किए बिना निष्पादित किया जाएगा। हम यह कह सकते हैं कि यह त्रुटि सी दुनिया की है, क्योंकि यह मेरे द्वारा जीसीसी संकलक के अंदर V640 निदान का उपयोग करके पाया गया था। जीसीसी कोड मुख्य रूप से सी में लिखा गया है, और इस भाषा में मैक्रोज़ करना मुश्किल है। हालाँकि, आपको यह स्वीकार करना होगा कि ऐसा नहीं है। यहां वास्तविक कार्य करना काफी संभव था।

दूसरा: कोड पढ़ना अधिक जटिल हो जाता है


यदि आप एक ऐसे प्रोजेक्ट में आते हैं, जो अन्य मैक्रोज़ से मिलकर मैक्रोज़ से भरा होता है, तो आप समझते हैं कि इस तरह के प्रोजेक्ट को समझना क्या नरक है। यदि आपने सामना नहीं किया है, तो एक शब्द लें, यह दुखद है। कोड के एक उदाहरण के रूप में जो पढ़ना मुश्किल है, मैं पहले उल्लेख किए गए जीसीसी संकलक का हवाला दे सकता हूं।

किंवदंती के अनुसार, ऐप्पल ने एलएलवीएम परियोजना के विकास में जीसीसी के विकल्प के रूप में जीसीसी कोड की जटिलता के कारण निवेश किया है। जहां मैंने इसके बारे में पढ़ा, मुझे याद नहीं है, इसलिए कोई सबूत नहीं होगा।

तीसरा: मैक्रोज़ लिखना कठिन है


खराब मैक्रो लिखना आसान है। मैं उन्हें हर जगह इसी परिणाम के साथ मिलते हैं। लेकिन एक समान फ़ंक्शन लिखने की तुलना में एक अच्छा और विश्वसनीय मैक्रो लिखना अक्सर अधिक कठिन होता है।

एक अच्छा मैक्रो लिखना इस कारण से मुश्किल है कि, एक फ़ंक्शन के विपरीत, इसे एक स्वतंत्र इकाई के रूप में नहीं माना जा सकता है। इसके उपयोग के लिए सभी संभावित विकल्पों के संदर्भ में मैक्रो पर तुरंत विचार करना आवश्यक है, अन्यथा फॉर्म की समस्या को हल करना बहुत आसान है:

 #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y)) m = MIN(ArrayA[i++], ArrayB[j++]); 

बेशक, ऐसे मामलों के लिए, वर्कअराउंड का लंबे समय तक आविष्कार किया गया है और मैक्रो को सुरक्षित रूप से लागू किया जा सकता है:

 #define MAX(a,b) \ ({ __typeof__ (a) _a = (a); \ __typeof__ (b) _b = (b); \ _a > _b ? _a : _b; }) 

एकमात्र सवाल यह है कि क्या हमें C ++ में यह सब चाहिए? नहीं, C ++ में कुशल कोड बनाने के लिए टेम्प्लेट और अन्य तरीके हैं। तो मैं C ++ कार्यक्रमों में इसी तरह के मैक्रों का सामना क्यों कर रहा हूं?

चौथा: डिबगिंग जटिल है


एक राय है कि डिबगिंग wimps :) के लिए है। यह, ज़ाहिर है, चर्चा के लिए दिलचस्प है, लेकिन व्यावहारिक दृष्टिकोण से, डीबगिंग उपयोगी है और त्रुटियों को खोजने में मदद करता है। मैक्रोज़ इस प्रक्रिया को जटिल करते हैं और निश्चित रूप से त्रुटियों की खोज को धीमा करते हैं।

पांचवां: स्थैतिक विश्लेषणकर्ताओं की झूठी सकारात्मकता


कई मैक्रोज़, अपने डिवाइस की बारीकियों के कारण, स्टैटिक कोड एनालाइज़र से कई झूठी पॉज़िटिव उत्पन्न करते हैं। मैं सुरक्षित रूप से कह सकता हूं कि सी और सी ++ कोड की जाँच करते समय अधिकांश गलत सकारात्मक मैक्रोज़ से जुड़े होते हैं।

मैक्रोज़ के साथ परेशानी यह है कि विश्लेषक केवल सही कोड को गलत कोड से अलग नहीं कर सकते हैं। क्रोमियम की जाँच के बारे में लेख इन मैक्रोज़ में से एक का वर्णन करता है।

क्या करें?


चलो सी ++ प्रोग्राम में मैक्रोज़ का उपयोग न करें जब तक कि बिल्कुल आवश्यक न हो!

C ++ समृद्ध उपकरण प्रदान करता है जैसे कि टेम्पलेट फ़ंक्शंस, ऑटोमैटिक टाइप इंफ़ेक्शन (ऑटो, डिक्लेप्ट), कॉन्स्टैक्स फ़ंक्शंस।

लगभग हमेशा, एक मैक्रो के बजाय, आप एक साधारण फ़ंक्शन लिख सकते हैं। अक्सर यह सामान्य आलस्य के कारण नहीं किया जाता है। यह आलस्य हानिकारक है, और हमें इससे लड़ना चाहिए। एक छोटे से अतिरिक्त समय को एक पूर्ण फ़ंक्शन लिखने में खर्च किया जाता है जो ब्याज के साथ भुगतान करेगा। कोड को पढ़ना और बनाए रखना आसान होगा। अपने स्वयं के पैर की शूटिंग की संभावना कम हो जाएगी, और संकलक और स्थिर विश्लेषक कम झूठी सकारात्मकता पैदा करेंगे।

कुछ का तर्क हो सकता है कि फ़ंक्शन के साथ कोड कम कुशल है। यह भी सिर्फ एक "बहाना" है।

कंपाइलर अब पूरी तरह से कोड को इनलाइन करते हैं, भले ही आपने इनलाइन कीवर्ड न लिखा हो।

यदि हम संकलन चरण में अभिव्यक्तियों की गणना करने के बारे में बात कर रहे हैं, तो यहां मैक्रोज़ की आवश्यकता नहीं है और यहां तक ​​कि हानिकारक भी हैं। इसी उद्देश्य के लिए, कॉन्स्टैक्स का उपयोग करना बेहतर और सुरक्षित है।

मैं एक उदाहरण के साथ समझाऊंगा। यहाँ एक क्लासिक मैक्रो त्रुटि है जो मैंने FreeBSD कर्नेल कोड से उधार ली है।

 #define ICB2400_VPOPT_WRITE_SIZE 20 #define ICB2400_VPINFO_PORT_OFF(chan) \ (ICB2400_VPINFO_OFF + \ sizeof (isp_icb_2400_vpinfo_t) + \ (chan * ICB2400_VPOPT_WRITE_SIZE)) // <= static void isp_fibre_init_2400(ispsoftc_t *isp) { .... if (ISP_CAP_VP0(isp)) off += ICB2400_VPINFO_PORT_OFF(chan); else off += ICB2400_VPINFO_PORT_OFF(chan - 1); // <= .... } 

कोष्ठक में लिपटे बिना एक मैक्रो में चान तर्क का उपयोग किया जाता है। नतीजतन, अभिव्यक्ति ICB2400_VPOPT_WRITE_SIZE अभिव्यक्ति (चान - 1) को गुणा नहीं करता है, लेकिन केवल एक।

यदि मैक्रो के बजाय एक साधारण फ़ंक्शन लिखा गया था, तो त्रुटि प्रकट नहीं होगी।

 size_t ICB2400_VPINFO_PORT_OFF(size_t chan) { return ICB2400_VPINFO_OFF + sizeof(isp_icb_2400_vpinfo_t) + chan * ICB2400_VPOPT_WRITE_SIZE; } 

यह बहुत संभावना है कि आधुनिक सी और सी ++ संकलक स्वतंत्र रूप से फ़ंक्शन की इनलाइनिंग करेंगे, और मैक्रो के मामले में कोड उतना ही कुशल होगा।

इसी समय, कोड अधिक पठनीय हो गया है, साथ ही त्रुटियों से भी मुक्त हो गया है।

यदि यह ज्ञात है कि इनपुट मूल्य हमेशा एक स्थिर है, तो आप कॉन्स्टैक्स को जोड़ सकते हैं और सुनिश्चित कर सकते हैं कि सभी गणना संकलन चरण में होगी। कल्पना कीजिए कि यह C ++ है और यह जप हमेशा एक स्थिर है। फिर इस तरह के ICB2400_VPINFO_PORT_OFF फ़ंक्शन को घोषित करना उपयोगी है:

 constexpr size_t ICB2400_VPINFO_PORT_OFF(size_t chan) { return ICB2400_VPINFO_OFF + sizeof(isp_icb_2400_vpinfo_t) + chan * ICB2400_VPOPT_WRITE_SIZE; } 

लाभ!

मुझे उम्मीद है कि मैं आपको समझाने में कामयाब रहा। सौभाग्य और कोड में कम मैक्रो!

Source: https://habr.com/ru/post/hi444612/


All Articles