यह कोड आउटपुट क्यों देता है C++Sucks
? इसके पीछे क्या अवधारणा है?
#include <stdio.h>
double m[] = {7709179928849219.0, 771};
int main() {
m[1]--?m[0]*=2,main():printf((char*)m);
}
इसका परीक्षण यहां करें ।
skcuS++C
।
यह कोड आउटपुट क्यों देता है C++Sucks
? इसके पीछे क्या अवधारणा है?
#include <stdio.h>
double m[] = {7709179928849219.0, 771};
int main() {
m[1]--?m[0]*=2,main():printf((char*)m);
}
इसका परीक्षण यहां करें ।
skcuS++C
।
जवाबों:
संख्या 7709179928849219.0
में 64-बिट के रूप में निम्नलिखित बाइनरी प्रतिनिधित्व है double
:
01000011 00111011 01100011 01110101 01010011 00101011 00101011 01000011
+^^^^^^^ ^^^^---- -------- -------- -------- -------- -------- --------
+
संकेत की स्थिति दिखाता है; ^
प्रतिपादक की, और -
मंटिसा की (यानी घातांक के बिना मूल्य)।
चूंकि प्रतिनिधित्व द्विआधारी घातांक और मंटिसा का उपयोग करता है, इसलिए संख्या को बढ़ाकर घातांक एक करके। आपका कार्यक्रम इसे 771 बार ठीक करता है, इसलिए 1075 (दशमलव प्रतिनिधित्व 10000110011
) पर शुरू होने वाला घातांक 1075 + 771 = 1846 अंत में बन जाता है; 1846 का बाइनरी प्रतिनिधित्व है 11100110110
। परिणामी पैटर्न इस तरह दिखता है:
01110011 01101011 01100011 01110101 01010011 00101011 00101011 01000011
-------- -------- -------- -------- -------- -------- -------- --------
0x73 's' 0x6B 'k' 0x63 'c' 0x75 'u' 0x53 'S' 0x2B '+' 0x2B '+' 0x43 'C'
यह पैटर्न उस स्ट्रिंग से मेल खाता है जिसे आप मुद्रित करते हैं, केवल पीछे की ओर देखते हैं। इसी समय, सरणी का दूसरा तत्व शून्य हो जाता है, अशक्त टर्मिनेटर प्रदान करता है, जिससे स्ट्रिंग को पास होने के लिए उपयुक्त होता है printf()
।
7709179928849219
मान चिपकाया , और बाइनरी प्रतिनिधित्व वापस मिला।
अधिक पठनीय संस्करण:
double m[2] = {7709179928849219.0, 771};
// m[0] = 7709179928849219.0;
// m[1] = 771;
int main()
{
if (m[1]-- != 0)
{
m[0] *= 2;
main();
}
else
{
printf((char*) m);
}
}
यह main()
771 बार पुनरावर्ती कॉल करता है।
शुरुआत में m[0] = 7709179928849219.0
, जिसके लिए खड़ा है C++Suc;C
। हर कॉल में, m[0]
अंतिम दो अक्षरों को "मरम्मत" करने के लिए दोगुना हो जाता है। अंतिम कॉल में, m[0]
ASCII के चार प्रतिनिधित्व शामिल हैं C++Sucks
और m[1]
इसमें केवल शून्य हैं, इसलिए इसमें स्ट्रिंग के लिए एक शून्य टर्मिनेटर है C++Sucks
। सभी अनुमानों के तहत m[0]
8 बाइट्स पर संग्रहीत किया जाता है, इसलिए प्रत्येक चार 1 बाइट लेता है।
पुनरावृत्ति और अवैध main()
कॉलिंग के बिना यह इस तरह दिखेगा:
double m[] = {7709179928849219.0, 0};
for (int i = 0; i < 771; i++)
{
m[0] *= 2;
}
printf((char*) m);
डिस्क्लेमर: यह उत्तर प्रश्न के मूल रूप में पोस्ट किया गया था, जिसमें केवल C ++ का उल्लेख था और इसमें C ++ हेडर शामिल था। शुद्ध सी से प्रश्न का रूपांतरण मूल प्रश्नकर्ता से इनपुट के बिना, समुदाय द्वारा किया गया था।
औपचारिक रूप से, इस कार्यक्रम के बारे में तर्क करना असंभव है क्योंकि यह बीमार है (यानी यह कानूनी सी ++ नहीं है)। यह C ++ 11 का उल्लंघन करता है [basic.start.main] p3:
फ़ंक्शन मेन का उपयोग किसी प्रोग्राम के भीतर नहीं किया जाएगा।
यह एक तरफ, यह इस तथ्य पर निर्भर करता है कि एक विशिष्ट उपभोक्ता कंप्यूटर पर, double
8 बाइट्स लंबा है, और एक निश्चित प्रसिद्ध आंतरिक प्रतिनिधित्व का उपयोग करता है। सरणी के प्रारंभिक मानों की गणना इसलिए की जाती है कि जब "एल्गोरिथ्म" किया जाता है, तो पहले का अंतिम मान double
ऐसा होगा कि आंतरिक प्रतिनिधित्व (8 बाइट्स) 8 वर्णों का ASCII कोड होगा C++Sucks
। सरणी में दूसरा तत्व तब है 0.0
, जिसका पहला बाइट 0
आंतरिक प्रतिनिधित्व में है, जिससे यह एक वैध सी-स्टाइल स्ट्रिंग है। यह तो उत्पादन का उपयोग करने के लिए भेजा है printf()
।
इसे HW पर चलाना जहाँ उपर्युक्त में से कुछ पकड़ में नहीं आता है, इसके बजाय कचरा पाठ (या शायद सीमा से बाहर पहुंच) होगा।
basic.start.main
इसी शब्द के साथ 3.6.1 / 3 था ।
main()
, या इसे हार्ड ड्राइव को प्रारूपित करने के लिए एपीआई कॉल से बदल दे, या जो भी हो।
शायद कोड को समझने का सबसे आसान तरीका रिवर्स में चीजों के माध्यम से काम करना है। हम प्रिंट करने के लिए एक स्ट्रिंग के साथ शुरू करेंगे - संतुलन के लिए, हम "C ++ Rocks" का उपयोग करेंगे। महत्वपूर्ण बिंदु: मूल की तरह, यह बिल्कुल आठ वर्ण लंबा है। चूंकि हम मूल की तरह (लगभग) ऐसा करने जा रहे हैं, और इसे उल्टे क्रम में प्रिंट करते हैं, हम इसे रिवर्स ऑर्डर में डालकर शुरू करेंगे। हमारे पहले चरण के लिए, हम उस बिट पैटर्न को एक के रूप में देखेंगे double
, और परिणाम का प्रिंट आउट लेंगे :
#include <stdio.h>
char string[] = "skcoR++C";
int main(){
printf("%f\n", *(double*)string);
}
यह पैदा करता है 3823728713643449.5
। इसलिए, हम किसी तरह से हेरफेर करना चाहते हैं जो स्पष्ट नहीं है, लेकिन रिवर्स करना आसान है। मैं अर्ध-मनमाने ढंग से गुणा करके 256 का चयन करूंगा, जो हमें देता है 978874550692723072
। अब, हमें केवल 256 से विभाजित करने के लिए कुछ ओफ़्फ़ुसेटेड कोड लिखने की आवश्यकता है, फिर उस के अलग-अलग बाइट्स को उल्टा करके प्रिंट करें:
#include <stdio.h>
double x [] = { 978874550692723072, 8 };
char *y = (char *)x;
int main(int argc, char **argv){
if (x[1]) {
x[0] /= 2;
main(--x[1], (char **)++y);
}
putchar(*--y);
}
अब हमारे पास बहुत से कास्टिंग, पास करने के तर्क हैं (पुनरावर्ती) main
जिन्हें पूरी तरह से नजरअंदाज कर दिया गया है (लेकिन वेतन वृद्धि और वेतन वृद्धि प्राप्त करने के लिए मूल्यांकन पूरी तरह से महत्वपूर्ण है), और निश्चित रूप से इस तथ्य को कवर करने के लिए पूरी तरह से मनमाना लग रहा है कि हम क्या कर रहे हैं? वास्तव में बहुत सीधा है।
बेशक, चूँकि पूरा बिंदु आपत्तिजनक है, अगर हमें ऐसा लगता है कि हम और भी कदम उठा सकते हैं। उदाहरण के लिए, हम शॉर्ट-सर्किट मूल्यांकन का लाभ उठा सकते हैं, अपने if
कथन को एक एकल अभिव्यक्ति में बदल सकते हैं , इसलिए मुख्य का शरीर इस तरह दिखता है:
x[1] && (x[0] /= 2, main(--x[1], (char **)++y));
putchar(*--y);
किसी ऐसे व्यक्ति के लिए जो आचार संहिता (और / या कोड गोल्फ) का आदी नहीं है, यह वास्तव में बहुत अजीब लग रहा है - कंप्यूटिंग और and
कुछ अर्थहीन फ्लोटिंग पॉइंट नंबर की तार्किकता को त्यागना और वापसी मूल्य main
, जो कि वापस नहीं आ रहा है मूल्य। इससे भी बदतर, यह एहसास किए बिना (और इस बारे में) कि शॉर्ट-सर्किट मूल्यांकन कैसे काम करता है, यह तुरंत स्पष्ट भी नहीं हो सकता है कि यह अनंत पुनरावृत्ति से कैसे बचा जाता है।
हमारा अगला कदम संभवतः प्रत्येक चरित्र को उस चरित्र को खोजने से अलग करना होगा। हम उस सही मूल्य को आसानी से वापस मूल्य के रूप में उत्पन्न करके main
, और क्या main
रिटर्न प्रिंट कर सकते हैं :
x[1] && (x[0] /= 2, putchar(main(--x[1], (char **)++y)));
return *--y;
कम से कम मेरे लिए, यह पर्याप्त रूप से बाधित लगता है, इसलिए मैं इसे उस पर छोड़ दूंगा।
यह सिर्फ एक डबल ऐरे (16 बाइट्स) का निर्माण कर रहा है, जिसे - अगर एक चार सरणी के रूप में व्याख्या किया जाता है - स्ट्रिंग "सी ++ सक्स" के लिए एएससीआईआई कोड बनाएं।
हालाँकि, कोड प्रत्येक प्रणाली पर काम नहीं कर रहा है, यह कुछ अपरिभाषित तथ्यों पर निर्भर करता है:
निम्नलिखित कोड प्रिंट करता है C++Suc;C
, इसलिए संपूर्ण गुणा केवल अंतिम दो अक्षरों के लिए है
double m[] = {7709179928849219.0, 0};
printf("%s\n", (char *)m);
अन्य लोगों ने सवाल को अच्छी तरह से समझाया है, मैं एक नोट जोड़ना चाहूंगा कि यह मानक के अनुसार अपरिभाषित व्यवहार है ।
C ++ 11 3.6.1 / 3 मुख्य कार्य
फ़ंक्शन मेन का उपयोग किसी प्रोग्राम के भीतर नहीं किया जाएगा। मुख्य का लिंकेज (3.5) कार्यान्वयन-परिभाषित है। एक प्रोग्राम जो मुख्य को हटाए गए के रूप में परिभाषित करता है या जो मुख्य को इनलाइन, स्टेटिक, या कॉन्स्टैक्स होने की घोषणा करता है, बीमार है। मुख्य नाम अन्यथा आरक्षित नहीं है। [उदाहरण: सदस्य कार्यों, वर्गों और गणनाओं को मुख्य कहा जा सकता है, जैसा कि अन्य नामस्थानों में इकाइयाँ कर सकती हैं। उदाहरण का]
कोड को इस तरह दोबारा लिखा जा सकता है:
void f()
{
if (m[1]-- != 0)
{
m[0] *= 2;
f();
} else {
printf((char*)m);
}
}
यह क्या कर रहा है double
सरणी में बाइट्स का एक सेट पैदा कर रहा है m
जो वर्णों के अनुरूप होने के लिए होता है 'C ++ Sucks' जिसके बाद null-termator होता है। उन्होंने दोहरे मान का चयन करके कोड को बाधित कर दिया है, जो जब 771 बार दोगुना होता है, तो मानक प्रतिनिधित्व में, सरणी के दूसरे सदस्य द्वारा प्रदान किए गए शून्य टर्मिनेटर के साथ बाइट्स का सेट।
ध्यान दें कि यह कोड एक अलग एंडियन प्रतिनिधित्व के तहत काम नहीं करेगा। इसके अलावा, कॉलिंग main()
की सख्ती से अनुमति नहीं है।
f
वापसी एक क्यों है int
?
int
सवाल में वापसी की नकल कर रहा था । मुझे वह ठीक करने दो।
पहले हमें याद रखना चाहिए कि द्विआधारी प्रारूप में डबल सटीक संख्याएं मेमोरी में संग्रहीत की जाती हैं:
(i) संकेत के लिए 1 बिट
(ii) प्रतिपादक के लिए ११ बिट्स
(iii) परिमाण के लिए 52 बिट्स
बिट्स का क्रम (i) से (iii) तक घट जाता है।
पहले दशमलव भिन्नात्मक संख्या को समकक्ष भिन्नात्मक संख्या में परिवर्तित किया जाता है और फिर इसे बाइनरी में परिमाण के क्रम के रूप में व्यक्त किया जाता है।
तो नंबर 7709179928849219.0 हो जाता है
(11011011000110111010101010011001010110010101101000011)base 2
=1.1011011000110111010101010011001010110010101101000011 * 2^52
अब परिमाण बिट्स पर विचार करते समय 1. उपेक्षा की जाती है क्योंकि परिमाण विधि के सभी क्रम 1 से शुरू होंगे ।
तो परिमाण भाग बन जाता है:
1011011000110111010101010011001010110010101101000011
अब की शक्ति 2 है 52 है, हम के रूप में यह करने के लिए संख्या biasing जोड़ने की जरूरत है 2 ^ (प्रतिपादक के लिए बिट्स -1) -1 यानी 2 ^ (11 -1) -1 = 1023 , इसलिए हमारे प्रतिपादक हो जाता है 52 + 1023 = 1075
अब हमारा कोड 2 , 771 बार संख्या को बदल देता है जो कि घातांक को 771 तक बढ़ा देता है
तो हमारा घातांक है (1075 + 771) = 1846 जिसका बाइनरी समतुल्य है (11100110110)
अब हमारी संख्या सकारात्मक है इसलिए हमारा साइन बिट 0 है ।
तो हमारी संशोधित संख्या बन जाती है:
साइन बिट + प्रतिपादक + परिमाण (बिट्स का सरल संयोजन)
0111001101101011011000110111010101010011001010110010101101000011
चूंकि मी चार सूचक में परिवर्तित हो गया है इसलिए हम एलएसडी से 8 के भाग में बिट पैटर्न को विभाजित करेंगे
01110011 01101011 01100011 01110101 01010011 00101011 00101011 01000011
(हेक्स समतुल्य है :)
0x73 0x6B 0x63 0x75 0x53 0x2B 0x2B 0x43
जैसा कि चरित्र मानचित्र से दिखाया गया है:
s k c u S + + C
अब एक बार इसे m [1] बना दिया गया है, जिसका अर्थ है NULL वर्ण
अब यह मानते हुए कि आप इस प्रोग्राम को थोड़ा-सा एंडियन मशीन पर चलाते हैं (निचला ऑर्डर बिट कम एड्रेस में स्टोर किया जाता है) इसलिए पॉइंटर एम पॉइंटर को सबसे कम एड्रेस बिट तक ले जाते हैं और फिर 8 के चक में बिट्स उठाकर आगे बढ़ते हैं (जैसा कि चारो को टाइप किया गया * ) और प्रिंट () बंद हो जाता है जब आखिरी chunck में 00000000 का सामना किया ...
यह कोड हालांकि पोर्टेबल नहीं है।