2008-10-21 17 views
25

मेरे पास कोड का यह टुकड़ा है (संक्षेप में) ...संदर्भ पैरामीटर के साथ varargs का उपयोग कर gotchas हैं

AnsiString working(AnsiString format,...) 
{ 
    va_list argptr; 
    AnsiString buff; 

    va_start(argptr, format); 
    buff.vprintf(format.c_str(), argptr); 

    va_end(argptr); 
    return buff; 
} 

और, संदर्भ के अनुसार पास होने के आधार पर जहां संभव हो, मैंने इसे बदल दिया।

AnsiString broken(const AnsiString &format,...) 
{ 
... the rest, totally identical ... 
} 

मेरा कॉलिंग कोड इस तरह है: -

AnsiString s1, s2; 
    s1 = working("Hello %s", "World"); 
    s2 = broken("Hello %s", "World"); 

लेकिन, एस 1 में "हैलो वर्ल्ड" है, जबकि एस 2 में "हैलो (शून्य)" है। मुझे लगता है कि यह va_start काम करता है, लेकिन मुझे बिल्कुल यकीन नहीं है कि क्या हो रहा है।

उत्तर

38

आप क्या va_start के लिए बाहर फैलता को देखें, तो आप क्या हो रहा है देखेंगे:

va_start(argptr, format); 

(मोटे तौर पर) हो जाता है

argptr = (va_list) (&format+1); 

हैं प्रारूप एक मूल्य की तरह है, यह रख दिया जाता है सभी विविध तर्कों से पहले ढेर पर। यदि प्रारूप एक संदर्भ प्रकार है, तो केवल स्टैक पर पता लगाया जाता है। जब आप संदर्भ चर का पता लेते हैं, तो आपको पता या मूल चर (ब्रोकन कॉल करने से पहले बनाए गए अस्थायी AnsiString के मामले में) मिलता है, तर्क का पता नहीं।

आप पूर्ण वर्गों के आसपास पारित करने के लिए नहीं करना चाहते हैं, तो अपने विकल्पों या तो सूचक द्वारा पारित करने के लिए कर रहे हैं, या एक डमी तर्क में डाल:

AnsiString working_ptr(const AnsiString *format,...) 
{ 
    ASSERT(format != NULL); 
    va_list argptr; 
    AnsiString buff; 

    va_start(argptr, format); 
    buff.vprintf(format->c_str(), argptr); 

    va_end(argptr); 
    return buff; 
} 

... 

AnsiString format = "Hello %s"; 
s1 = working_ptr(&format, "World"); 

या

AnsiString working_dummy(const AnsiString &format, int dummy, ...) 
{ 
    va_list argptr; 
    AnsiString buff; 

    va_start(argptr, dummy); 
    buff.vprintf(format.c_str(), argptr); 

    va_end(argptr); 
    return buff; 
} 

... 

s1 = working_dummy("Hello %s", 0, "World"); 
+2

क्या हो रहा है की अच्छी व्याख्या। –

+6

यह प्रोवर्बियल लीकी अबास्ट्रक्शन का एक अच्छा उदाहरण है। va_start कुछ प्रकार के जादू की तरह लगता है, लेकिन वास्तव में यह संकलक-अनुमोदित हैकर का थोड़ा सा है। – Eclipse

4

एक अच्छा विश्लेषण कि आपको यह नहीं करना चाहती में N0695

2

सी के अनुसार ++ कोडिंग मानक (सूटर, Alexandrescu) पाया जाता है:

varargs सी के साथ ++ प्रयोग नहीं करना चाहिए:

वे सुरक्षित प्रकार नहीं हैं और कक्षा प्रकार की वस्तुओं के लिए अंतर्निहित व्यवहार है, जो आपकी समस्या का कारण बन सकता है।

+0

इस मामले में, यह वर्ग प्रकार मुद्दा नहीं है, के बाद से वहाँ va_list में केवल चार * प्रकार हैं। यह तथ्य है कि सूची से पहले तर्क एक संदर्भ है। – Eclipse

+1

मैक्रो विस्तार के बारे में, मैं आपके उत्तर से सहमत हूं, यह एक अच्छा बिंदु है और शायद इस मामले में वास्तविक समस्या है। हालांकि, यह अभी भी आपके सी ++ कोड में इसे मिश्रण करने का एक बुरा विचार है, क्योंकि किसी उपयोगकर्ता को रोकने की कोशिश करने के लिए कुछ भी नहीं है ( – lefticus

+0

Yup में, उपयोगकर्ता को रोकने के लिए कुछ भी नहीं है, सी + में विविधता टेम्पलेट की प्रतीक्षा नहीं कर सकता + 0x! – Eclipse

14

यहाँ क्या हो रहा है सी ++ मानक (18.7 - अन्य रनटाइम समर्थन) के बारे में va_start() (जोर मेरा):

प्रतिबंध आईएसओ सी पर va_start() मैक्रो में शीर्ष पैरामीटर <stdarg.h> इस अंतर्राष्ट्रीय मानक में भिन्न हैं। पैरामीटर parmN समारोह परिभाषा के चर पैरामीटर सूची में दायीं पैरामीटर के पहचानकर्ता (सिर्फ ... से पहले एक) है। पैरामीटर parmN एक समारोह, सरणी के साथ घोषित किया जाता है, या संदर्भ प्रकार, या एक प्रकार से उस प्रकार कि परिणाम जब एक तर्क जो के लिए कोई पैरामीटर है गुजर के साथ संगत नहीं है, तो व्यवहार अपरिभाषित है।

जैसा कि अन्य लोगों ने उल्लेख किया है, सी ++ में varargs का उपयोग करना खतरनाक है यदि आप इसे गैर-सीधी-सी वस्तुओं (और संभवतः अन्य तरीकों से भी) के साथ उपयोग करते हैं।

कहा - मैं अभी भी printf() हर समय का उपयोग करें ...

+0

तो क्या होगा जब varargs तर्क पैरामीटर सूची में पहला है? –

+0

@Angelorf: यदि परिवर्तनीय तर्क सूची के बाईं ओर कोई औपचारिक पैरामीटर नहीं हैं, तो परिवर्तनीय तर्क पहुंच योग्य नहीं हैं। यह कुछ अपवादों के साथ आम तौर पर उपयोगी नहीं है। ऐसा एक अपवाद यह है कि यदि आपको फ़ंक्शन हस्ताक्षर की आवश्यकता है जो किसी भी तर्क से मेल खाता है। इसका उपयोग आम तौर पर टेम्पलेट मेटा-प्रोग्रामिंग में कैच-ऑल कार्यान्वयन के रूप में किया जाता है (उदाहरण के लिए जेनेरिक फ़ंक्शन पॉइंटर प्रकार से मिलान करते समय)। – IInspectable

0

साइड नोट:

वर्ग प्रकार के लिए व्यवहार varargs के रूप में तर्क अपरिभाषित किया जा सकता है, लेकिन यह मेरे अनुभव में संगत है। कंपाइलर वर्ग की स्मृति के आकार (वर्ग) को ढेर पर धक्का देता है। यानी, छद्म कोड में:

alloca(sizeof(class)); 
memcpy(stack, &instance, sizeof(class); 

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

0

यहाँ मेरी आसान वैकल्पिक हल (विजुअल C++ के साथ 2010 संकलित) है:

void not_broken(const string& format,...) 
{ 
    va_list argptr; 
    _asm { 
    lea eax, [format]; 
    add eax, 4; 
    mov [argptr], eax; 
    } 

    vprintf(format.c_str(), argptr); 
} 
संबंधित मुद्दे