- C भाषा के नियम pointer comparison, aliasing, null pointer, और uninitialized values जैसी दिखने में सरल code को भी undefined behavior बना सकते हैं
- integer constants,
sizeof, character constants, और uint8_t arithmetic में type selection और integer promotion की वजह से platform, notation, और intermediate assignment की स्थिति के अनुसार नतीजे बदल सकते हैं
- function declarations में
foo() और foo(void), prototype का न होना, default argument promotions, और बिना return value वाले functions की C और C++ में वैधता या behavior अलग हो सकता है
- array, pointer नहीं है; array parameters को pointer में adjust किया जाता है, और
a, &a, &a[0] का address एक ही दिखे तब भी type अलग होने की वजह से इन्हें एक-दूसरे के स्थान पर इस्तेमाल नहीं किया जा सकता
- operator precedence और evaluation order अलग चीजें हैं, और
switch body की संरचना से लेकर temporary object की lifetime तक, standard की wording वास्तविक execution result को प्रभावित करती है
undefined behavior और pointer नियम
-
pointer comparison और strict aliasing नियम
- एक ही type के pointers
p और q अगर एक ही address को point करें, तब भी यदि वे अलग objects से निकले हों और किसी एक ही aggregate या union object के हिस्से न हों, तो p == q comparison undefined behavior हो सकता है
- pointer सिर्फ संख्यात्मक address से अधिक abstract चीज है; यह बात संबंधित लेख में आगे समझाई गई है
int object को short lvalue के रूप में access करने पर strict aliasing नियम के अनुसार undefined behavior होता है
unsigned char pointer अपवाद के रूप में किसी भी object को alias कर सकता है, इसलिए int object को unsigned char lvalue से access करना वैध है
unsigned char के लिए यह गारंटी है कि उसमें padding bits और trap representation नहीं होते, और C11 से signed char के लिए भी padding bits न होने की गारंटी है
- type-based alias analysis पर संबंधित लेख में चर्चा की गई है
-
null pointer और pointer representation
- null pointer का bit representation जरूरी नहीं कि सभी bits 0 ही हो
- C standard null pointer constant को परिभाषित करता है, लेकिन runtime पर null pointer representation या सामान्य pointer representation को परिभाषित नहीं करता
- Symbolics Lisp Machine 3600 संख्यात्मक pointer की जगह
<array-object, index> रूप का tuple इस्तेमाल करती है, और इसका null pointer representation <nil, 0> है
- अतिरिक्त उदाहरण clc FAQ 5.17 में हैं
- constant
0 context के अनुसार integer या null pointer बन सकता है, और (void *)0 null pointer के रूप में evaluate होता है
- सिर्फ इस वजह से कि expression
e का मान 0 है, यह गारंटी नहीं होती कि (void *)e null pointer होगा
- केवल null pointer constant को pointer type में convert करने पर ही उसके null pointer के बराबर होने की गारंटी है
- null pointer पर arithmetic undefined behavior है, इसलिए
e null pointer हो तब भी e + 0 के null pointer होने की गारंटी नहीं है
-
uninitialized values
- automatic storage duration वाले uninitialized object को पढ़ते समय, अगर वह object
register storage class का हो सकता है और उसका address कभी लिया न गया हो, तो C11 § 6.3.2.1 ¶ 2 के अनुसार यह undefined behavior है
- यह नियम DR338 में चर्चित Intel Itanium architecture से जुड़ा है
- Itanium के सामान्य integer registers में 64 bits और एक trap bit होता है; यह trap bit
NaT (not-a-thing) बताता है कि register initialized है या नहीं
- variable का address लेने पर यह शर्त हट जाती है, लेकिन value फिर भी indeterminate रहती है और वह trap representation या unspecified value हो सकती है
- trap representation को पढ़ना C11 § 6.2.6.1 ¶ 5 के अनुसार undefined behavior है
- यदि value unspecified हो, तो
x != x का परिणाम true या false दोनों हो सकता है, और अगर int x unspecified है, तो x *= 0 के बाद भी x के 0 होने की गारंटी नहीं है
- indeterminate और unspecified values पर DR260, DR451, N1793, N1818, N2012, N2013, N2221 में चर्चा की गई है
-
unsigned char और memcpy
unsigned char type में C11 § 6.2.6.1 ¶ 3 के अनुसार trap representation नहीं होता, इसलिए इसकी initial value unspecified होती है
- StackOverflow पर C committee member के उत्तर के अनुसार standard library function
memcpy को call करने के बाद x की value specified हो जानी चाहिए, और इस व्याख्या में x != x का परिणाम false माना जाता है
- C standard में इसे समर्थन देने वाला आधार स्पष्ट नहीं है, और DR451 पर committee response कहता है कि indeterminate value पर library function का उपयोग undefined behavior है, जो इस व्याख्या से टकराता है
- यह प्रश्न अब भी खुला है, और आगे की चर्चा Uninitialized Reads में है
पूर्णांक constant, प्रमोशन, sizeof
-
पूर्णांक constant का निरूपण और type
- बिना suffix वाले दशमलव पूर्णांक constant हमेशा signed type की सूची में से चुने जाते हैं, लेकिन octal और hexadecimal constant signed या unsigned type हो सकते हैं
- C17 § 6.4.4.1 के अनुसार पूर्णांक constant का type उस सूची के पहले type से तय होता है जो उस मान को represent कर सके
- जब suffix न हो, तो दशमलव constant के लिए क्रम
int, long int, long long int होता है, और octal/hexadecimal constant के लिए क्रम int, unsigned int, long int, unsigned long int, long long int, unsigned long long int होता है
INT_MAX+1 से UINT_MAX के बीच के constant का type इस बात पर बदल सकता है कि वह दशमलव है या hexadecimal, और इससे ABI-सेंसिटिव code, जैसे variadic function call, में फर्क पड़ सकता है
- Arm 32-bit architecture ABI में
int और long 32-bit के रूप में एक register में pass होते हैं, जबकि long long 64-bit के रूप में दो register में pass होता है
- जिन platform पर
int 32-bit है, वहाँ -1 < 0x8000 का परिणाम true होता है, और जिन platform पर int 16-bit है, वहाँ false, जिससे portability problem हो सकती है
- generic selection, C++ overloaded function, और
sizeof(0x80000000) == sizeof(2147483648) जैसे expression में भी constant type का अंतर परिणाम बदल सकता है
-
sizeof(int) > -1
sizeof operator size_t type का unsigned integer लौटाता है
- C11 § 6.3.1.8 की usual arithmetic conversions के अनुसार, अगर signed operand का rank unsigned operand से कम है, तो वह उसी rank के unsigned type में convert हो जाता है
-1 के बराबर signed integer जब unsigned में convert होता है, तो वह उस rank का अधिकतम unsigned integer बन जाता है
- इसलिए
sizeof(int) > -1 का मूल्यांकन हमेशा false होता है
-
character constant का type
- C में character constant, C11 § 6.4.4.4 ¶ 10 के अनुसार,
int type का होता है
- इसलिए
sizeof(char) == sizeof('x') हमेशा true होगा, इसकी गारंटी नहीं है; सिर्फ sizeof(int) == sizeof('x') की गारंटी है
- integer character constant एक या अधिक multibyte character sequence हो सकता है, इसलिए
'abc' भी वैध है, और उसका representation implementation-defined है
- एकल character वाले integer character constant का मान, उसी एकल character को दर्शाने वाले
char type object के integer representation के बराबर होता है
-
uint8_t arithmetic और division
- भले ही
a, b, c को पढ़ने से पहले initialize किया गया हो, integer promotion और intermediate assignment की स्थिति के कारण x और z के मान अलग हो सकते हैं
- हर variable value पहले
int आकार तक promote होती है, फिर addition और division किए जाते हैं, और हर assignment का परिणाम संबंधित variable type में truncate होकर store होता है
- उदाहरण के लिए
a=255, b=1, c=2 हो, तो x का मान ((255 + 1) / 2) % 256 = 128 होगा
- intermediate variable
y का मान (255 + 1) % 256 = 0 होगा, और उसके बाद z का मान (0 / 2) % 256 = 0 होगा, इसलिए 128 != 0 है
- unsigned integer overflow defined behavior है
- modulo operation addition पर distributive होता है, इसलिए अगर division को addition से बदल दिया जाए, तो
x और z हमेशा समान होंगे
- अगर पहली assignment को
uint8_t x = ((uint8_t)(a + b)) / c; में बदल दिया जाए, तब भी x और z हमेशा समान होंगे
-
const variable और variable length array
const से qualified variables n और m को array size के रूप में इस्तेमाल करने पर भी, ये C में integer constant expression नहीं होते
- C11 § 6.6 ¶ 6 में integer constant expression को integer constant, enumeration constant, character constant, ऐसा
sizeof, _Alignof, cast का तत्काल operand बनने वाला floating constant जिसका परिणाम integer constant हो, आदि तक सीमित किया गया है
- यदि array size expression integer constant expression न हो, तो C11 § 6.7.6.2 ¶ 4 के अनुसार वह variable length array बन जाता है
- variable length array file scope में अनुमति प्राप्त नहीं है, इसलिए global array
x वाला compilation unit compile नहीं होगा
- block scope में variable length array की अनुमति है, इसलिए local array
y वाला compilation unit compile हो सकता है
- variable length array एक conditional feature है जिसे implementation support न भी करे, इसलिए जो compiler इसे support नहीं करते, उनमें block scope वाला उदाहरण भी compile नहीं हो सकता
- C++ में दोनों compilation unit compile हो जाते हैं, और C++ में variable length array की अवधारणा नहीं होने से
y को 42 elements वाले सामान्य array के रूप में compile किया जाता है
फ़ंक्शन घोषणा, रिटर्न वैल्यू, linkage
-
foo() और foo(void)
foo() रूप की फ़ंक्शन घोषणा ऐसे फ़ंक्शन की घोषणा करती है जिसके arguments की संख्या और type अज्ञात हों, जबकि foo(void) बिना arguments वाले nullary function की घोषणा करती है
- यह अंतर फ़ंक्शन declaration·definition·prototype पर लेख में समझाया गया है
- argument सूची के बिना की गई घोषणा केवल फ़ंक्शन का नाम प्रस्तुत करती है और arguments की संख्या व type को परिभाषित नहीं करती, इसलिए बाद की फ़ंक्शन definition के साथ मिलकर वैध हो सकती है
- prototype के बिना फ़ंक्शन call होने पर default argument promotions लागू होते हैं और
float, double में promote हो जाता है
- promotion के बाद का फ़ंक्शन type यदि वास्तविक फ़ंक्शन definition के type के साथ compatible नहीं है, तो declaration और definition का संयोजन वैध नहीं होता
- declaration के बिना फ़ंक्शन call, C में implicit function की वजह से compile हो सकता है, लेकिन C++ में यह compile error है
- declaration के बिना
bar(42) जैसा call करने पर integer argument promotion लागू होता है और 42, int के रूप में व्यक्त होता है, इसलिए यदि bar किसी return type T के लिए T (*)(int) के साथ compatible नहीं है, तो यह undefined behavior बनता है
-
value-returning फ़ंक्शन जो कोई मान return नहीं करता
- यदि किसी फ़ंक्शन का return type
int है और वह कोई मान return नहीं करता, तब भी C में यह वैध हो सकता है, बशर्ते call के result value का उपयोग न किया जाए
- K&R C में
void type नहीं था और type छोड़ देने पर default type int माना जाता था, इसलिए मान return न करने वाले फ़ंक्शन और implicit int नियम का ऐतिहासिक संबंध है
- implicit
int नियम C99 में हटा दिया गया था, और संबंधित चर्चा N661 तथा C99 rationale में है
- C17 § 6.9.1 ¶ 12 के अनुसार यदि execution फ़ंक्शन के अंत के
} तक पहुँच जाए और caller फ़ंक्शन call के मान का उपयोग करे, तो यह undefined behavior है
- C++98 § 6.6.3 ¶ 2 में value-returning फ़ंक्शन के अंत तक बहकर पहुँचना स्वयं बिना मान वाले
return के बराबर है, और value-returning फ़ंक्शन में यह undefined behavior बनता है
- C++ compiler आम तौर पर यह सिद्ध नहीं कर सकते कि किसी branch में
abort_program() execution समाप्त करता है या नहीं, इसलिए ऐसे मामले में वे error की जगह केवल diagnostic दे सकते हैं
-
linkage और extern
- यदि पहले की declaration दिखाई देने वाले scope में उसी identifier को
extern के साथ फिर से declare किया जाए, तो बाद की declaration का linkage, पहले वाली declaration के linkage के समान होगा
- C17 § 6.2.2 ¶ 4 यह निर्धारित करता है कि यदि पहले की declaration ने internal या external linkage निर्दिष्ट किया था, तो बाद की
extern declaration का भी वही linkage होगा
- यदि पहले की declaration दिखाई नहीं देती, या पहले की declaration में linkage नहीं था, तो
extern identifier का external linkage होगा
- उल्टे क्रम में declaration के संयोजन undefined behavior बन सकते हैं, और GCC व Clang इसे पकड़ लेते हैं
qualifiers और incomplete type
-
फ़ंक्शन parameters में const
- यदि फ़ंक्शन declaration में parameter
x, const से qualified है लेकिन फ़ंक्शन definition में नहीं, और फ़ंक्शन body में x को मान लिखा जाता है, तब भी यह वैध है
- C11 § 6.7.6.3 ¶ 15 के अनुसार फ़ंक्शन parameter type compatibility और composite type तय करते समय qualified type के रूप में घोषित प्रत्येक parameter को उसकी unqualified version के रूप में माना जाता है
- यही विषय DR040 में भी लिया गया है
-
फ़ंक्शन return type में const
- यदि केवल फ़ंक्शन definition का return type
const से qualified हो और declaration का नहीं, तो इसका उत्तर सिर्फ़ सही या ग़लत कहना आसान नहीं है
- समग्र सहमति यह रही है कि rvalue के qualifiers को नज़रअंदाज़ किया जाना चाहिए, लेकिन C11 तक के standard text ने इसे स्पष्ट रूप से नहीं संभाला था
- C17 में यह स्पष्ट किया गया कि cast, lvalue conversion और function declarator में rvalue qualifiers को नज़रअंदाज़ किया जाना चाहिए
- C17 § 6.7.6.3 ¶ 5 में स्पष्ट रूप से कहा गया कि फ़ंक्शन द्वारा return किया गया type,
T की unqualified version होता है, और यह wording C17 में जोड़ी गई थी
- return type के
const qualifier अलग होने पर भी फ़ंक्शन type assignment वैध हो सकता है
- अतिरिक्त चर्चा DR423 और DR481 में है
-
incomplete struct और global variable
- global variable declaration के समय यदि
struct foo incomplete type है और उसका size ज्ञात नहीं है, तब भी यदि उसी translation unit में बाद में type complete हो जाता है, तो कुछ स्थितियों में यह अनुमति है
- global variable या incomplete type array पर भी इसी तरह का logic लागू होता है
- यह विषय DR016 में भी चर्चा में है
-
void type का external object
- internal linkage वाले
void type variable की declaration वैध नहीं है, लेकिन external linkage वाले void type variable की declaration व्याकरण की दृष्टि से वैध है और C11 standard में कहीं भी इसे स्पष्ट रूप से निषिद्ध नहीं किया गया है
- C11 § 6.2.5 ¶ 19 के अनुसार
void type, मानों के रिक्त समुच्चय से बना incomplete object type है जो complete नहीं हो सकता
- C11 § 6.3.2.1 ¶ 1, lvalue को
void के अलावा object type वाले expression के रूप में परिभाषित करता है, इसलिए void type object नाम foo, वैध lvalue नहीं है
- C11 के आधार पर external
void object पर कोई अर्थपूर्ण और conforming operation सोचना कठिन है
- DR012 में बताया गया है कि यदि type को
const void में बदला जाए तो object foo का address लेना वैध हो जाता है, जो जानबूझकर बनाई गई सुविधा से अधिक एक oversight जैसा लगता है
-
pointer-const conversion
array, string literal, pointer adjustment
-
array, pointer नहीं है
- array initialization और pointer initialization समतुल्य नहीं हैं
- पहला रूप automatic या static storage duration वाले modifiable array को initialize करता है
- दूसरा रूप ऐसे pointer को initialize करता है जो static storage duration वाले array को point करता है, और वह array आवश्यक नहीं कि modifiable हो
- array, pointer नहीं है, और विवरण संबंधित लेख में है
-
a, &a, &a[0]
int a[42]; में a, &a, और &a[0], तीनों array के पहले element के address के रूप में evaluate होते हैं
- लेकिन इन तीनों expressions के type अलग-अलग हैं, इसलिए इन्हें परस्पर बदलकर इस्तेमाल नहीं किया जा सकता
- विवरण संबंधित लेख में है
-
array parameters और local array
- यदि फ़ंक्शन parameter type “
T का array” है, तो उसे “T को point करने वाले pointer” में adjust किया जाता है
- भले ही parameter
x, int[42] जैसा दिखे, वास्तव में उसे int * के रूप में माना जाता है
- यदि local variable
y, int[42] है, तो sizeof(y), 42 * sizeof(int) होता है
- सामान्यतः object pointer का size, 42 integers के size के बराबर नहीं होता, इसलिए
sizeof(x) == sizeof(y) आम तौर पर false होता है
- विवरण संबंधित लेख में है
ऑपरेटर, evaluation order, control flow
-
x+++y
- C में C++ की तरह नए ऑपरेटर define नहीं किए जा सकते, इसलिए
+++ जैसा कोई नया ऑपरेटर नहीं है
x+++y को मौजूदा ऑपरेटरों के संयोजन के रूप में समझा जाता है, और यह (x++) + y के बराबर है
--*--p भी कोई नया ऑपरेटर नहीं, बल्कि मौजूदा ऑपरेटरों का संयोजन है
--*--p --(*(--p)) के बराबर है, और उदाहरण में इसका मान -1 निकलता है तथा side effect के रूप में x[0] में -1 assign होता है
-
arithmetic operands का evaluation order
- ऑपरेटर precedence अच्छी तरह परिभाषित है, लेकिन arithmetic operands का evaluation order परिभाषित नहीं है
(x=1) + (x=2) में दोनों assignment किस क्रम में होंगे यह तय नहीं है, इसलिए x का अंतिम मान 1 होगा या 2 यह निश्चित नहीं है; इस वजह से यह undefined behavior है
-std=c11 -O2 विकल्प पर GCC 8.2.1 उदाहरण expression का मान 4 निकालता है, जबकि Clang 7.0.0 3 निकालता है
-
logical operators का evaluation order
- logical operators
&& और || में operands का evaluation order भी स्पष्ट रूप से परिभाषित है
- C standard की भाषा में, पहले operand के evaluation और दूसरे operand के evaluation के बीच एक sequence point मौजूद होता है
- उदाहरण में पहले
x=1 evaluate होता है और true बनता है, फिर x=2 evaluate होता है और वह भी true बनता है, इसलिए पूरा expression true होता है
-
switch के body की मुक्त संरचना
switch statement का body कोई भी statement हो सकता है, इसलिए loop और if के मिले-जुले ढांचे भी वैध हो सकते हैं
- भले ही
if statement की control expression हमेशा false हो, उसके अंदर की true branch में case label हो तो वह statement live हो जाता है, और printf("1"); dead code नहीं होता
case 2 पर jump करने पर loop का clause-1 और control expression execute नहीं भी हो सकते, इसलिए variable i को पहले से initialize किया हुआ होना चाहिए
case 1 में break न होने से fall through हो जाए, तब भी अगर case 1 if की true branch में है और case 2 false branch में, तो execution case 2 को छोड़कर case 3 तक जारी रह सकता है
- तीन calls
foo(0); foo(1); foo(2); के बाद console output 02313223 होगा
- loop और switch को मिलाकर बना एक प्रसिद्ध वास्तविक उदाहरण Duff's device है
temporary object lifetime और C standard version के अंतर
- कुछ code fragments C11 में undefined behavior हैं, लेकिन C99 में ज़रूरी नहीं कि ऐसे हों
- C11 में कुछ objects की lifetime छोटी कर दी गई है, इसलिए function call से लौटने वाला object केवल तब तक जीवित रहता है जब तक right-hand side evaluate हो रहा हो
- C99 में वही object enclosing block के अंत तक जीवित रहता है
- lifetime समाप्त हो चुके object को reference करना C11 § 6.2.4 ¶ 2 के अनुसार undefined behavior है
- C99 में भी automatic storage duration वाले object की lifetime सबसे नज़दीकी enclosing block से बंधी होती है, इसलिए उस block के बाहर object को reference करना undefined behavior है
- C11 § 6.2.4 ¶ 8 यह निर्धारित करता है कि यदि struct या union type का non-lvalue expression किसी array member को शामिल करता है, तो वह automatic storage duration और temporary lifetime वाले object को refer करता है
- इस temporary object की lifetime expression के evaluate होने पर शुरू होती है, और जिस full expression या full declarator में वह शामिल है उसके evaluation के अंत में समाप्त हो जाती है
- temporary lifetime वाले object को modify करने की कोशिश undefined behavior है
- यह उदाहरण N1285 से लिया गया है, और अतिरिक्त चर्चा भी वहीं उपलब्ध है
1 टिप्पणियां
Lobste.rs की राय
प्रश्न 4 C23 में वैध नहीं है, लेकिन उससे पहले वैध था
प्रश्न 10 न सही उत्तर है न गलत, इसलिए उसे multiple-choice कहना थोड़ा खटकता है
प्रश्न 15 तकनीकी रूप से गलत था, खासकर प्रश्न 13 के संदर्भ में, और प्रश्न 20 "निर्दिष्ट नहीं" है, इसलिए वह भी किसी उत्तर में नहीं आता
प्रश्न 30 पढ़ने के तरीके पर निर्भर होने की वजह से अस्पष्ट है
फिर भी 31 में से 27 सही किए, और compiler developer होना शायद थोड़ा काम आया
लगभग चार सवाल हल करने के बाद, C इतना simple है कि side project में इस्तेमाल किया जा सकता है वाली जो थोड़ी-बहुत भावना बची थी, वह खत्म हो गई
clangमें-std=<language-standard>-pedantic -Wall -Wextraका इस्तेमाल करें और हर warning आने पर सच में उसे ठीक करें, और pointer casting व pointer manipulation को जितना हो सके टालें, तो शायद बड़े pitfalls से बचा जा सकता हैआजकल GCC/
clangwarnings काफ़ी अच्छी हैं, और <language-standard> के लिए c89, c99, c11, c23 इस्तेमाल किए जा सकते हैंअगर tcc जैसा compiler इस्तेमाल करें, जो अजीब optimizations नहीं करता, तो कम विचित्र surprises मिलेंगे
मैंने बस "यहाँ सबसे बेतुका behavior क्या होगा?" के आधार पर चुना और 32 में से 21 सही निकले
जो ज़्यादातर गलत हुए, वह इसलिए कि मैंने उस बेतुकेपन की गहराई तक पर्याप्त नहीं सोचा
C को 15 साल से भी पहले बस थोड़ा-सा छुआ था, और ऐसी quiz देखकर उसे फिर से आज़माने का मन नहीं करता
C23 के हिसाब से प्रश्न 4 का उत्तर वैध नहीं है
दिलचस्प बात यह है कि मैंने काफ़ी समय से C इस्तेमाल नहीं किया, फिर भी 32 में से 27 सही किए
ऐसी ही चीज़ों की वजह से मैं static checker और linter पर निर्भर रहा हूँ
प्रश्न 1 से ही अटपटा लगा
यह नहीं सोचा गया कि वे pointers कहाँ से आ सकते हैं, और वहाँ बताए गए मामले के सही होने के लिए बहुत विशेष शर्तें चाहिए
ज़्यादातर मामलों में उन pointers को बनाने की कोशिश करना ही undefined behavior है, लेकिन फिर भी इसे निष्पक्ष माना जा सकता है
प्रश्न 3 सच में चौंकाने वाला था, और C का एक और pitfall निकला
यह बात ही बहुत परेशान करने वाली है कि C integer literals के fixed types होते हैं
integer promotion के नियम कुछ हद तक चीज़ों को संभाल लेते हैं, लेकिन वही errors का स्रोत भी हैं
आधुनिक भाषाओं को ज़्यादातर, या सभी implicit numeric casting पर रोक लगानी चाहिए, और जहाँ संभव हो वहाँ context से literal type infer करना चाहिए, और जहाँ संभव न हो वहाँ explicit casting की मांग करनी चाहिए
प्रश्न 6 के बाद मैंने test पर भरोसा करना छोड़ दिया
शुरुआत में इसलिए, क्योंकि प्रश्न 5 का उत्तर मानो प्रश्न 6 को गलत करवाने के लिए ही बनाया गया था, लेकिन दोबारा देखने पर लगा कि प्रश्न 6 खुद ही गलत है
व्याख्या कहती है कि function call undefined behavior है, लेकिन सवाल यह था कि function definition वैध है या नहीं, और शायद उसके वैध होने की पूरी संभावना थी
और ऐसा बहुत दुर्लभ मामला भी नहीं लगता
switch()वाला सवाल सच में बहुत अच्छा थापेचीदा था, लेकिन उसे दिमाग़ में हल करने की प्रक्रिया बहुत मज़ेदार थी