क्या C लाइब्रेरी के कार्यों को हमेशा एक स्ट्रिंग की लंबाई की अपेक्षा करनी चाहिए?


15

मैं वर्तमान में सी में लिखी एक लाइब्रेरी पर काम कर रहा हूँ। इस लाइब्रेरी के कई कार्यों में char*या const char*उनके तर्कों में एक स्ट्रिंग की उम्मीद है । मैंने उन कार्यों के साथ शुरुआत की, जो हमेशा स्ट्रिंग की लंबाई की अपेक्षा करते हैं size_tताकि शून्य-समाप्ति की आवश्यकता न हो। हालाँकि, जब परीक्षण लिखते हैं, तो इसका लगातार उपयोग होता है strlen(), जैसे:

const char* string = "Ugh, strlen is tedious";
libFunction(string, strlen(string));

उपयोगकर्ता को सही ढंग से समाप्त किए गए स्ट्रिंग्स पास करने के लिए भरोसा करने से कम सुरक्षित, लेकिन अधिक संक्षिप्त और (मेरी राय में) पठनीय कोड प्राप्त होगा:

libFunction("I hope there's a null-terminator there!");

तो, यहाँ समझदार अभ्यास क्या है? API का उपयोग करने के लिए और अधिक जटिल बनाएं, लेकिन उपयोगकर्ता को उनके इनपुट के बारे में सोचने के लिए मजबूर करें, या एक अशक्त-समाप्त स्ट्रिंग के लिए आवश्यकता को दस्तावेज करें और कॉलर पर भरोसा करें?

जवाबों:


4

सबसे निश्चित रूप से और बिल्कुल लंबाई चारों ओर ले । मानक सी लाइब्रेरी इस तरह से बदनाम है, जिससे बफर ओवरफ्लो से निपटने में दर्द का कोई अंत नहीं है। यह दृष्टिकोण इतनी घृणा और पीड़ा का फोकस है कि आधुनिक कंपाइलर वास्तव में इस तरह के मानक पुस्तकालय कार्यों का उपयोग करते समय चेतावनी देंगे, सचेत करेंगे और शिकायत करेंगे।

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

इसकी भावना को छोड़कर, यह बहुत कुछ है जो आपके स्ट्रिंग के अंत में उस NULL के साथ गलत हो सकता है, इसे पढ़ने और हेरफेर करने में - प्लस यह वास्तव में आधुनिक डिजाइन अवधारणाओं जैसे कि रक्षा-में-गहराई में प्रत्यक्ष उल्लंघन है (जरूरी नहीं कि सुरक्षा के लिए लागू हो, लेकिन एपीआई डिजाइन के लिए)। C API के उदाहरण जो लंबाई को कम करते हैं - पूर्व। विंडोज एपीआई।

वास्तव में, इस समस्या को 90 के दशक में किसी समय सुलझा लिया गया था, आज की उभरती आम सहमति यह है कि आपको अपने तार भी नहीं छूने चाहिए

बाद में संपादित करें : यह काफी जीवंत बहस है इसलिए मैं नीचे और ऊपर के सभी लोगों पर भरोसा करूंगा कि आप अच्छे बनें और पुस्तकालय str का उपयोग करें * फ़ंक्शन ठीक है, जब तक कि आप क्लासिक सामान नहीं देखते हैं output = malloc(strlen(input)); strcpy(output, input);या जैसे while(*src) { *dest=transform(*src); dest++; src++; }। मैं पृष्ठभूमि में मोजार्ट के लैक्रिमोसा को लगभग सुन सकता हूं।


1
मैं विंडोज एपीआई के आपके उदाहरण को नहीं समझता कि तार की लंबाई की आपूर्ति करने के लिए कॉलर की आवश्यकता है। उदाहरण के लिए, एक विशिष्ट Win32 API फ़ंक्शन जैसे इनपुट के रूप में CreateFileएक LPTCSTR lpFileNameपैरामीटर लेता है । कॉलर से स्ट्रिंग की कोई लंबाई अपेक्षित नहीं है। वास्तव में, एनयूएल-टर्मिनेटेड स्ट्रिंग्स का उपयोग इतना उलझा हुआ है कि प्रलेखन में यह भी उल्लेख नहीं है कि फाइल का नाम एनयूएल-टर्मिनेटेड होना चाहिए (लेकिन निश्चित रूप से यह होना चाहिए)।
ग्रेग हेवगिल

1
वास्तव में Win32 में, LPSTRप्रकार कहता है कि तार NUL- समाप्त हो सकते हैं, और यदि नहीं , तो संबंधित विनिर्देश में इंगित किया जाएगा। तो जब तक विशेष रूप से अन्यथा इंगित नहीं किया जाता है, तब तक Win32 में इस तरह के तार NUL- समाप्त होने की उम्मीद है।
ग्रेग हेविगिल

महान बिंदु, मैं अभेद्य था। इस पर विचार करें कि CreateFile और उसका गुच्छा Windows NT 3.1 (शुरुआती 90 के दशक) के आसपास का है; वर्तमान एपीआई (यानी XP SP2 में Strsafe.h की शुरुआत के बाद से - माइक्रोसॉफ्ट के सार्वजनिक माफी के साथ) स्पष्ट रूप से सभी पूर्ण-समाप्त सामग्री को हटा सकते हैं। पहली बार जब Microsoft को वास्तव में NULL- टर्मिनेटेड स्ट्रिंग्स का उपयोग करने के लिए वास्तव में खेद महसूस हुआ, वास्तव में बहुत पहले था, जब उन्हें किसी तरह एक ही नाव में VB, COM और पुराने WINAPI लाने के लिए OLE 2.0 विनिर्देश में BSTR को पेश करना था।
vski

1
यहां तक ​​कि StringCbCatउदाहरण के लिए, केवल गंतव्य के पास अधिकतम बफर है, जो समझ में आता है। स्रोत अभी भी एक साधारण NUL-समाप्त सी स्ट्रिंग है। शायद आप इनपुट पैरामीटर और आउटपुट पैरामीटर के बीच अंतर को स्पष्ट करके अपने उत्तर को बेहतर बना सकते हैं । आउटपुट पैरामीटर में हमेशा अधिकतम बफर लंबाई होनी चाहिए; इनपुट पैरामीटर आमतौर पर एनयूएल-टर्मिनेटेड (अपवाद हैं, लेकिन मेरे अनुभव में दुर्लभ हैं)।
ग्रेग हेविगेल

1
हाँ। मंच स्तर पर JVM / Dalvik और .NET CLR दोनों पर स्ट्रिंग्स अपरिवर्तनीय हैं, साथ ही साथ कई अन्य भाषाओं में भी। मैं इतनी दूर जाऊंगा और अनुमान लगाऊंगा कि देशी दुनिया अभी तक ऐसा नहीं कर सकती है (सी ++ 11 मानक) क्योंकि) विरासत (आप वास्तव में इतना नहीं हासिल करते हैं कि आपके तार अपरिवर्तनीय का सिर्फ एक हिस्सा हो) और बी ) इस काम को करने के लिए आपको वास्तव में GC और एक स्ट्रिंग टेबल की आवश्यकता होती है, C ++ 11 में स्कूप किए गए आवंटनकर्ता इसे काट नहीं सकते।
विस्की

16

सी में, मुहावरा यह है कि चरित्र के तार NUL- टर्मिनेटेड हैं, इसलिए यह सामान्य व्यवहार का पालन करने के लिए समझ में आता है - यह वास्तव में अपेक्षाकृत संभावना नहीं है कि लाइब्रेरी के उपयोगकर्ताओं के पास गैर-एनयूएल-टर्मिनेटेड स्ट्रिंग्स होंगे (चूंकि इन्हें प्रिंट करने के लिए अतिरिक्त काम की आवश्यकता होती है। प्रिंटफ का उपयोग करना और अन्य संदर्भ में उपयोग करना)। किसी अन्य प्रकार के स्ट्रिंग का उपयोग करना अप्राकृतिक है और शायद अपेक्षाकृत दुर्लभ है।

इसके अलावा, परिस्थितियों में, आपका परीक्षण मुझे थोड़ा अजीब लग रहा है, क्योंकि सही तरीके से काम करने के लिए (स्ट्रलेन का उपयोग करके), आप पहली जगह में एनयूएल-टर्मिनेटेड स्ट्रिंग मान रहे हैं। यदि आप उनके साथ काम करने के लिए अपने पुस्तकालय का इरादा रखते हैं, तो आपको गैर-एनयूएल-समाप्त स्ट्रिंग्स के मामले का परीक्षण करना चाहिए।


-1, मुझे खेद है, यह केवल बीमार है।
विस्की

पुराने दिनों में, यह हमेशा सच नहीं था। मैंने बाइनरी प्रोटोकॉल के साथ बहुत काम किया है जो निश्चित लंबाई वाले क्षेत्रों में स्ट्रिंग डेटा डालते हैं जो NULL समाप्त नहीं थे। ऐसे मामलों में, उन कार्यों के साथ काम करना बहुत आसान था जो एक लंबाई लेते थे। मैंने एक दशक में सी नहीं किया है, हालांकि।
रोबोट

4
@vski, लक्ष्य फ़ंक्शन को कॉल करने से पहले उपयोगकर्ता को 'strlen' कॉल करने के लिए कैसे मजबूर किया जाता है बफर अतिप्रवाह समस्याओं से बचने के लिए कुछ भी करें? कम से कम यदि आप लक्ष्य फ़ंक्शन के भीतर खुद की लंबाई की जांच करते हैं, तो आप आश्वस्त हो सकते हैं कि किस अर्थ में लंबाई का उपयोग किया जा रहा है (टर्मिनल नल या नहीं सहित)।
चार्ल्स ई। ग्रांट

@Charles E. Grant: StringCbCat और StringCbCatN के बारे में उपरोक्त टिप्पणी देखें। यदि आपके पास बस एक चार * और कोई लंबाई नहीं है, तो वास्तव में आपके पास str * फ़ंक्शन का उपयोग करने के अलावा कोई वास्तविक विकल्प नहीं है, लेकिन बिंदु लंबाई-चारों ओर ले जाने का है, इस प्रकार यह str * और strn * के बीच एक विकल्प बन जाता है जिन कार्यों को बाद में पसंद किया जाता है।
विस्की

2
@vski एक स्ट्रिंग की लंबाई के आसपास से गुजरने की कोई जरूरत नहीं है । वहाँ है एक के आसपास पारित करने के लिए एक की जरूरत बफर की लंबाई। सभी बफ़र्स स्ट्रिंग्स नहीं हैं, और सभी स्ट्रिंग्स बफ़र्स नहीं हैं।
jamesdlin

10

आपका "सुरक्षा" तर्क वास्तव में पकड़ में नहीं आता है। यदि आप उस उपयोगकर्ता पर भरोसा नहीं करते हैं जो आपको एक शून्य-समाप्त स्ट्रिंग सौंपने के लिए है, जब आपने दस्तावेज (और सादे सी के लिए "आदर्श" क्या है), तो आप वास्तव में उस लंबाई पर भरोसा नहीं कर सकते जो वे आपको देते हैं (जो वे करेंगे संभवत: strlenजैसे आप कर रहे हैं वैसे ही उपयोग करके प्राप्त करें यदि उनके पास यह काम नहीं है, और जो विफल हो जाएगा यदि "स्ट्रिंग" पहली जगह में एक स्ट्रिंग नहीं थी)।

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

संभवतः इसके लिए एक प्रदर्शन पहलू भी है। यदि आपके फ़ंक्शन को पहले से स्ट्रिंग की लंबाई जानने की जरूरत है, और आप अपने उपयोगकर्ताओं से कम से कम आमतौर पर पहले से ही उस जानकारी को जानने की उम्मीद करते हैं, तो उन्हें इसे पास करने (बल्कि आप इसकी गणना करने के बजाय) कुछ चक्र दाढ़ी कर सकते हैं।

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


इस दृष्टिकोण से दृढ़ता से असहमत हैं। कभी भी अपने कॉलर्स पर विश्वास न करें, विशेष रूप से एक लाइब्रेरी एपीआई के पीछे, आपके द्वारा दिए गए सामान पर सवाल उठाने और अपनी कृपा से असफल होने का अपना सर्वश्रेष्ठ प्रयास करें। रफ़ू की गई लंबाई को ले जाएं, NULL- टर्मिनेटेड स्ट्रिंग्स के साथ काम करना ऐसा नहीं है जो "आपके कॉलर्स के साथ ढीला हो और आपके कैलेज़ के साथ सख्त" हो।
vski

2
मैं ज्यादातर आपकी स्थिति से सहमत हूं , लेकिन आप उस लंबाई के तर्क में बहुत अधिक विश्वास रखते हैं - ऐसा कोई कारण नहीं है कि यह शून्य टर्मिनेटर की तुलना में विश्वसनीय होना चाहिए। मेरी स्थिति यह है कि यह पुस्तकालय क्या करता है पर निर्भर करता है।
Mat

बहुत अधिक है जो मूल्य द्वारा पारित लंबाई की तुलना में स्ट्रिंग्स में NULL टर्मिनेटर के साथ गलत हो सकता है। सी में, एकमात्र कारण लंबाई पर भरोसा करेगा क्योंकि यह अनुचित और अव्यवहारिक होगा - बफर लंबाई को ले जाना एक अच्छा जवाब नहीं है, बस विकल्पों पर विचार करना सबसे अच्छा है। यह एक कारण है कि स्ट्रिंग्स (और सामान्य रूप से बफ़र्स) को रेड भाषाओं में बड़े करीने से पैक किया और समझाया जाता है।
विस्की

2

स्ट्रिंग्स हमेशा परिभाषा से शून्य-समाप्त होती है, स्ट्रिंग की लंबाई बेमानी है।

गैर-शून्य-वर्ण वाले वर्ण डेटा को कभी भी "स्ट्रिंग" नहीं कहा जाना चाहिए। इसे संसाधित करना (और चारों ओर लंबाई फेंकना) आमतौर पर एक पुस्तकालय के भीतर समझाया जाना चाहिए , और एपीआई का हिस्सा नहीं होना चाहिए । एकल स्ट्रेंलेन से बचने के लिए एक पैरामीटर के रूप में लंबाई की आवश्यकता () कॉल की संभावना है समयपूर्व अनुकूलन।

API फ़ंक्शन के कॉलर पर भरोसा करना असुरक्षित नहीं है ; अपरिभाषित व्यवहार पूरी तरह से ठीक है अगर दस्तावेजित पूर्व शर्त पूरी नहीं की जाती है।

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


न केवल पूरी तरह से ठीक है, लेकिन वास्तव में अपरिहार्य है जब तक कि कोई एक स्मृति-सुरक्षित, एकल-थ्रेडेड भाषा में नहीं चलता। हो सकता है कुछ और नेकसी प्रतिबंध हटा दिए गए हों ...
डेडुप्लिकेटर

1

आपको हमेशा अपनी लंबाई के आसपास रखना चाहिए। एक के लिए, आपके उपयोगकर्ता उनमें NULLs शामिल करना चाह सकते हैं। और दूसरी बात, यह मत भूलो कि strlenहे (एन) है और पूरे स्ट्रिंग-बाय-बाय कैश को छूने की आवश्यकता है। और तीसरा, यह सबसे आसान है कि वह सबसे उप-भाग के आसपास से गुजरता है- उदाहरण के लिए, वे वास्तविक लंबाई से कम दे सकते हैं।


4
क्या लाइब्रेरी फ़ंक्शन स्ट्रिंग्स में एम्बेडेड NULL से संबंधित है, बहुत अच्छी तरह से प्रलेखित होने की आवश्यकता है। अधिकांश C लाइब्रेरी फ़ंक्शंस NULL या लंबाई पर रोकते हैं, जो भी पहले हो। (और अगर सक्षम रूप से लिखा जाए, तो जो लंबाई नहीं लेते हैं, वे strlenलूप टेस्ट में कभी भी उपयोग नहीं करते हैं ।)
रोबोट

1

आपको एक स्ट्रिंग के चारों ओर घूमने और एक बफर के चारों ओर गुजरने के बीच अंतर करना चाहिए ।

सी में, तार पारंपरिक रूप से एनयूएल-टर्मिनेटेड हैं। यह उम्मीद करना पूरी तरह से उचित है। इसलिए आमतौर पर स्ट्रिंग की लंबाई के आसपास से गुजरने की कोई आवश्यकता नहीं है; strlenयदि आवश्यक हो तो इसके साथ गणना की जा सकती है।

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

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

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


यह वही है जो सवाल और अन्य उत्तर याद किया।
ब्लरफ्ल

0

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

void use_string(char *string, int length);

एक मैक्रो को परिभाषित कर सकता है:

#define use_strlit(x) use_string(x, sizeof ("" x "")-1)

और फिर इसे दिखाए अनुसार:

void test(void)
{
  use_strlit("Hello");
}

हालांकि "मैक्रो" पास करने के लिए "रचनात्मक" चीजों के साथ आना संभव हो सकता है जो कि संकलित करेगा लेकिन वास्तव में काम नहीं करेगा, """साइज़ोफ़" के मूल्यांकन के भीतर स्ट्रिंग के दोनों ओर का उपयोग चरित्र का उपयोग करने के आकस्मिक प्रयासों को पकड़ना चाहिए विघटित स्ट्रिंग शाब्दिक के अलावा बिंदु [ "", उन की अनुपस्थिति में , एक चरित्र सूचक को पारित करने का प्रयास गलती से एक सूचक के आकार के रूप में लंबाई देगा, शून्य से एक।

C99 में एक वैकल्पिक दृष्टिकोण "सूचक और लंबाई" संरचना प्रकार को परिभाषित करने और एक मैक्रो को परिभाषित करने के लिए होगा जो एक स्ट्रिंग शाब्दिक को उस संरचना प्रकार के यौगिक शाब्दिक में परिवर्तित करता है। उदाहरण के लिए:

struct lstring { char const *ptr; int length; };
#define as_lstring(x) \
  (( struct lstring const) {x, sizeof("" x "")-1})

ध्यान दें कि यदि कोई इस तरह के दृष्टिकोण का उपयोग करता है, तो किसी को अपने पते के आसपास से गुजरने के बजाय मूल्य द्वारा ऐसी संरचनाओं को पारित करना चाहिए। अन्यथा कुछ इस तरह:

struct lstring *p;
if (foo)
{
  p = &as_lstring("Hello");
}
else
{
  p = &as_lstring("Goodbye!");
}
use_lstring(p);

कंपाउंड के जीवनकाल के बाद से विफल हो सकता है क्योंकि उनके संलग्न बयानों के अंत में समाप्त हो जाएगा।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.