यह कैसे संभव है? sizeof के विरोधाभास का परिचय
\n\nकल्पना करें: आप C++ में दो फील्ड्स वाली एक सरल स्ट्रक्चर बना रहे हैं — char (1 बाइट) और int (4 बाइट्स)। तार्किक रूप से, इसका आकार 5 बाइट होना चाहिए। आप कोड लिखते हैं, sizeof कॉल करते हैं और... 8 बाइट प्राप्त करते हैं। "बकवास!" — आप सोचते हैं। "क्या कंपाइलर पागल हो गया है?"
यह प्रश्न Stack Overflow पर सबसे लोकप्रिय प्रश्नों में से एक है (200,000 से अधिक व्यूज)। और यह कंपाइलर की गलती के बारे में नहीं है। यह इस बारे में है कि प्रोसेसर और रैम वास्तव में निम्न स्तर पर कैसे काम करते हैं। इस लेख में हम C++ में स्ट्रक्चर अलाइनमेंट, पैडिंग (भराव) की अवधारणा को समझेंगे, और स्ट्रक्चर के वास्तविक आकार की भविष्यवाणी करना सीखेंगे। और सबसे महत्वपूर्ण बात — जब वास्तव में आवश्यकता हो, तो इसे कैसे नियंत्रित करें।
\n\n1 + 4 ≠ 5 क्यों? प्रक्रिया का भौतिकी
\n\nआधुनिक प्रोसेसर मेमोरी को बाइट दर बाइट नहीं पढ़ते हैं। वे "शब्दों" में काम करते हैं — 4, 8 या 16 बाइट के ब्लॉक (आर्किटेक्चर पर निर्भर करता है)। यदि 4-बाइट का पूर्णांक (int) ऐसे पते पर स्थित है जो 4 का गुणज नहीं है, तो प्रोसेसर को इस संख्या को पढ़ने के लिए मेमोरी तक दो बार पहुँचना होगा, और फिर इसे दो टुकड़ों से "जोड़ना" होगा। यह बेहद धीमा है।
इससे बचने के लिए, कंपाइलर स्ट्रक्चर के फील्ड्स के बीच "खाली" बाइट्स (पैडिंग) डालता है। वह प्रत्येक फील्ड को उसके आकार के गुणज पते पर संरेखित करता है। इसे प्राकृतिक संरेखण (natural alignment) कहा जाता है।
\n\n// सरल उदाहरण जो सभी को आश्चर्यचकित करता है\n#include <iostream>\n\nstruct Example {\n char c; // 1 बाइट\n int i; // 4 बाइट्स\n};\n\nint main() {\n std::cout << "char का आकार: " << sizeof(char) << std::endl; // 1\n std::cout << "int का आकार: " << sizeof(int) << std::endl; // 4\n std::cout << "struct का आकार: " << sizeof(Example) << std::endl; // 8 (!)\n return 0;\n}\n\nयह मेमोरी में कैसा दिखता है? c बाइट 0 पर है। बाइट 1, 2, 3 — पैडिंग हैं। और i बाइट 4 से शुरू होता है। कुल: 1 (c) + 3 (खाली) + 4 (i) = 8 बाइट्स।
संरेखण के नियम: कंपाइलर कैसे निर्णय लेता है
\n\nकंपाइलर सरल लेकिन सख्त नियमों का पालन करता है। इन्हें जानकर, आप प्रोग्राम चलाए बिना स्ट्रक्चर के आकार की भविष्यवाणी कर सकते हैं।
\n\n- \n
- नियम 1: फील्ड्स का क्रम संरक्षित रहता है। कंपाइलर मेमोरी में फील्ड्स को पुनर्व्यवस्थित नहीं करता (हालाँकि कुछ कंपाइलर ऑप्टिमाइज़ेशन के दौरान ऐसा कर सकते हैं, लेकिन मानक इसकी गारंटी नहीं देता)। \n
- नियम 2: प्रत्येक फील्ड उस पते पर संरेखित होता है जो उसके आकार का गुणज हो।
char(1 बाइट) कहीं भी हो सकता है।short(2 बाइट्स) — सम पतों पर।int(4 बाइट्स) — 4 के गुणज पतों पर।double(8 बाइट्स) — 8 के गुणज पतों पर। \n - नियम 3: पूरी स्ट्रक्चर का आकार सबसे बड़े फील्ड के संरेखण का गुणज होना चाहिए। यह आवश्यक है ताकि स्ट्रक्चर की सरणियाँ सही ढंग से काम करें। यदि सबसे बड़ा वेरिएबल
double(8 बाइट्स) है, तो स्ट्रक्चर का आकार 8 का गुणज होगा। \n
आइए एक अधिक जटिल उदाहरण देखें:
\n\nstruct Complex {\n char a; // 1 बाइट\n int b; // 4 बाइट्स\n short c; // 2 बाइट्स\n char d; // 1 बाइट\n};\n// sizeof क्या होगा? आइए एक साथ गणना करें।\n\nचरण-दर-चरण विश्लेषण:
\n- \n
a(char) — बाइट 0। \n b(int) — 4 के गुणज पते पर होना चाहिए। अगला ऐसा पता 4 है। इसलिए, पैडिंग: बाइट्स 1, 2, 3। b के बाद कुल: 8 बाइट्स (0-7)। \n c(short) — सम पते पर होना चाहिए। वर्तमान स्थिति 8 (सम) है। बढ़िया। c के बाद कुल: 10 बाइट्स (0-9)। \n d(char) — कहीं भी हो सकता है। इसे बाइट 10 पर रखें। d के बाद कुल: 11 बाइट्स (0-10)। \n