2015-06-28 4 views
6

मैं फ़ंक्शंस के साथ थोड़ा सा प्रयोग कर रहा हूं और मुझे पता चला है कि तर्कों का क्रम स्मृति में उलट दिया गया है। ऐसा क्यों है?फ़ंक्शन तर्कों का क्रम क्यों उलट दिया गया है?

ढेर-test.cpp:

#include <stdio.h> 

void test(int a, int b, int c) { 
    printf("%p %p %p\n", &a, &b, &c); 
    printf("%d %d\n", *(&b - 1), *(&b + 1)); 
} 

int main() { 
    test(1,2,3); 
    return 0; 
} 

बजना:

$ clang++ stack-test.cpp && ./a.out 
0x7fffb9bb816c 0x7fffb9bb8168 0x7fffb9bb8164 
3 1 

जीसीसी:

$ g++ stack-test.cpp && ./a.out 
0x7ffe0b983b3c 0x7ffe0b983b38 0x7ffe0b983b34 
3 1 

संपादित करें: नकल नहीं: मूल्यांकन के आदेश स्मृति लेआउट से अलग हो सकता है , तो यह एक अलग सवाल है।

+0

संभव डुप्लिकेट: // stackoverflow।कॉम/प्रश्न/621542/कंपाइलर-एंड-तर्क-ऑर्डर-ऑफ-मूल्यांकन-इन-सी) – Steephen

+1

@ स्टेफेन मूल्यांकन का आदेश, वास्तव में? – kravemir

+0

@Miro यह कार्यान्वयन विशिष्ट है और संकलक – Steephen

उत्तर

7

बुला सम्मेलन कार्यान्वयन पर निर्भर करता है।

लेकिन सी परिवर्तनीय कार्यों का समर्थन करने के लिए (औपचारिक तर्क सूची में ... अंडाकार के साथ व्यक्त सी ++ में) तर्क आमतौर पर धक्का दिया जाता है, या दाएं से बाएं क्रम में, उनके लिए स्टैक स्पेस आरक्षित होता है। इसे आमतौर पर (1)सी कॉलिंग सम्मेलन कहा जाता है। इस सम्मेलन के साथ, और सामान्य सम्मेलन कि मशीन स्टैक स्मृति में नीचे की ओर बढ़ता है, पहला तर्क आपके परिणाम के विपरीत, सबसे कम पते पर समाप्त होना चाहिए।

और जब मैं आपके प्रोग्राम को MinGW g ++ 5 के साथ संकलित करता हूं।1, जो 64-बिट, मैं

000000000023FE30 000000000023FE38 000000000023FE40 

मिलता और जब मैं 32-बिट विजुअल C++ 2015 के साथ अपने कार्यक्रम संकलन, मैं

00BFFC5C 00BFFC60 00BFFC64 

और दोनों मिल उन परिणामों में से सी परिणाम कॉलिंग सम्मेलन के अनुरूप हैं, आपके परिणाम के विपरीत।

तो निष्कर्ष यह प्रतीत होता है कि आपका कंपाइलर कम से कम गैर-परिवर्तनीय कार्यों के लिए सी कॉलिंग सम्मेलन से कुछ और के लिए डिफ़ॉल्ट है।

आप औपचारिक तर्क सूची के अंत में ... जोड़कर इसका परीक्षण कर सकते हैं।


1) सी बुला सम्मेलन भी शामिल है कि यह फोन करने वाले है कि ढेर सूचक समायोजित कर देता है जब समारोह रिटर्न है, लेकिन वह यहाँ अप्रासंगिक है।

+0

तर्कों का पता लेना शायद इसके साथ कुछ करने के लिए है, इसका मतलब है कि वे रजिस्टर्स –

+0

में संग्रहीत नहीं हो सकते हैं (मैंने) रिकर्सन का उपयोग करके अपनी स्टैक विकास दिशा का परीक्षण करने की कोशिश की है, और यह स्मृति में नीचे की ओर बढ़ता है। इसलिए, यह आपके द्वारा बताए गए अनुसार अलग कॉलिंग सम्मेलन है। अब, मैं कॉलिंग सम्मेलनों की जांच कर रहा हूं और यह अलग-अलग क्यों उपयोग कर रहा है :) – kravemir

+0

यह वास्तव में तथ्य है कि x86-64 में सम्मेलन को कॉल करना परिभाषा रजिस्टर-आधारित है (मुझे लगता है कि मशीन आर्म 64 या स्पार्क 64 नहीं है, या Itanium, अल्फा 64, आदि, क्योंकि वे काफी दुर्लभ हैं)। 'सी 'कॉलिंग सम्मेलनों जैसी कोई चीज नहीं है - यह कुछ ऐसा करने के लिए कंपाइलर डिज़ाइनर पर निर्भर करता है - हालांकि एक बहुत आम बात वास्तव में बाएं से बाएं चीजों को धक्का देती है और कॉलर इसे वापस समायोजित करता है, यह कुछ भी नहीं कह रहा है ऐसा होना चाहिए (उदाहरण के लिए x86-64 के अनुसार दिखाया गया है) –

7

यह व्यवहार कार्यान्वयन विशिष्ट है।

आपके मामले में, ऐसा इसलिए है क्योंकि तर्क स्टैक पर धकेल दिए जाते हैं। यहां एक interesting article जो एक प्रक्रिया के सामान्य मेमोरी लेआउट को दिखाता है, जो दिखाता है कि ढेर कैसे बढ़ता है। स्टैक पर धकेलने वाला पहला तर्क इसलिए उच्चतम पता होगा।

+0

Thx, लेकिन यह जवाब नहीं देता * क्यों * :) यह स्पष्ट है कि व्यवहार कार्यान्वयन विशिष्ट है। हालांकि, मैं पूछ रहा हूं कि इसे क्यों उलट दिया गया है, स्मृति में पहली बार तर्क क्यों नहीं है? – kravemir

+0

@Miro मैंने एक लिंक के साथ संपादित किया है जो बताता है कि स्टैक बढ़ता है (कम से कम विंडोज़ और लिनक्स पर) – Christophe

+0

+ लिंक के लिए, लेकिन स्वीकृत उत्तर बेहतर है :) – kravemir

1

सी (और सी ++) कोड फ़ंक्शंस के लिए तर्क पारित करने के लिए प्रोसेसर स्टैक का उपयोग करता है।

स्टैक कैसे संचालित होता है, प्रोसेसर पर निर्भर करता है। ढेर (सैद्धांतिक रूप से) नीचे या ऊपर की ओर बढ़ सकता है। तो यदि आपका पता बढ़ता या घटता है तो आपका प्रोसेसर परिभाषित कर रहा है। अंत में, प्रोसेसर आर्किटेक्चर अकेले इसके लिए ज़िम्मेदार नहीं है, लेकिन आर्किटेक्चर पर चल रहे कोड के लिए calling conventions हैं।

कॉलिंग सम्मेलन कहते हैं, एक विशिष्ट प्रोसेसर आर्किटेक्चर के लिए स्टैक पर तर्क कैसे लगाए जाने चाहिए। सम्मेलन आवश्यक हैं, कि विभिन्न कंपाइलरों के पुस्तकालयों को एक साथ जोड़ा जा सकता है।

असल में, आपके लिए सी उपयोगकर्ता के रूप में यह सामान्य रूप से कोई फर्क नहीं पड़ता है, अगर ढेर पर चर के पते बढ़ते हैं या सिकुड़ते हैं।

विवरण:

+0

सी ऐसी कोई चीज़ नहीं है। कुछ कार्यान्वयन * ऐसा कर सकते हैं, लेकिन भाषा द्वारा इसकी आवश्यकता नहीं है। उदाहरण के लिए, कुछ प्रोसेसर के पास बहस को सुविधाजनक बनाने के लिए बहुत बड़े रजिस्टर सेट होते हैं। –

+0

@AndrewMedico: मेरा मतलब था, आर्किटेक्चर के लिए कॉलिंग सम्मेलन हैं, जो सी कोड भी मानते हैं। सी मानक इस प्रकार परिभाषित नहीं करता है - लेकिन एक ऐसे आर्किटेक्चर के लिए ऐसे मानकों को परिभाषित करना आवश्यक है जो विभिन्न कंपाइलर आउटपुट को एक साथ जोड़ा जा सके। मेरा लिंक देखें। मैंने अपनी सजा स्पष्ट की। – Juergen

+0

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

2

सी (और सी ++) मानक पारित होने वाले तर्कों के क्रम को परिभाषित नहीं करता है, या स्मृति में उन्हें कैसे व्यवस्थित किया जाना चाहिए। यह किसी विशेष प्रोसेसर आर्किटेक्चर पर काम करने वाली किसी चीज़ के साथ आने के लिए कंपाइलर डेवलपर (आमतौर पर ओएस डेवलपर्स के साथ सहयोग में) पर निर्भर करता है।

अधिकांश आर्किटेक्चर में, स्टैक (और रजिस्ट्रार) का उपयोग किसी फ़ंक्शन में तर्क पारित करने के लिए किया जाता है, और फिर, अधिकांश आर्किटेक्चर के लिए, ढेर "उच्च से कम" पते से बढ़ता है, और अधिकांश सी कार्यान्वयन में, ऑर्डर का क्रम तर्क पारित किया जा रहा "पिछले छोड़ दिया" कर रहे हैं, इसलिए यदि हम एक समारोह

void test(int a, int b, int c) 

तो तर्क क्रम में पारित कर रहे हैं है:

c, b, a 
कार्य करने के लिए

हालांकि, यह जटिलता है जब तर्कों का मूल्य रजिस्टरों में पारित किया जाता है, और तर्क का उपयोग कर कोड उन तर्कों का पता ले रहा है - रजिस्टरों के पास पते नहीं हैं, इसलिए आप पता नहीं ले सकते एक रजिस्टर चर के। इसलिए संकलक स्टैक पर पते को स्टोर करने के लिए कुछ कोड उत्पन्न करेगा [जहां से हम मूल्य के पते को स्थानीय रूप से फ़ंक्शन पर प्राप्त कर सकते हैं]। यह पूरी तरह से संकलक के फैसले पर निर्भर करता है जो यह आदेश देता है, और मुझे पूरा यकीन है कि यह वही है जो आप देख रहे हैं।

आप हमें आपके कोड लेने के लिए और बजना के माध्यम से इसे पारित, तो हम देखते हैं:

define void @test(i32 %a, i32 %b, i32 %c) #0 { 
entry: 
    %a.addr = alloca i32, align 4 
    %b.addr = alloca i32, align 4 
    %c.addr = alloca i32, align 4 
    store i32 %a, i32* %a.addr, align 4 
    store i32 %b, i32* %b.addr, align 4 
    store i32 %c, i32* %c.addr, align 4 
    %call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([10 x i8], [10 x i8]* @.str, i32 0, i32 0), i32* %a.addr, i32* %b.addr, i32* %c.addr) 
    %add.ptr = getelementptr inbounds i32, i32* %b.addr, i64 -1 
    %0 = load i32, i32* %add.ptr, align 4 
    %add.ptr1 = getelementptr inbounds i32, i32* %b.addr, i64 1 
    %1 = load i32, i32* %add.ptr1, align 4 
    %call2 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.1, i32 0, i32 0), i32 %0, i32 %1) 
    ret void 
} 

हालांकि यह पूरी तरह से तुच्छ को पढ़ने के लिए नहीं हो सकता है, आप की कसौटी-समारोह है पहली कुछ पंक्तियों देख सकते हैं:

%a.addr = alloca i32, align 4 
    %b.addr = alloca i32, align 4 
    %c.addr = alloca i32, align 4 
    store i32 %a, i32* %a.addr, align 4 
    store i32 %b, i32* %b.addr, align 4 
    store i32 %c, i32* %c.addr, align 4 

यह अनिवार्य रूप से ढेर (%alloca) पर अंतरिक्ष पैदा कर रही है और चर a, b भंडारण, और उन स्थानों में c

भी कम आसान को पढ़ने के लिए कोडांतरक कोड जीसीसी जेनरेट करता है, लेकिन आप एक ऐसी ही बात यहाँ हो रहा देख सकते हैं:

subq $16, %rsp   ; <-- "alloca" for 4 integers. 
movl %edi, -4(%rbp)  ; Store a, b and c. 
movl %esi, -8(%rbp) 
movl %edx, -12(%rbp) 
leaq -12(%rbp), %rcx  ; Take address of ... 
leaq -8(%rbp), %rdx 
leaq -4(%rbp), %rax 
movq %rax, %rsi 
movl $.LC0, %edi 
movl $0, %eax 
call printf    ; Call printf. 

आप सोच रहे होंगे क्यों यह 4 पूर्णांकों के लिए अंतरिक्ष आवंटित - कि है, क्योंकि ढेर चाहिए हमेशा x86-64 में 16 बाइट्स के साथ गठबंधन किया जाना चाहिए।

1

एबीआई परिभाषित करता है कि पैरामीटर कैसे पास करें।

अपने उदाहरण में, यह थोड़ा जटिल के बाद से x86_64 ABI डिफ़ॉल्ट जीसीसी और बजना द्वारा रजिस्टरों पर मानकों (*) से गुजरता है, वहाँ उनके लिए कोई पता नहीं था।

तो फिर तुम मानकों का संदर्भ है, तो संकलक उन चर के लिए स्थानीय भंडारण आवंटित करने के लिए मजबूर किया जाता है, और कहा कि आदेश और स्मृति लेआउट भी कार्यान्वयन-विशिष्ट है।

  • नोट: 6 छोटे पैरामीटर तक, यदि अधिक हैं, तो यह ढेर पर गुजरता है।
  • संदर्भ: x86_64 ABI
0

32 बिट x86 बारे में बोलते हुए विंडोज

लघु जवाब: समारोह तर्कों को सूचक ढेर जहां वास्तविक समारोह फोन पर धकेल दिया गया करने के लिए आवश्यक सूचक नहीं है, लेकिन कर सकते हैं कहीं भी संकलक चर वैरिएबल स्थानांतरित हो।

लांग जवाब: बजना को bcc32 से मेरे कोड (Embarcadero क्लासिक संकलक) परिवर्तित करते हुए एक ही समस्या हुई। एमआईडीएल कंपाइलर द्वारा उत्पन्न आरपीसी कोड टूटा गया था क्योंकि आरपीसी फ़ंक्शन तर्क अनुवादक क्रमिक तर्कों को पॉइंटर को पहले फ़ंक्शन तर्क में ले कर तर्क देते हैं, मानते हैं कि निम्नलिखित सभी तर्कों का पालन किया जाएगा जैसे उदा। Serialize (& ए)।

डीबग किया cdecl फ़ंक्शन कॉल दोनों BCC32 और बजना द्वारा उत्पन्न:

  • BCC32: समारोह तर्क स्टैक पर सही क्रम में पारित कर रहे हैं, तो जब तर्क पता आवश्यक है, ढेर पता है सीधे दिया गया।

  • बजना: समारोह तर्क स्टैक पर सही क्रम में पारित कर रहे हैं, हालांकि वास्तविक समारोह में, सभी तर्कों की एक प्रति ढेर के विपरीत क्रम में स्मृति में किया जाता है, और जब समारोह तर्क पता आवश्यक है, मेमोरी एड्रेस सीधे दिया जाता है, जिसके परिणामस्वरूप रिवर्ट ऑर्डर होता है।

अन्यथा कहा, यह न मानें कि कैसे समारोह तर्क समारोह C/C++ कोड के भीतर से स्मृति में निपटाया जाता है। इसका कंपाइलर निर्भर है।

मेरे मामले में, एक संभव समाधान पास्कल बुला सम्मेलन (Win32) के साथ आरपीसी कार्यों की घोषणा करने के लिए मजबूर कर रहा MIDL संकलक अलग-अलग तर्क पार्स करने के लिए है। दुर्भाग्य से MIDL कोड उत्पन्न होता है भारी और बुरे कोड की आवश्यकता होगी, संकलित करने के लिए सुधार करने के बहुत सारे हैं, अभी भी नहीं किया) [में सी ++ संकलनकर्ता और मूल्यांकन के तर्क क्रम] (http के

संबंधित मुद्दे