एक मेमने का आकार 1 बाइट क्यों होता है?


89

मैं C ++ में कुछ मेमनों की स्मृति के साथ काम कर रहा हूं, लेकिन मैं उनके आकार से थोड़ा हैरान हूं।

यहाँ मेरा परीक्षण कोड है:

#include <iostream>
#include <string>

int main()
{
  auto f = [](){ return 17; };
  std::cout << f() << std::endl;
  std::cout << &f << std::endl;
  std::cout << sizeof(f) << std::endl;
}

आप इसे यहाँ चला सकते हैं: http://fiddle.jyt.io/github/b13f682d1237eb69dd60728bb52598

Ouptut है:

17
0x7d90ba8f626f
1

इससे पता चलता है कि मेरे लंबो का आकार 1 है।

  • यह कैसे हो सकता है?

  • क्या मेमने को कम से कम, इसके कार्यान्वयन के लिए एक संकेतक नहीं होना चाहिए?


17
इसे एक फंक्शन ऑब्जेक्ट (a ) के structसाथ लागू किया गयाoperator()
george_ptr

14
और एक खाली संरचना का आकार 0 नहीं हो सकता है इसलिए 1 परिणाम। कुछ कैप्चर करने की कोशिश करें और देखें कि आकार क्या होता है।
मोहम्मद एलगावी

2
एक लंबोदर क्यों एक सूचक होना चाहिए ??? यह एक वस्तु है जिसमें एक कॉल ऑपरेटर है।
केरेक एसबी

7
C ++ में लैम्ब्डा संकलन-समय पर मौजूद होते हैं, और इनवॉइस संकलन या लिंक समय पर लिंक (या इनबिल्ड) से जुड़े होते हैं। इसलिए ऑब्जेक्ट में रनटाइम पॉइंटर की कोई आवश्यकता नहीं है। @KerrekSB यह अपेक्षा करना अप्राकृतिक अनुमान नहीं है कि एक लैम्ब्डा में एक फ़ंक्शन पॉइंटर होगा, क्योंकि लैम्बडा को लागू करने वाली अधिकांश भाषाएं C ++ की तुलना में अधिक गतिशील हैं।
काइल स्ट्रैंड

2
@KerrekSB "क्या मायने रखता है" - किस मायने में? एक बंद वस्तु का कारण खाली हो सकता है (फ़ंक्शन पॉइंटर को शामिल करने के बजाय) क्योंकि यह कहा जाता है कि फ़ंक्शन संकलन या लिंक समय पर जाना जाता है। ऐसा लगता है कि ओपी को गलतफहमी हुई है। मैं नहीं देखता कि आपकी टिप्पणी किस तरह से चीजों को स्पष्ट करती है।
काइल स्ट्रैंड

जवाबों:


107

प्रश्न में लंबोदर का वास्तव में कोई राज्य नहीं है

की जांच:

struct lambda {
  auto operator()() const { return 17; }
};

और अगर हमारे पास था lambda f;, तो यह एक खाली वर्ग है। न केवल उपर्युक्त lambdaकार्यात्मक रूप से आपके लैम्ब्डा के समान है, यह है (मूल रूप से) कि आपका लैम्ब्डा कैसे लागू किया जाता है! (इसे सूचक संचालक को कार्य करने के लिए एक अंतर्निहित कलाकार की आवश्यकता होती है, और नाम lambdaको कुछ संकलक-जनित छद्म मार्गदर्शक के साथ प्रतिस्थापित किया जा रहा है)

C ++ में, ऑब्जेक्ट पॉइंटर्स नहीं हैं। वे वास्तविक चीजें हैं। वे केवल उन डेटा को संग्रहीत करने के लिए आवश्यक स्थान का उपयोग करते हैं। किसी वस्तु का सूचक किसी वस्तु से बड़ा हो सकता है।

जब आप एक समारोह के लिए एक संकेत के रूप में उस मेमने के बारे में सोच सकते हैं, यह नहीं है। आप auto f = [](){ return 17; };एक अलग फ़ंक्शन या लैम्ब्डा को पुन: असाइन नहीं कर सकते हैं !

 auto f = [](){ return 17; };
 f = [](){ return -42; };

उपरोक्त अवैध है । में कोई जगह नहीं है fकी दुकान में जो समारोह के नाम से जाना जा रहा है - कि जानकारी में संग्रहित है प्रकार की f, के मूल्य में नहीं f!

यदि आपने ऐसा किया है:

int(*f)() = [](){ return 17; };

या यह:

std::function<int()> f = [](){ return 17; };

अब आप सीधे मेमने का भंडारण नहीं कर रहे हैं। इन दोनों मामलों में, f = [](){ return -42; }कानूनी है - इसलिए इन मामलों में, हम यह मान रहे हैं कि हम किस फ़ंक्शन को मान रहे हैं f। और sizeof(f)अब नहीं है 1, लेकिन बल्कि sizeof(int(*)())या बड़ा (मूल रूप से, सूचक आकार या बड़ा हो, जैसा कि आप उम्मीद करते हैं। std::functionमानक द्वारा निहित एक न्यूनतम आकार है (उन्हें एक निश्चित आकार तक "खुद के अंदर कॉलबेल" स्टोर करने में सक्षम होना चाहिए) कम से कम उतने बड़े हैं जितने कि फंक्शन पॉइंटर प्रैक्टिस में)।

में int(*f)()मामला है, आप एक समारोह के लिए एक समारोह सूचक भंडारण कर रहे हैं कि बर्ताव करता है के रूप में यदि आप उस लैम्ब्डा कहा जाता है। यह केवल स्टेटलेस लैम्ब्डा (खाली []कैप्चर लिस्ट वाले लोगों ) के लिए काम करता है ।

में std::function<int()> fमामला है, आप एक प्रकार-विलोपन वर्ग पैदा कर रहे std::function<int()>उदाहरण है कि (इस मामले में) में प्लेसमेंट के लिए एक आंतरिक बफर में आकार -1 लैम्ब्डा की एक प्रति स्टोर करने के लिए नए (और उपयोग करता है, एक बड़ा लैम्ब्डा में पारित किया गया था, तो (अधिक राज्य के साथ ), ढेर आवंटन का उपयोग करेगा)।

एक अनुमान के रूप में, ऐसा कुछ है जो आप सोचते हैं कि शायद चल रहा है। वह लंबोदर एक वस्तु है जिसका प्रकार उसके हस्ताक्षर द्वारा वर्णित है। सी ++ में, मैनुअल फ़ंक्शन ऑब्जेक्ट कार्यान्वयन पर लंबोदा शून्य लागत सार बनाने का निर्णय लिया गया था । यह आपको एक लैंबडा को एक stdएल्गोरिथ्म (या समान) में पास करने देता है और इसकी सामग्री कंपाइलर को पूरी तरह से दिखाई दे सकती है जब यह एल्गोरिथम टेम्पलेट को तत्काल तैयार करता है। यदि एक लंबोदर के पास एक प्रकार था std::function<void(int)>, तो इसकी सामग्री पूरी तरह से दिखाई नहीं देगी, और एक हाथ से तैयार की गई फ़ंक्शन ऑब्जेक्ट तेज हो सकती है।

सी ++ मानकीकरण का लक्ष्य हाथ से तैयार किए गए सी कोड पर शून्य ओवरहेड के साथ उच्च स्तरीय प्रोग्रामिंग है।

अब जब आप समझते हैं कि आपका fवास्तव में स्टेटलेस है, तो आपके सिर में एक और सवाल होना चाहिए: लैम्ब्डा की कोई स्थिति नहीं है। इसका आकार क्यों नहीं है 0?


संक्षिप्त उत्तर है।

C ++ की सभी वस्तुओं का मानक के तहत 1 का न्यूनतम आकार होना चाहिए, और एक ही प्रकार की दो वस्तुओं का समान पता नहीं हो सकता है। ये जुड़े हुए हैं, क्योंकि एक प्रकार के सरणी में Tतत्वों sizeof(T)को अलग रखा जाएगा ।

अब, क्योंकि इसमें कोई राज्य नहीं है, कभी-कभी यह कोई स्थान नहीं ले सकता है। यह तब नहीं हो सकता जब यह "अकेला" हो, लेकिन कुछ संदर्भों में यह हो सकता है। std::tupleऔर इसी तरह के पुस्तकालय कोड इस तथ्य का फायदा उठाते हैं। यहाँ दिया गया है कि यह कैसे काम करता है:

के रूप में एक लैम्ब्डा एक वर्ग के बराबर है जिसमें operator()अतिभारित, स्टेटलेस लैम्ब्डा (एक []कैप्चर सूची के साथ) सभी खाली वर्ग हैं। वे sizeofकी 1। वास्तव में, यदि आप उनसे विरासत में प्राप्त हुए हैं (जो अनुमति दी गई है!), वे तब तक कोई स्थान नहीं लेंगे, जब तक कि वह एक ही प्रकार के पते की टक्कर का कारण न बने । (इसे खाली आधार अनुकूलन के रूप में जाना जाता है)।

template<class T>
struct toy:T {
  toy(toy const&)=default;
  toy(toy &&)=default;
  toy(T const&t):T(t) {}
  toy(T &&t):T(std::move(t)) {}
  int state = 0;
};

template<class Lambda>
toy<Lambda> make_toy( Lambda const& l ) { return {l}; }

sizeof(make_toy( []{std::cout << "hello world!\n"; } ))है sizeof(int)(अच्छी तरह से, ऊपर अवैध है, क्योंकि आप एक गैर का मूल्यांकन संदर्भ में एक लैम्ब्डा नहीं बना सकते: यदि आप एक नामित बनाने के लिए auto toy = make_toy(blah);तो sizeof(blah)है, लेकिन वह सिर्फ शोर है)। sizeof([]{std::cout << "hello world!\n"; })अभी भी 1(समान योग्यता) है।

यदि हम एक और खिलौना प्रकार बनाते हैं:

template<class T>
struct toy2:T {
  toy2(toy2 const&)=default;
  toy2(T const&t):T(t), t2(t) {}
  T t2;
};
template<class Lambda>
toy2<Lambda> make_toy2( Lambda const& l ) { return {l}; }

यह मेमने की दो प्रतियां हैं । के रूप में वे एक ही पता साझा नहीं कर सकते, sizeof(toy2(some_lambda))है 2!


6
Nit: एक फ़ंक्शन पॉइंटर एक शून्य * से छोटा हो सकता है। दो ऐतिहासिक उदाहरण: सबसे पहले शब्द पता मशीनें जहां आकार (शून्य *) == आकार (चार *)> आकार (संरचना *) == आकार (अंतर *)। (void * और char * को किसी शब्द के भीतर ऑफ़सेट रखने के लिए कुछ अतिरिक्त बिट्स की आवश्यकता होती है) एक फ़ंक्शन पॉइंटर केवल 16 बिट्स था)।
मार्टिन बोनर ने

1
@ स्मार्ट सच। अतिरिक्त ()जोड़ा गया।
यक्क - एडम नेवरामोंट

50

एक लैम्ब्डा कोई फ़ंक्शन पॉइंटर नहीं है।

एक लंबोदर एक वर्ग का एक उदाहरण है। आपका कोड लगभग बराबर है:

class f_lambda {
public:

  auto operator() { return 17; }
};

f_lambda f;
std::cout << f() << std::endl;
std::cout << &f << std::endl;
std::cout << sizeof(f) << std::endl;

आंतरिक वर्ग जो लैम्बडा का प्रतिनिधित्व करता है, उसके पास कोई वर्ग सदस्य नहीं है, इसलिए इसका sizeof()1 (यह 0 नहीं हो सकता है, अन्य कारणों से पर्याप्त रूप से कहा गया है )।

यदि आपका लैम्ब्डा कुछ चर पर कब्जा करने के लिए था, तो वे कक्षा के सदस्यों के बराबर होंगे, और आपका sizeof()तदनुसार संकेत देगा।


3
क्या आप "अन्यत्र" से लिंक कर सकते हैं, जो बताता है कि sizeof()0 क्यों नहीं हो सकता है?
user1717828

26

आपका कंपाइलर कमोबेश निम्न प्रकार के लैम्बडा का अनुवाद करता है:

struct _SomeInternalName {
    int operator()() { return 17; }
};

int main()
{
     _SomeInternalName f;
     std::cout << f() << std::endl;
}

चूंकि उस संरचना में कोई गैर-स्थैतिक सदस्य नहीं है, इसलिए इसका आकार खाली संरचना के समान है, जो कि है 1

जैसे ही आप अपने लैम्ब्डा में एक गैर-खाली कैप्चर सूची जोड़ते हैं, तो यह परिवर्तन होता है:

int i = 42;
auto f = [i]() { return i; };

जिसका अनुवाद होगा

struct _SomeInternalName {
    int i;
    _SomeInternalName(int outer_i) : i(outer_i) {}
    int operator()() { return i; }
};


int main()
{
     int i = 42;
     _SomeInternalName f(i);
     std::cout << f() << std::endl;
}

चूंकि उत्पन्न संरचना को अब intकब्जा करने के लिए एक गैर-स्थिर सदस्य को संग्रहीत करने की आवश्यकता है , इसलिए इसका आकार बड़ा हो जाएगाsizeof(int) । जैसे-जैसे आप अधिक सामान कैप्चर करेंगे, आकार बढ़ता रहेगा।

(कृपया नमक के एक दाने के साथ संरचना सादृश्य लें। हालांकि यह इस बात का एक अच्छा तरीका है कि लैम्ब्डा आंतरिक रूप से कैसे काम करता है, यह कंपाइलर क्या करेगा इसका शाब्दिक अनुवाद नहीं है)


12

लांबा नहीं होना चाहिए, म्यूटमम में, इसके कार्यान्वयन के लिए एक संकेतक?

जरुरी नहीं। मानक के अनुसार, अद्वितीय, अनाम वर्ग का आकार कार्यान्वयन-परिभाषित है[Expr.prim.lambda] से अंश , C ++ 14 (जोर मेरा):

लंबोदर-अभिव्यक्ति का प्रकार (जो कि क्लोजर ऑब्जेक्ट का प्रकार भी है) एक अद्वितीय, अनाम अनाम वर्ग प्रकार है - जिसे क्लोजर प्रकार कहा जाता है - जिसके गुणों का वर्णन नीचे किया गया है।

[...]

एक कार्यान्वयन क्लोजर प्रकार को अलग-अलग परिभाषित कर सकता है जो नीचे वर्णित है, बशर्ते कि यह कार्यक्रम के अवलोकन योग्य व्यवहार को बदल नहीं सकता है :

- क्लोजर प्रकार का आकार और / या संरेखण ,

- क्या क्लोजर प्रकार तुच्छ रूप से प्रतिलिपि योग्य है (क्लाज 9),

- क्या क्लोजर प्रकार एक मानक-लेआउट वर्ग (क्लाज 9) है, या

- क्या क्लोजर टाइप POD क्लास है (क्लाज 9)

आपके मामले में - आपके द्वारा उपयोग किए जाने वाले कंपाइलर के लिए - आपको 1 का आकार मिलता है, जिसका मतलब यह नहीं है कि यह तय है। यह विभिन्न संकलक कार्यान्वयनों के बीच भिन्न हो सकता है।


क्या आपको यकीन है कि यह बिट लागू होता है? कैप्चर-समूह के बिना एक लैम्ब्डा वास्तव में एक "बंद" नहीं है। (क्या मानक खाली-कैप्चर-ग्रुप लंबोदा को "क्लोजर" के रूप में वैसे भी संदर्भित करता है?)
काइल स्ट्रैंड

1
हाँ यह करता है। यह वही है जो मानक कहता है " लैम्ब्डा-एक्सप्रेशन के मूल्यांकन से एक अस्थायी परिणाम निकलता है। इस अस्थायी को क्लोजर ऑब्जेक्ट कहा जाता है। ", कैप्चर करना या न करना, यह एक क्लोजर ऑब्जेक्ट है, बस एक अपवैलिटी से शून्य हो जाएगा।
किंवदंतियां 2

मैंने डाउनवोट नहीं किया, लेकिन संभवतः डाउनवॉटर को लगता है कि यह उत्तर मूल्यवान नहीं है क्योंकि यह नहीं बताता है कि यह क्यों संभव है (सैद्धांतिक दृष्टिकोण से, मानकों के नजरिए से नहीं) एक रन-टाइम पॉइंटर को शामिल किए बिना लैम्ब्डा को लागू करना। कॉल-ऑपरेटर फ़ंक्शन। (प्रश्न के तहत केरेकेएसबी के साथ मेरी चर्चा देखें।)
काइल स्ट्रैंड

7

से http://en.cppreference.com/w/cpp/language/lambda :

लैम्ब्डा अभिव्यक्ति अद्वितीय अनाम गैर-यूनियन गैर-एग्रीगेट वर्ग प्रकार के एक अनाम प्रचलित अस्थायी ऑब्जेक्ट का निर्माण करती है, जिसे क्लोजर प्रकार के रूप में जाना जाता है , जिसे (ADL के प्रयोजनों के लिए) सबसे छोटे ब्लॉक स्कोप, क्लास स्कोप, या नेमस्पेस स्कोप में शामिल किया गया है। मेमने की अभिव्यक्ति।

यदि लैम्ब्डा-अभिव्यक्ति किसी भी चीज़ को कॉपी द्वारा कैप्चर करता है (या तो कैप्चर क्लॉज़ के साथ [=] या स्पष्ट रूप से कैप्चर के साथ जिसमें कैरेक्टर और जैसे, [a, b, c]) शामिल नहीं है , क्लोजर प्रकार में अनाम गैर-स्थैतिक डेटा शामिल है सदस्यों , अनिर्दिष्ट आदेश में घोषित, जो सभी संस्थाओं की प्रतियों को रखते हैं जो इतने पर कब्जा कर लिया गया था।

संदर्भों द्वारा कैप्चर की गई संस्थाओं के लिए (डिफ़ॉल्ट कैप्चर [और] के साथ या चरित्र का उपयोग करते समय और, जैसे [और ए, और बी, और सी]), यह अनिर्दिष्ट है यदि अतिरिक्त डेटा सदस्यों को क्लोजर प्रकार में घोषित किया जाता है

से http://en.cppreference.com/w/cpp/language/sizeof

एक खाली वर्ग प्रकार पर लागू होने पर, हमेशा 1 रिटर्न देता है।

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