2010-11-13 15 views
38

मैं एक उच्च स्तरीय सांकेतिक शब्दों में बदलनेवाला किया गया है, और आर्किटेक्चर सुंदर मेरे लिए नए हैं, इसलिए मैं विधानसभा पर ट्यूटोरियल यहाँ पढ़ने के लिए फैसला किया:स्टैक को संरेखित करने का क्या अर्थ है?

http://en.wikibooks.org/wiki/X86_Assembly/Print_Version

सुदूर ट्यूटोरियल नीचे, कैसे कन्वर्ट करने के लिए के बारे में निर्देश नमस्ते दुनिया! कार्यक्रम

#include <stdio.h> 

int main(void) { 
    printf("Hello, world!\n"); 
    return 0; 
} 

बराबर विधानसभा कोड में दिया गया था और निम्नलिखित बनाया गया:

 .text 
LC0: 
     .ascii "Hello, world!\12\0" 
.globl _main 
_main: 
     pushl %ebp 
     movl %esp, %ebp 
     subl $8, %esp 
     andl $-16, %esp 
     movl $0, %eax 
     movl %eax, -4(%ebp) 
     movl -4(%ebp), %eax 
     call __alloca 
     call ___main 
     movl $LC0, (%esp) 
     call _printf 
     movl $0, %eax 
     leave 
     ret 

लाइनों में से एक के लिए,

andl $-16, %esp 

विवरण था:

यह कोड "और" ईएसपी 0xFFFFFFF0,के साथअगले न्यूनतम 16-बाइट सीमा के साथ स्टैक को संरेखित कर रहा है। मिंगव के स्रोत कोड की परीक्षा से पता चलता है कि यह सिम "_main" दिनचर्या में दिखाई देने वाले निर्देशों के लिए हो सकता है, जो केवल पते पर हस्ताक्षर किए जाते हैं। चूंकि हमारी दिनचर्या में सिम निर्देश नहीं हैं, इसलिए यह पंक्ति अनावश्यक है।

मुझे इस बिंदु को समझ में नहीं आता है। क्या कोई मुझे अगले 16-बाइट सीमा के साथ ढेर को संरेखित करने का अर्थ बता सकता है और इसकी आवश्यकता क्यों है? और andl यह कैसे प्राप्त कर रहा है?

+3

http://en.wikipedia.org/wiki/Data_structure_alignment – chrisaycock

+1

ऑप्टिमाइज़र को सक्षम किए बिना मशीन कोड को देखने के लिए बहुत अधिक समझदारी नहीं होती है। –

उत्तर

51

मान लें ढेर लगता है कि _main में प्रवेश पर इस (ढेर सूचक का पता सिर्फ एक उदाहरण है):

| existing  | 
| stack content | 
+-----------------+ <--- 0xbfff1230 

पुश %ebp, और स्थानीय चर के लिए कुछ स्थान बुक करने %esp से 8 घटाना:

| existing  | 
| stack content | 
+-----------------+ <--- 0xbfff1230 
|  %ebp  | 
+-----------------+ <--- 0xbfff122c 
: reserved  : 
:  space  : 
+-----------------+ <--- 0xbfff1224 

अब, andl अनुदेश %esp की कम 4 बिट्स, शून्य जो मई दिसम्बर इसका पीछा करो; इस विशिष्ट उदाहरण में, यह एक अतिरिक्त 4 बाइट आरक्षण की प्रभाव पड़ता है:

| existing  | 
| stack content | 
+-----------------+ <--- 0xbfff1230 
|  %ebp  | 
+-----------------+ <--- 0xbfff122c 
: reserved  : 
:  space  : 
+ - - - - - - - - + <--- 0xbfff1224 
: extra space : 
+-----------------+ <--- 0xbfff1220 

इस की बात कुछ "SIMD" (एकल निर्देश, एकाधिक डेटा) निर्देश (भी 86-देश में जाना जाता है देखते हैं कि है "स्ट्रीमिंग सिम एक्सटेंशन" के लिए "एसएसई" के रूप में) जो स्मृति में एकाधिक शब्दों पर समांतर संचालन कर सकता है, लेकिन उन एकाधिक शब्दों को एक पते पर शुरू होने वाले ब्लॉक होने की आवश्यकता होती है जो 16 बाइट्स का एक बहु है।

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

+0

अब, एंडल निर्देश% esp के निम्न 4 बिट्स को शून्य करता है, जो इसे कम कर सकता है। तो कंपाइलर कैसे जानता है कि बाद में स्टैक को संतुलित करने के लिए कितने बाइट कम हो गए थे? – secmask

+3

@secmask: मूल '% ebp' को धक्का देने के बाद'% esp' का मान '% ebp' में संग्रहीत किया गया है, इसलिए इसे जानने की आवश्यकता नहीं है, क्योंकि आरक्षित के शीर्ष पर'% ebp' अंक अंतरिक्ष। '% esp' को दिखाए गए कोड में 'छोड़ने' निर्देश द्वारा बहाल किया जाता है -' छोड़ ''movl% ebp,% esp के बराबर है; popl% ebp'। –

3

यह केवल अजीब लोगों पर भी पते पर होना चाहिए, क्योंकि प्रदर्शन प्रदर्शन घाटा है।

+0

इसका प्रदर्शन के साथ कुछ लेना देना नहीं है। सीपीयू बस एक असाइन किए गए पते से डेटा नहीं ला सकता है क्योंकि यह बस त्रुटि होगी। – chrisaycock

+0

बस त्रुटि या नहीं, यह असफल नहीं है। –

+0

@chrisaycock आधुनिक प्रोसेसर छोटे प्रदर्शन दंड के साथ कर सकते हैं। – YoYoYonnY

7

इसे byte alignment के साथ करना है। कुछ आर्किटेक्चरों को संचालन के विशिष्ट सेट के लिए उपयोग किए जाने वाले पते की आवश्यकता होती है जो विशिष्ट बिट सीमाओं के साथ गठबंधन की जाती हैं।

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

बाइट संरेखण की एक महत्वपूर्ण विशेषता (यह मानते हुए कि संख्या 2 की शक्ति है) यह है कि कम से कम एक्स पते के बिट हमेशा शून्य होते हैं। यह प्रोसेसर को नीचे एक्स बिट्स का उपयोग न करके कम बिट्स के साथ अधिक पते का प्रतिनिधित्व करने की अनुमति देता है।

+1

+1 मेरी तरफ से भी! स्पष्टीकरण के लिए धन्यवाद। – Legend

5

इस में 8 "स्लाइड" के पते एक से अधिक में "ड्राइंग"

 
addresses 
xxxabcdef... 
    [------][------][------] ... 
registers 

मान आसानी से कल्पना (64-बिट) पंजीकृत करता

 
addresses 
     56789abc ... 
    [------][------][------] ... 
registers 

बेशक पंजीकृत करता है "सैर" के चरणों में 8 बाइट

अब यदि आप एक रजिस्टर में xxx5 पते पर मूल्य डालना चाहते हैं तो यह अधिक कठिन है :-)


andl संपादित -16

-16 द्विआधारी

जब आप "और" -16 आप पिछले 4 बिट्स 0 पर सेट के साथ एक मूल्य मिल के साथ कुछ भी ... या में 11111111111111111111111111110000 है 16 का बहुमुखी 16.

3

जब प्रोसेसर स्मृति से डेटा को एक रजिस्टर में लोड करता है, तो इसे मूल पते और आकार से एक्सेस करने की आवश्यकता होती है। उदाहरण के लिए, यह पता 10100100 से 4 बाइट लाएगा। ध्यान दें कि उस उदाहरण के अंत में दो शून्य हैं। ऐसा इसलिए है क्योंकि चार बाइट संग्रहीत किए जाते हैं ताकि 101001 अग्रणी बिट्स महत्वपूर्ण हों। (प्रोसेसर वास्तव में 101001XX लाने के द्वारा "परवाह नहीं करते" के माध्यम से इन तक पहुंचता है।)

इसलिए स्मृति में कुछ संरेखित करने के लिए डेटा को पुन: व्यवस्थित करना है (आमतौर पर पैडिंग के माध्यम से) ताकि वांछित आइटम के पते में पर्याप्त शून्य बाइट्स हों। उपरोक्त उदाहरण को जारी रखते हुए, हम 10100101 से 4 बाइट नहीं ला सकते हैं क्योंकि पिछले दो बिट शून्य नहीं हैं; जो बस त्रुटि का कारण बनता है। इसलिए हमें 10101000 तक पते को टक्कर देना होगा (और प्रक्रिया में तीन पता स्थान बर्बाद करना होगा)।

कंपाइलर यह आपके लिए स्वचालित रूप से करता है और असेंबली कोड में प्रदर्शित होता है।

ध्यान दें कि यह C/C++ एक अनुकूलन के रूप में प्रकट होता है:

struct first { 
    char letter1; 
    int number; 
    char letter2; 
}; 

struct second { 
    int number; 
    char letter1; 
    char letter2; 
}; 

int main() 
{ 
    cout << "Size of first: " << sizeof(first) << endl; 
    cout << "Size of second: " << sizeof(second) << endl; 
    return 0; 
} 

उत्पादन

Size of first: 12 
Size of second: 8 

उलटफेर करने पर है दो char के मतलब है कि int ठीक से संरेखित किया जाएगा, और तो कंपाइलर को पैडिंग के माध्यम से आधार पते को टक्कर नहीं लेनी पड़ेगी। यही कारण है कि दूसरे का आकार छोटा है।

13

यह विशिष्ट रूप से ढेर नहीं है, लेकिन सामान्य रूप से संरेखण नहीं है। शायद शब्द पूर्णांक एकाधिक के बारे में सोचो।

यदि आपके पास स्मृति में आइटम हैं जो कि आकार में बाइट हैं, 1 की इकाइयां हैं, तो बस यह कहें कि वे सभी गठबंधन हैं। चीजें जो आकार में दो बाइट हैं, फिर पूर्णांक समय 2 को गठबंधन किया जाएगा, 0, 2, 4, 6, 8, आदि। और गैर-पूर्णांक गुणक, 1, 3, 5, 7 को गठबंधन नहीं किया जाएगा। आइटम जो 4 बाइट आकार में हैं, पूर्णांक गुणक 0, 4, 8, 12, आदि गठबंधन हैं, 1,2,3,5,6,7, आदि नहीं हैं। 8, 0,8,16,24 और 16 16,32,48,64, और इसी तरह के लिए चला जाता है।

इसका क्या अर्थ है कि आप आइटम के लिए मूल पता देख सकते हैं और यह निर्धारित कर सकते हैं कि यह गठबंधन है या नहीं।

 
size in bytes, address in the form of 
1, xxxxxxx 
2, xxxxxx0 
4, xxxxx00 
8, xxxx000 
16,xxx0000 
32,xx00000 
64,x000000 
and so on 

एक संकलक .text खंड यह काफी के रूप में आवश्यक डेटा संरेखित करने के लिए सीधा है निर्देश के साथ डेटा में मिश्रण के मामले में (अच्छी तरह से, वास्तुकला पर निर्भर करता है)। लेकिन ढेर एक रनटाइम चीज है, संकलक सामान्य रूप से निर्धारित नहीं कर सकता कि स्टैक रन टाइम पर कहाँ होगा। तो रनटाइम पर यदि आपके पास स्थानीय वेरिएबल हैं जिन्हें गठबंधन करने की आवश्यकता है तो आपको कोड को प्रोग्रामिक रूप से स्टैक समायोजित करने की आवश्यकता होगी।

उदाहरण के लिए कहें कि आपके पास ढेर पर दो 8 बाइट आइटम हैं, 16 कुल बाइट्स हैं, और आप वास्तव में उन्हें गठबंधन करना चाहते हैं (8 बाइट सीमाओं पर)। प्रवेश पर समारोह सामान्य रूप से इन दो वस्तुओं के लिए जगह बनाने के लिए स्टैक पॉइंटर से 16 घटाएगा। लेकिन उन्हें संरेखित करने के लिए और कोड होना आवश्यक होगा। अगर हम चाहते थे कि इन दो 8 बाइट आइटम 8 बाइट सीमाओं पर गठबंधन हों और 16 घटाए जाने के बाद स्टैक पॉइंटर 0xFF82 था, तो कम 3 बिट्स 0 नहीं हैं, इसलिए यह गठबंधन नहीं है। निचले तीन बिट 0b010 हैं। एक सामान्य अर्थ में हम 0xFF80 प्राप्त करने के लिए 0xFF82 से 2 घटा सकते हैं। हम कैसे निर्धारित करते हैं कि यह 2 बीबी 1 (0x7) के साथ और उस राशि को घटाना होगा। इसका मतलब है एयू संचालन एक और एक घटाना। लेकिन अगर हम और 0x7 (~ 0x7 = 0xFFFF ... FFF8) के पूरक मूल्य के साथ हम एक शॉर्टकट ले सकते हैं तो हमें एक अलू ऑपरेशन का उपयोग करके 0xFF80 मिलता है (जब तक संकलक और प्रोसेसर के पास ऐसा करने के लिए एक एकल ऑपोड तरीका होता है, यदि नहीं, तो यह आपको और अधिक घटाने से अधिक खर्च कर सकता है)।

ऐसा लगता है कि आपका प्रोग्राम क्या कर रहा था। -16 के साथ एंडिंग 0xFFFF के साथ एंडिंग जैसा ही है .... एफएफएफ 0, जिसके परिणामस्वरूप एक 16 बाइट सीमा पर गठबंधन किया गया पता है।

तो यह लपेट के लिए, यदि आप एक ठेठ ढेर सूचक है कि उच्च पतों के माध्यम से स्मृति नीचे अपनी तरह से काम करता पतों कम करने के लिए की तरह कुछ है, तो आप

 
sp = sp & (~(n-1)) 

करना चाहते हैं जहां n बाइट की संख्या है संरेखित करने के लिए (शक्तियां होनी चाहिए लेकिन यह ठीक है अधिकांश संरेखण में आमतौर पर दो की शक्तियां शामिल होती हैं)। यदि आप एक malloc किया कहना है (पते को निम्न से उच्च वृद्धि) और कुछ का पता संरेखित करना चाहते हैं तो

 
if(ptr&(~(n-)) { ptr = (ptr+n)&(~(n-1)); } 

(एक से अधिक आप की जरूरत malloc के लिए कम से कम संरेखण आकार से याद) या यदि आप चाहते हैं बस बाहर निकलें और हर बार जोड़ें और मुखौटा करें।

कई/अधिकांश गैर-x86 आर्किटेक्चर में संरेखण नियम और आवश्यकताएं हैं। x86 उतना ही लचीला है जब तक कि निर्देश सेट चला जाता है, लेकिन जहां तक ​​निष्पादन जाता है, आप x86 पर असाइन किए गए एक्सेस के लिए जुर्माना दे सकते हैं, तो भले ही आप इसे कर सकें, आपको किसी भी के साथ गठबंधन करने का प्रयास करना चाहिए अन्य वास्तुकला। शायद यही वह कोड था जो यह कर रहा था।

+1

शानदार उत्तर, यह पृष्ठ के निचले हिस्से में क्यों है? – jwbensley

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