क्या सूचक को पारित करने के बजाय सी में मूल्य के आधार पर संरचना को पारित करने के लिए कोई डाउनसाइड है?


157

क्या सूचक को पारित करने के बजाय सी में मूल्य के आधार पर संरचना को पारित करने के लिए कोई डाउनसाइड है?

यदि संरचना बड़ी है, तो स्पष्ट रूप से बहुत सारे डेटा की नकल करने का प्रदर्शन पहलू है, लेकिन एक छोटी संरचना के लिए, यह मूल रूप से एक फ़ंक्शन के लिए कई मानों को पारित करने के समान होना चाहिए।

यह शायद और भी दिलचस्प है जब वापसी मूल्यों के रूप में उपयोग किया जाता है। C में केवल फ़ंक्शन से एकल वापसी मान हैं, लेकिन आपको अक्सर कई की आवश्यकता होती है। तो एक सरल उपाय यह है कि उन्हें एक संरचना में रखा जाए और उसे वापस लौटाया जाए।

क्या इसके कोई कारण हैं या इसके खिलाफ हैं?

चूंकि यह हर किसी के लिए स्पष्ट नहीं हो सकता है कि मैं यहां किस बारे में बात कर रहा हूं, मैं एक सरल उदाहरण दूंगा।

यदि आप C में प्रोग्रामिंग कर रहे हैं, तो आप जल्दी या बाद में इस तरह दिखने वाले फ़ंक्शन लिखना शुरू करेंगे:

void examine_data(const char *ptr, size_t len)
{
    ...
}

char *p = ...;
size_t l = ...;
examine_data(p, l);

यह कोई समस्या नहीं है। एकमात्र मुद्दा यह है कि आपको अपने सहकर्मी के साथ सहमत होना होगा जिसमें पैरामीटर होना चाहिए ताकि आप सभी कार्यों में समान सम्मेलन का उपयोग कर सकें।

लेकिन जब आप उसी तरह की जानकारी वापस करना चाहते हैं तो क्या होता है? आपको आमतौर पर कुछ इस तरह मिलता है:

char *get_data(size_t *len);
{
    ...
    *len = ...datalen...;
    return ...data...;
}
size_t len;
char *p = get_data(&len);

यह ठीक काम करता है, लेकिन बहुत अधिक समस्याग्रस्त है। एक वापसी मूल्य एक वापसी मूल्य है, सिवाय इसके कि इस कार्यान्वयन में यह नहीं है। ऊपर से यह बताने का कोई तरीका नहीं है कि फ़ंक्शन get_data को यह देखने की अनुमति नहीं है कि लेन किस बिंदु पर है। और ऐसा कुछ भी नहीं है जो संकलक की जांच करता है कि मूल्य वास्तव में उस सूचक के माध्यम से वापस आ गया है। इसलिए अगले महीने, जब कोई और इसे ठीक से समझे बिना कोड को संशोधित करता है (क्योंकि उसने दस्तावेज़ीकरण नहीं पढ़ा था?) यह बिना किसी सूचना के टूट जाता है, या यह बेतरतीब ढंग से दुर्घटनाग्रस्त होने लगता है।

इसलिए, मैं जो प्रस्ताव प्रस्तुत करता हूं वह सरल संरचना है

struct blob { char *ptr; size_t len; }

उदाहरण इस तरह से लिखे जा सकते हैं:

void examine_data(const struct blob data)
{
    ... use data.tr and data.len ...
}

struct blob = { .ptr = ..., .len = ... };
examine_data(blob);

struct blob get_data(void);
{
    ...
    return (struct blob){ .ptr = ...data..., .len = ...len... };
}
struct blob data = get_data();

किसी कारण से, मुझे लगता है कि ज्यादातर लोग सहज रूप से check_data को एक पॉवर ब्लॉब में ले जाते हैं, लेकिन मैं ऐसा नहीं करता। यह अभी भी एक सूचक और पूर्णांक प्राप्त करता है, यह सिर्फ इतना स्पष्ट है कि वे एक साथ चलते हैं। और गेट_डेटा मामले में मैंने पहले वर्णित तरीके से गड़बड़ करना असंभव है, क्योंकि लंबाई के लिए कोई इनपुट मूल्य नहीं है, और एक लौटी हुई लंबाई होनी चाहिए।


जो इसके लायक है, void examine data(const struct blob)वह गलत है।
क्रिस लुत्ज़

धन्यवाद, इसे एक चर नाम शामिल करने के लिए बदल दिया।
dkagedal

1
"ऊपर से यह बताने का कोई तरीका नहीं है कि फ़ंक्शन get_data को यह देखने की अनुमति नहीं है कि लेन किस बिंदु पर है। और ऐसा कुछ भी नहीं है जो कंपाइलर की जांच करता है कि मूल्य वास्तव में उस पॉइंटर के माध्यम से वापस आ गया है।" - इससे मुझे कोई मतलब नहीं है (शायद क्योंकि आपका उदाहरण एक फ़ंक्शन के बाहर दिखाई देने वाली अंतिम दो पंक्तियों के कारण अमान्य कोड है); क्या आप विस्तृत कर सकते हैं?
एडम स्पियर्स

2
फ़ंक्शन के नीचे की दो पंक्तियाँ यह वर्णन करने के लिए हैं कि फ़ंक्शन को कैसे कहा जाता है। फ़ंक्शन हस्ताक्षर इस तथ्य को कोई संकेत नहीं देता है कि कार्यान्वयन केवल सूचक को लिखना चाहिए। और संकलक के पास यह जानने का कोई तरीका नहीं है कि यह सत्यापित करना चाहिए कि एक मान सूचक को लिखा गया है, इसलिए रिटर्न वैल्यू तंत्र केवल प्रलेखन में वर्णित किया जा सकता है।
18

1
C में लोग अक्सर ऐसा नहीं करते इसकी बड़ी वजह ऐतिहासिक है। C89 से पहले, आप मान द्वारा स्ट्रक्चर्स को पास या वापस नहीं कर सकते हैं , इसलिए सभी सिस्टम इंटरफेस को C89 से पूर्ववर्ती करते हैं और तार्किक रूप से gettimeofdayइसके बजाय (जैसे ) पॉइंटर्स का उपयोग करना चाहते हैं , और लोग इसे एक उदाहरण के रूप में लेते हैं।
zwol

जवाबों:


202

मूल्य से गुजरने वाली छोटी संरचनाओं (जैसे बिंदु, आयत) के लिए पूरी तरह से स्वीकार्य है। लेकिन, गति के अलावा, एक और कारण है कि आपको मूल्य द्वारा बड़ी संरचनाओं को पारित करने / वापस करने में सावधानी बरतनी चाहिए: स्टैक स्थान।

बहुत सी प्रोग्रामिंग एम्बेडेड सिस्टम के लिए है, जहाँ मेमोरी एक प्रीमियम पर होती है, और स्टैक का आकार KB या बाइट्स में भी मापा जा सकता है ... यदि आप मान द्वारा स्ट्रक्चर्स पास कर रहे हैं या लौटा रहे हैं, तो उन स्ट्रक्चर्स की प्रतियों को रखा जाएगा। स्टैक, संभावित रूप से उस स्थिति का कारण बनता है जिसे इस साइट का नाम दिया गया है ...

यदि मुझे एक ऐसा अनुप्रयोग दिखाई देता है जिसमें बहुत अधिक स्टैक का उपयोग होता है, तो मूल्य द्वारा पारित संरचनाएं उन चीजों में से एक हैं जिन्हें मैं पहले देखता हूं।


2
"यदि आप मान से स्ट्रक्चर्स पास कर रहे हैं या लौटा रहे हैं, तो उन स्ट्रक्चर्स की प्रतियां स्टैक पर रखी जाएंगी" मैं किसी भी टूलकिन को ऐसा करने के लिए कहता हूं । हां, यह दुखद है कि इतने सारे लोग ऐसा करेंगे, लेकिन यह कुछ भी नहीं है कि सी मानक कॉल करता है। एक समझदार संकलक इसे सभी को अनुकूलित करेगा।
मोनिका

1
@ क्यूबाओबर यही कारण है कि अक्सर ऐसा नहीं होता है: stackoverflow.com/questions/552134/…
रोडी

1
क्या कोई निश्चित रेखा है जो एक छोटी संरचना को एक बड़ी संरचना से अलग करती है?
जोसी थॉम्पसन

63

ऐसा नहीं करने का एक कारण जिसका उल्लेख नहीं किया गया है वह यह है कि यह एक समस्या पैदा कर सकता है जहां द्विआधारी संगतता मायने रखती है।

उपयोग किए गए संकलक के आधार पर, संरचनाओं को स्टैक या रजिस्टरों के माध्यम से संकलक विकल्पों / कार्यान्वयन के आधार पर पारित किया जा सकता है

देखें: http://gcc.gnu.org/oniltocs/gcc/Code-Gen-Options.html

-fpcc-struct-रिटर्न

-freg-struct-रिटर्न

यदि दो संकलक असहमत हैं, तो चीजें उड़ सकती हैं। यह कहने की आवश्यकता नहीं है कि ऐसा नहीं करने के मुख्य कारणों में स्टैक खपत और प्रदर्शन कारण हैं।


4
यह उसी तरह का जवाब था जिसकी मुझे तलाश थी।
dkagedal

2
यह सच है, लेकिन वे विकल्प पास-दर-मूल्य से संबंधित नहीं हैं। वे लौटने वाली संरचनाओं से संबंधित हैं जो पूरी तरह से एक अलग चीज है। संदर्भ द्वारा चीजों को वापस करना आमतौर पर दोनों पैरों में अपने आप को गोली मारने का एक निश्चित आग तरीका है। int &bar() { int f; int &j(f); return j;};
रॉडी

19

करने के लिए वास्तव में विधानसभा भूमि में गहरी खुदाई करने के लिए, इस सवाल का जवाब एक की जरूरत है:

(निम्नलिखित उदाहरण x86_64 पर gcc का उपयोग करता है। MSVC, ARM, आदि जैसे अन्य आर्किटेक्चर को जोड़ने के लिए किसी का भी स्वागत है।)

चलो हमारे उदाहरण कार्यक्रम है:

// foo.c

typedef struct
{
    double x, y;
} point;

void give_two_doubles(double * x, double * y)
{
    *x = 1.0;
    *y = 2.0;
}

point give_point()
{
    point a = {1.0, 2.0};
    return a;
}

int main()
{
    return 0;
}

इसे पूर्ण अनुकूलन के साथ संकलित करें

gcc -Wall -O3 foo.c -o foo

विधानसभा को देखें:

objdump -d foo | vim -

यह वही है जो हमें मिलता है:

0000000000400480 <give_two_doubles>:
    400480: 48 ba 00 00 00 00 00    mov    $0x3ff0000000000000,%rdx
    400487: 00 f0 3f 
    40048a: 48 b8 00 00 00 00 00    mov    $0x4000000000000000,%rax
    400491: 00 00 40 
    400494: 48 89 17                mov    %rdx,(%rdi)
    400497: 48 89 06                mov    %rax,(%rsi)
    40049a: c3                      retq   
    40049b: 0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

00000000004004a0 <give_point>:
    4004a0: 66 0f 28 05 28 01 00    movapd 0x128(%rip),%xmm0
    4004a7: 00 
    4004a8: 66 0f 29 44 24 e8       movapd %xmm0,-0x18(%rsp)
    4004ae: f2 0f 10 05 12 01 00    movsd  0x112(%rip),%xmm0
    4004b5: 00 
    4004b6: f2 0f 10 4c 24 f0       movsd  -0x10(%rsp),%xmm1
    4004bc: c3                      retq   
    4004bd: 0f 1f 00                nopl   (%rax)

noplपैड को छोड़कर , give_two_doubles()27 बाइट्स हैं जबकि give_point()29 बाइट्स हैं। दूसरी ओर, give_point()एक से कम निर्देश देता हैgive_two_doubles()

दिलचस्प बात यह है कि हम नोटिस संकलक का अनुकूलन करने में सक्षम रहा है movतेजी से SSE2 वेरिएंट में movapdऔर movsd। इसके अलावा, give_two_doubles()वास्तव में मेमोरी से डेटा को अंदर और बाहर ले जाता है, जिससे चीजें धीमी हो जाती हैं।

जाहिरा तौर पर इसका ज्यादातर हिस्सा एम्बेडेड परिवेशों में लागू नहीं हो सकता है (जो कि C के लिए खेल का मैदान अब अधिकांश समय है)। मैं एक विधानसभा जादूगर नहीं हूँ इसलिए किसी भी टिप्पणी का स्वागत किया जाएगा!


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

6
@ डेडकल: सच। पूर्वव्यापी में, मुझे लगता है कि मेरा अपना उत्तर बहुत खराब लिखा गया था। हालाँकि मैंने बहुत से निर्देशों पर ध्यान केंद्रित नहीं किया था (डननो ने आपको वह धारणा क्यों दी: पी), बनाने के लिए वास्तविक बिंदु यह था कि मूल्य से संरचना को पारित करना छोटे प्रकारों के संदर्भ में पारित करने के लिए बेहतर है। वैसे भी, मूल्य से गुजरना पसंद किया जाता है क्योंकि यह सरल है (आजीवन बाजीगरी नहीं, किसी को अपने डेटा या constहर समय बदलने के बारे में चिंता करने की ज़रूरत नहीं है ) और मैंने पाया कि पास-दर-मूल्य की नकल में बहुत अधिक प्रदर्शन जुर्माना नहीं है (यदि लाभ नहीं है) , इसके विपरीत जो कई विश्वास कर सकते हैं।
kizzx2

15

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

बस जवाब को पूरा करने के लिए ( रोडी को पूरा क्रेडिट ) स्टैक का उपयोग एक और कारण है जो संरचना को मूल्य से पारित नहीं करता है, मेरा विश्वास है कि स्टैक ओवरफ्लो डिबगिंग वास्तविक पीटीए है।

टिप्पणी करने के लिए फिर से खेलना:

पॉइंटर से स्ट्रक्चर पास करने का अर्थ है कि कुछ इकाई का इस वस्तु पर स्वामित्व है और उन्हें क्या और कब जारी किया जाना चाहिए, इसकी पूरी जानकारी है। मूल्य द्वारा संरचना को पार करना संरचना के आंतरिक डेटा (किसी अन्य संरचना आदि के लिए संकेत) के लिए एक छिपा संदर्भ बनाता है।) इस पर बनाए रखना मुश्किल है (संभव है लेकिन क्यों?)।


6
लेकिन एक पॉइंटर को पास करना अधिक "खतरनाक" नहीं है क्योंकि आप इसे एक संरचना में रखते हैं, इसलिए मैं इसे नहीं खरीदता हूं।
11:18 बजे dkagedal

एक संरचना की नकल करने पर महान बिंदु जिसमें एक सूचक होता है। यह बिंदु बहुत स्पष्ट नहीं हो सकता है। उन लोगों के लिए जो यह नहीं जानते कि वे किस चीज का जिक्र कर रहे हैं, गहरी नकल बनाम उथली प्रति पर एक खोज करें।
झुरोपा

1
C फ़ंक्शन सम्मेलनों में से एक इनपुट मापदंडों से पहले आउटपुट मापदंडों को सूचीबद्ध करना है, उदाहरण के लिए int func (char * out, char * in);
झुरोपा

आपका मतलब है कि कैसे उदाहरण के लिए getaddrinfo () आउटपुट पैरामीटर को अंतिम रखता है? :-) सम्मेलनों के एक हज़ार सेट हैं, और आप जो चाहें चुन सकते हैं।
dkagedal

10

एक बात यहाँ के लोग अब तक उल्लेख करना भूल गए हैं (या मैंने इसे नजरअंदाज कर दिया है) यह है कि आमतौर पर संरचनाओं में एक पैडिंग होती है!

struct {
  short a;
  char b;
  short c;
  char d;
}

हर चार 1 बाइट है, हर शॉर्ट 2 बाइट्स है। संरचना कितनी बड़ी है? नहीं, यह 6 बाइट्स नहीं है। कम से कम किसी भी अधिक इस्तेमाल किए जाने वाले सिस्टम पर नहीं। अधिकांश प्रणालियों पर यह होगा 8. समस्या यह है कि संरेखण स्थिर नहीं है, यह प्रणाली पर निर्भर है, इसलिए एक ही संरचना में अलग-अलग संरेखण और विभिन्न प्रणालियों पर अलग-अलग आकार होंगे।

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


2
हाँ, लेकिन पैडिंग मूल्य या संदर्भ द्वारा संरचना को पारित करने पर निर्भरता के साथ मौजूद है।
इल्या

2
@ डेडग्रेडल: "अलग-अलग सिस्टम पर अलग-अलग आकार" का कौन सा हिस्सा आपको समझ नहीं आया? सिर्फ इसलिए कि यह आपके सिस्टम का तरीका है, आप यह मान लेते हैं कि यह किसी अन्य के लिए समान होना चाहिए - यही कारण है कि आपको मूल्य से नहीं गुजरना चाहिए। परिवर्तित नमूना तो यह आपके सिस्टम पर भी विफल रहता है।
मीक़ी

2
मुझे लगता है कि स्ट्रक्चर पैडिंग के बारे में मेकी की टिप्पणी विशेष रूप से एम्बेडेड सिस्टम के लिए प्रासंगिक है जहां स्टैक का आकार एक मुद्दा हो सकता है।
झुरोपा

1
मुझे लगता है कि तर्क का दूसरा पहलू यह है कि यदि आपकी संरचना एक साधारण संरचना है (आदिम प्रकार के एक जोड़े से युक्त), तो मूल्य से गुजरने से कंपाइलर इसे रजिस्टरों का उपयोग करके इसे हथकंडा करने में सक्षम होगा - जबकि यदि आप पॉइंटर्स का उपयोग करते हैं, तो चीजें समाप्त हो जाती हैं। स्मृति, जो धीमी है। अगर इन चिड़ियों में से कोई भी चीज़ बहुत कम-स्तर और बहुत अधिक आपके लक्ष्य वास्तुकला पर निर्भर करती है।
kizzx2

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

9

मुझे लगता है कि आपके प्रश्न ने चीजों को बहुत अच्छी तरह से अभिव्यक्त किया है।

मूल्य द्वारा संरचना को पारित करने का एक अन्य लाभ यह है कि स्मृति स्वामित्व स्पष्ट है। इस बारे में कोई सोच नहीं है कि क्या संरचना ढेर से है, और इसे मुक्त करने की जिम्मेदारी किसकी है।


9

मैं कहूंगा कि मान (मान और रिटर्न मान) दोनों के द्वारा (न-बहुत बड़े) स्ट्रक्चर्स गुजर रहे हैं, एक पूरी तरह से वैध तकनीक है। एक को ध्यान रखना है, निश्चित रूप से, कि संरचना या तो एक पीओडी प्रकार है, या कॉपी शब्दार्थ अच्छी तरह से निर्दिष्ट हैं।

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


ध्यान दें कि मेरा प्रश्न C के बारे में था, C ++ के बारे में नहीं।
dkagedal

यह फ़ंक्शन से संरचना को वापस करने के लिए वैध है उपयोगी नहीं है :)
इल्या

1
मुझे फ़ंक्शन से डेटा वापस करने के लिए एक त्रुटि कोड और मापदंडों के रूप में रिटर्न का उपयोग करने के लिए llya का सुझाव पसंद है।
झुरोपा

8

यहां कुछ ऐसा है जिसका कोई उल्लेख नहीं किया गया है:

void examine_data(const char *c, size_t l)
{
    c[0] = 'l'; // compiler error
}

void examine_data(const struct blob blob)
{
    blob.ptr[0] = 'l'; // perfectly legal, quite likely to blow up at runtime
}

एक के सदस्य const structहैं const, लेकिन अगर वह सदस्य एक सूचक (जैसे char *) है, तो यह वास्तव char *constमें const char *हम चाहते हैं के बजाय हो जाता है । बेशक, हम यह मान सकते हैं कि constइरादे का दस्तावेज़ीकरण है, और जो कोई भी इसका उल्लंघन करता है, वह बुरा कोड लिख रहा है (जो वे हैं), लेकिन यह कुछ के लिए पर्याप्त नहीं है (विशेषकर उन लोगों के लिए जो केवल चार घंटे बिताते हैं, जो एक कारण का पता लगाते हैं) दुर्घटना)।

इसका विकल्प यह हो सकता है struct const_blob { const char *c; size_t l }कि आप इसका उपयोग करें और इसका उपयोग करें, लेकिन यह गड़बड़ है - यह उसी नामकरण-स्कीम समस्या में शामिल हो जाता है, जो मेरे पास है typedef। इस प्रकार, ज्यादातर लोग केवल दो मापदंडों (या, इस मामले के लिए अधिक संभावना है, एक स्ट्रिंग पुस्तकालय का उपयोग करके) से चिपके रहते हैं।


हां यह पूरी तरह से कानूनी है, और यह भी कि आप कभी-कभी करना चाहते हैं। लेकिन मैं मानता हूं कि यह संरचना समाधान की एक सीमा है कि आप उन बिंदुओं को नहीं बना सकते हैं जो वे बिंदु को इंगित करते हैं।
dkagedal

struct const_blobसमाधान के साथ एक गंदा गोटा यह है कि भले ही const_blobऐसे सदस्य हों जो blob"अप्रत्यक्ष-कास्ट-नेस" से भिन्न हों, सख्त अलियासिंग शासन के प्रयोजनों के लिए struct blob*एक प्रकार के प्रकार को struct const_blob*अलग माना जाएगा। नतीजतन, अगर कोड डाले एक blob*एक करने के लिए const_blob*, एक प्रकार का उपयोग कर चुपचाप अन्य प्रकार, जैसे कि किसी भी उपयोग अपरिभाषित व्यवहार लागू करेगा के किसी भी मौजूदा संकेत को अमान्य कर देंगे अंतर्निहित संरचना को बाद में किसी भी लिखने (जो आमतौर पर हानिरहित हो सकता है, लेकिन घातक हो सकता है) ।
सुपरकैट

5

Http://www.drpaulcarter.com/pcasm/ पर पीसी असेंबली ट्यूटोरियल के पेज 150 में स्पष्ट विवरण दिया गया है कि कैसे सी एक फंक्शन को वापस लौटने की अनुमति देता है:

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

उपरोक्त कथन को सत्यापित करने के लिए मैं निम्नलिखित C कोड का उपयोग करता हूं:

struct person {
    int no;
    int age;
};

struct person create() {
    struct person jingguo = { .no = 1, .age = 2};
    return jingguo;
}

int main(int argc, const char *argv[]) {
    struct person result;
    result = create();
    return 0;
}

C कोड के इस टुकड़े के लिए असेंबली बनाने के लिए "gcc -S" का उपयोग करें:

    .file   "foo.c"
    .text
.globl create
    .type   create, @function
create:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $16, %esp
    movl    8(%ebp), %ecx
    movl    $1, -8(%ebp)
    movl    $2, -4(%ebp)
    movl    -8(%ebp), %eax
    movl    -4(%ebp), %edx
    movl    %eax, (%ecx)
    movl    %edx, 4(%ecx)
    movl    %ecx, %eax
    leave
    ret $4
    .size   create, .-create
.globl main
    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $20, %esp
    leal    -8(%ebp), %eax
    movl    %eax, (%esp)
    call    create
    subl    $4, %esp
    movl    $0, %eax
    leave
    ret
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
    .section    .note.GNU-stack,"",@progbits

कॉल बनाने से पहले स्टैक:

        +---------------------------+
ebp     | saved ebp                 |
        +---------------------------+
ebp-4   | age part of struct person | 
        +---------------------------+
ebp-8   | no part of struct person  |
        +---------------------------+        
ebp-12  |                           |
        +---------------------------+
ebp-16  |                           |
        +---------------------------+
ebp-20  | ebp-8 (address)           |
        +---------------------------+

कॉल करने के बाद स्टैक सही:

        +---------------------------+
        | ebp-8 (address)           |
        +---------------------------+
        | return address            |
        +---------------------------+
ebp,esp | saved ebp                 |
        +---------------------------+

2
यहां पर दो समस्याएं हैं। सबसे स्पष्ट एक यह है कि यह बिल्कुल नहीं वर्णन करता है "सी कैसे एक फ़ंक्शन को एक संरचना को वापस करने की अनुमति देता है"। यह केवल वर्णन करता है कि यह 32-बिट x86 हार्डवेयर पर कैसे किया जा सकता है, जो कि सबसे सीमित आर्किटेक्चर में से एक होता है जब आप रजिस्टर आदि की संख्या को देखते हैं। दूसरी समस्या यह है कि सी कंपाइलर रिटर्न मान के लिए कोड उत्पन्न करता है। एबीआई द्वारा निर्देशित किया जाता है (गैर-निर्यात या इनलाइन कार्यों को छोड़कर)। और वैसे, इनलाइन फ़ंक्शन संभवतः उन स्थानों में से एक हैं, जहां से लौटने वाली संरचनाएं सबसे उपयोगी हैं।
dkagedal

सुधार के लिए धन्यवाद। कॉलिंग कन्वेंशन की पूरी विस्तृत जानकारी के लिए, en.wikipedia.org/wiki/Calling_convention एक अच्छा संदर्भ है।
जिंगुगो याओ

@ डेडक्रेडल: जो महत्वपूर्ण है वह यह नहीं है कि x86 इस तरह से चीजें करने के लिए होता है, बल्कि यह है कि "सार्वभौमिक" दृष्टिकोण (यानी यह एक) मौजूद है जो किसी भी मंच के लिए कंपाइलरों को किसी भी संरचना प्रकार के रिटर्न का समर्थन करने की अनुमति देगा जो isn ' स्टैक को उड़ाने के लिए इतना बड़ा हालांकि कई प्लेटफ़ॉर्म के लिए कंपाइलर कुछ संरचना-प्रकार के रिटर्न मानों को संभालने के लिए अन्य अधिक कुशल साधनों का उपयोग करेंगे, लेकिन संरचना के रिटर्न प्रकारों को सीमित करने के लिए भाषा की कोई आवश्यकता नहीं है जो प्लेटफ़ॉर्म को बेहतर तरीके से संभाल सकते हैं।
सुपरकैट

0

मैं सिर्फ आपके स्ट्रक्चर्स को वैल्यू द्वारा पास करने का एक फायदा बताना चाहता हूं कि एक ऑप्टिमाइज़िंग कंपाइलर आपके कोड को बेहतर तरीके से ऑप्टिमाइज़ कर सकता है।

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