c++ - Size_t क्यों हस्ताक्षरित है?




unsigned-integer size-t (3)

इंडेक्स प्रकारों को बिना हस्ताक्षर किए जाने का एक कारण सी और सी ++ की अर्ध-खुले अंतराल के लिए प्राथमिकता के साथ समरूपता के लिए है। और यदि आपके इंडेक्स प्रकारों को हस्ताक्षरित किया जा रहा है, तो आपके आकार के प्रकार को हस्ताक्षरित करना भी सुविधाजनक है।

सी में, आपके पास एक पॉइंटर हो सकता है जो एक सरणी में इंगित करता है। एक वैध सूचक सरणी के किसी भी तत्व या सरणी के अंत से पहले एक तत्व को इंगित कर सकता है। यह सरणी की शुरुआत से पहले एक तत्व को इंगित नहीं कर सकता है।

int a[2] = { 0, 1 };
int * p = a;  // OK
++p;  // OK, points to the second element
++p;  // Still OK, but you cannot dereference this one.
++p;  // Nope, now you've gone too far.
p = a;
--p;  // oops!  not allowed

सी ++ सहमत है और इस विचार को इटरेटर को बढ़ाता है।

हस्ताक्षरित इंडेक्स प्रकारों के खिलाफ तर्क अक्सर एक सरणी को पीछे से आगे की ओर जाने का एक उदाहरण बताते हैं, और कोड अक्सर इस तरह दिखता है:

// WARNING:  Possibly dangerous code.
int a[size] = ...;
for (index_type i = size - 1; i >= 0; --i) { ... }

यह कोड केवल तभी काम करता है जब index_type हस्ताक्षरित है, जिसका प्रयोग तर्क के रूप में किया जाता है कि इंडेक्स प्रकारों पर हस्ताक्षर किए जाने चाहिए (और, विस्तार से, आकारों पर हस्ताक्षर किए जाने चाहिए)।

वह तर्क अप्रचलित है क्योंकि वह कोड गैर-मूर्खतापूर्ण है। देखें कि क्या होता है यदि हम सूचकांक के बजाय पॉइंटर्स के साथ इस लूप को फिर से लिखने का प्रयास करते हैं:

// WARNING:  Bad code.
int a[size] = ...;
for (int * p = a + size - 1; p >= a; --p) { ... }

हां, अब हमारे पास अपरिभाषित व्यवहार है! size 0 होने पर समस्या को अनदेखा करते हुए, हमें पुनरावृत्ति के अंत में एक समस्या है क्योंकि हम एक अमान्य सूचक उत्पन्न करते हैं जो पहले तत्व से पहले इंगित करता है। यह अनिर्धारित व्यवहार है भले ही हम कभी भी सूचक को अस्वीकार करने का प्रयास न करें।

तो आप भाषा मानक को बदलकर इसे ठीक करने के लिए बहस कर सकते हैं ताकि वह पॉइंटर प्राप्त कर सके जो पहले से पहले तत्व को इंगित करता है, लेकिन ऐसा होने की संभावना नहीं है। आधे खुले अंतराल इन भाषाओं का एक मौलिक बिल्डिंग ब्लॉक है, इसलिए चलिए इसके बजाय बेहतर कोड लिखते हैं।

एक सही सूचक आधारित समाधान है:

int a[size] = ...;
for (int * p = a + size; p != a; ) {
  --p;
  ...
}

बहुत से लोग इस परेशानियों को पाते हैं क्योंकि अब हेडर में लूप के शरीर में कमी आ रही है, लेकिन ऐसा तब होता है जब आपका फॉर-सिंटैक्स मुख्य रूप से आधे खुले अंतराल के माध्यम से आगे की लूप के लिए डिज़ाइन किया गया है। (रिवर्स इटरेटर्स इस कमी को स्थगित करके इस असममितता को हल करते हैं।)

अब, समानता के अनुसार, सूचकांक आधारित समाधान बन जाता है:

int a[size] = ...;
for (index_type i = size; i != 0; ) {
  --i;
  ...
}

यह काम करता है कि index_type हस्ताक्षरित या हस्ताक्षरित है, लेकिन हस्ताक्षरित विकल्प कोड उत्पन्न करता है जो idiomatic सूचक और इटरेटर संस्करणों पर अधिक सीधे नक्शा करता है। बिना हस्ताक्षर किए जाने का अर्थ यह भी है कि, पॉइंटर्स और इटरेटर्स के साथ, हम अनुक्रम के प्रत्येक तत्व तक पहुंचने में सक्षम होंगे - हम गैरकानूनी मूल्यों का प्रतिनिधित्व करने के लिए हमारी संभावित सीमा का आधा आत्मसमर्पण नहीं करते हैं। हालांकि यह 64-बिट दुनिया में व्यावहारिक चिंता नहीं है, यह 16-बिट एम्बेडेड प्रोसेसर में एक बहुत ही वास्तविक चिंता हो सकती है या एक विशाल श्रेणी में स्पैस डेटा के लिए एक अमूर्त कंटेनर प्रकार बनाने में यह एक समान एपीआई प्रदान कर सकता है देशी कंटेनर।

Bjarne Stroustrup ने सी ++ प्रोग्रामिंग भाषा में लिखा था:

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

size_t को "पूर्णांक पूर्णांक का प्रतिनिधित्व करने के लिए एक और बिट प्राप्त करने के लिए" हस्ताक्षर नहीं किया जाता है। तो क्या यह एक गलती थी (या व्यापार बंद), और यदि हां, तो क्या हमें इसे अपने कोड में कम से कम उपयोग करना चाहिए?

स्कॉट मेयर्स द्वारा एक और प्रासंगिक लेख here । संक्षेप में, वह इंटरफेस में हस्ताक्षर किए बिना उपयोग न करने की सिफारिश करता है, इस पर ध्यान दिए बिना कि मूल्य हमेशा सकारात्मक है या नहीं। दूसरे शब्दों में, भले ही नकारात्मक मानों का कोई अर्थ न हो, आपको अनिवार्य रूप से हस्ताक्षर नहीं करना चाहिए।


दूसरी ओर ...

मिथक 1 : std::size_t हस्ताक्षरित है विरासत प्रतिबंधों के कारण है जो अब लागू नहीं होता है।

आमतौर पर यहां संदर्भित दो "ऐतिहासिक" कारण हैं:

  1. sizeof रिटर्न std::size_t , जिसे सी के दिनों से हस्ताक्षरित किया गया है।
  2. प्रोसेसर के छोटे शब्द के आकार होते थे, इसलिए उस अतिरिक्त बिट को बाहर निकालना महत्वपूर्ण था।

लेकिन इन कारणों में से कोई भी, बहुत पुराना होने के बावजूद, वास्तव में इतिहास में नहीं चलाया जाता है।

sizeof अभी भी एक std::size_t देता है जो अभी भी हस्ताक्षरित है। यदि आप sizeof या मानक लाइब्रेरी कंटेनर के साथ अंतःक्रिया करना चाहते हैं, तो आपको std::size_t का उपयोग करना होगा।

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

और जबकि अधिकांश मुख्यधारा कंप्यूटिंग 32- और 64-बिट प्रोसेसर पर किया जाता है, तब भी सी ++ अभी भी एम्बेडेड सिस्टम में 16-बिट माइक्रोप्रोसेसरों पर उपयोग किया जाता है। उन माइक्रोप्रोसेसरों पर, अक्सर शब्द-आकार का मान होना बहुत उपयोगी होता है जो आपकी मेमोरी स्पेस में किसी भी मूल्य का प्रतिनिधित्व कर सकता है।

हमारे नए कोड को अभी भी मानक पुस्तकालय के साथ अंतःक्रिया करना है। यदि हमारे नए कोड ने हस्ताक्षरित प्रकारों का उपयोग किया है, जबकि मानक लाइब्रेरी हस्ताक्षरित लोगों का उपयोग जारी रखती है, तो हम दोनों उपभोक्ताओं के लिए इसे कठिन बनाते हैं जिन्हें दोनों का उपयोग करना पड़ता है।

मिथक 2 : आपको उस अतिरिक्त बिट की आवश्यकता नहीं है। (एकेए, जब आपकी पता स्थान केवल 4 जीबी है तो आपके पास 2 जीबी से अधिक स्ट्रिंग नहीं होगी।)

आकार और अनुक्रमणिका केवल स्मृति के लिए नहीं हैं। आपका पता स्थान सीमित हो सकता है, लेकिन आप उन फ़ाइलों को संसाधित कर सकते हैं जो आपके पता स्थान से कहीं अधिक बड़े हैं। और जब आपके पास 2 जीबी के साथ स्ट्रिंग नहीं हो सकती है, तो आप आसानी से 2 जीबीट्स के साथ एक बिटसेट कर सकते हैं। और स्पैस डेटा के लिए डिज़ाइन किए गए वर्चुअल कंटेनर को न भूलें।

मिथक 3 : आप हमेशा एक व्यापक हस्ताक्षर प्रकार का उपयोग कर सकते हैं।

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

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

मिथक 4 : अज्ञात अंकगणित आश्चर्यजनक और अप्राकृतिक है।

इसका तात्पर्य है कि हस्ताक्षर अंकगणित आश्चर्यजनक नहीं है या किसी भी तरह से अधिक प्राकृतिक नहीं है। और, शायद यह तब होता है जब गणित के मामले में सोचते हैं जहां सभी मूल अंकगणितीय संचालन सभी पूर्णांक के सेट पर बंद होते हैं।

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

यह बग है:

auto mid = (min + max) / 2;  // BUGGY

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

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

वास्तविक हस्ताक्षर किए गए आश्चर्य घटाव के साथ आता है: यदि आप एक छोटे से हस्ताक्षर किए गए int को छोटे से घटाते हैं, तो आप एक बड़ी संख्या के साथ समाप्त होने जा रहे हैं। यदि आप 0 से विभाजित हैं तो यह परिणाम और आश्चर्यजनक नहीं है।

यहां तक ​​कि यदि आप अपने सभी एपीआई से हस्ताक्षरित प्रकारों को खत्म कर सकते हैं, तो भी आपको मानक कंटेनर या फ़ाइल प्रारूप या वायर प्रोटोकॉल से निपटने के लिए इन हस्ताक्षरित "आश्चर्य" के लिए तैयार रहना होगा। क्या यह वास्तव में समस्या का केवल एक हिस्सा "हल" करने के लिए आपके एपीआई में घर्षण जोड़ने लायक है?


size_t ऐतिहासिक कारणों से हस्ताक्षरित है।

16 बिट पॉइंटर्स के साथ एक आर्किटेक्चर पर, जैसे "छोटे" मॉडल डॉस प्रोग्रामिंग, स्ट्रिंग को 32 केबी तक सीमित करने के लिए अव्यवहारिक होगा।

इस कारण से, सी मानक को प्रभावी रूप से 17 बिट्स होने के लिए, आवश्यक सीमाओं के माध्यम से ptrdiff_t , ptrdiff_t के हस्ताक्षरित समकक्ष और पॉइंटर अंतर का परिणाम प्रकार आवश्यक है।

उन कारणों को अभी भी एम्बेडेड प्रोग्रामिंग दुनिया के कुछ हिस्सों में लागू कर सकते हैं।

हालांकि, वे आधुनिक 32-बिट या 64-बिट प्रोग्रामिंग पर लागू नहीं होते हैं, जहां एक और अधिक महत्वपूर्ण विचार यह है कि सी और सी ++ के दुर्भाग्यपूर्ण अंतर्निहित रूपांतरण नियमों में बग आकर्षित करने वालों में हस्ताक्षर किए गए प्रकार होते हैं, जब इन्हें संख्याओं के लिए उपयोग किया जाता है (और इसलिए, अंकगणितीय परिचालन और परिमाण तुलना)। 20-20 हिंडसाइट के साथ अब हम देख सकते हैं कि उन विशेष रूपांतरण नियमों को अपनाने का निर्णय, जहां string( "Hi" ).length() < -3 व्यावहारिक रूप से गारंटीकृत है, बल्कि मूर्ख और अव्यवहारिक था। हालांकि, उस निर्णय का मतलब है कि आधुनिक प्रोग्रामिंग में, संख्याओं के लिए हस्ताक्षरित प्रकारों को अपनाने के लिए गंभीर नुकसान होते हैं और कोई लाभ नहीं होता है - उन लोगों की भावनाओं को संतुष्ट करने के अलावा जो स्वयं को वर्णनात्मक प्रकार का नाम नहीं मानते हैं, और typedef int MyType बारे में सोचने में विफल रहते हैं।

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





size-t