पिछले कॉन्सेप्ट में बताया गया था कि लोकल लेबल और फंक्शन, दोनों असल में किसी ऐसे सेक्शन में रखे एड्रेस होते हैं जिसमें चलाने योग्य कोड होता है, जैसे section .text।
दरअसल, फंक्शन के साथ भी वैसा ही काम किया जा सकता है जैसा किसी भी मेमोरी एड्रेस के साथ किया जाता है। फंक्शन को रजिस्टर में लोड किया जा सकता है, एक जगह से दूसरी जगह भेजा जा सकता है और मेमोरी में संग्रहीत किया जा सकता है। call या jmp की मदद से कोड को किसी रजिस्टर या मेमोरी में रखे फंक्शन पर चलाया भी जा सकता है:
section .text
sum_op:
lea rax, [rdi + rsi] ; loads the sum rdi + rsi into rax
ret
apply_sum:
lea rax, [rel sum_op]
jmp rax ; tail call
जो फंक्शन एड्रेस किसी वैल्यू की तरह एक जगह से दूसरी जगह भेजा जाता है, उसे थंक कहते हैं। असेंबली में हायर-ऑर्डर प्रोग्रामिंग की बुनियाद थंक ही हैं, यानी ऐसा कोड जो दूसरे कोड पर काम करता है।
फंक्शन एड्रेस को मेमोरी में संग्रहीत करके बाद में निकाला भी जा सकता है:
section .bss
cached_fn resq 1
section .text
save_op:
mov qword [rel cached_fn], rdi
ret
apply_op:
; arguments are already set up according to the ABI
jmp qword [rel cached_fn] ; tail call
save_op को जो फंक्शन एड्रेस मिलता है, उसे यह cached_fn में लिख देता है। save_op लौटने के बाद भी यह वैल्यू बनी रहती है, इसलिए apply_op को बाद में कभी भी कॉल करने पर वह उसी एड्रेस पर टेल-जंप करता है जो सबसे बाद में संग्रहीत हुआ था। इससे यह मुमकिन हो जाता है कि रनटाइम पर बदला जा सके कि apply_op किस फंक्शन को कॉल करेगा।
फंक्शन एड्रेस को एक ऐरे में संग्रहीत करने से किसी इंडेक्स के हिसाब से अलग-अलग फंक्शन चुने जा सकते हैं, और वह इंडेक्स रनटाइम की किसी शर्त पर निर्भर भी हो सकता है। इसे डिस्पैच टेबल कहते हैं:
section .data
dispatch_table dq add_op, sub_op, mul_op
section .text
dispatch:
; this function takes two arguments in rdi and rsi, and an index in rdx
; it then applies the function corresponding to the index in rdx to the arguments
lea rax, [rel dispatch_table]
jmp qword [rax + 8*rdx] ; tail-call the function address for the index
जो थंक कॉल के बीच-बीच में किसी स्थायी मेमोरी को पढ़ता या बदलता है, उसका व्यवहार इस बात पर बदल सकता है कि उससे पहले क्या हुआ था। ऐसे थंक का परिणाम सिर्फ उसके आर्गुमेंट पर निर्भर नहीं होता, कुछ और भी उसमें भूमिका निभाता है।
उदाहरण के लिए, एक काउंटर लीजिए जो एक फंक्शन लेता है और उसे मौजूदा काउंट के साथ कॉल करता है, और हर बार काउंट आगे बढ़ा देता है:
section .data
count dq 0
section .text
tick:
mov rax, rdi ; saves the function address
mov rdi, [rel count] ; loads the current count as the function's argument
inc qword [rel count] ; advances the count
jmp rax ; tail-calls the function
tick दिए गए फंक्शन को मौजूदा काउंट आर्गुमेंट के साथ कॉल करता है और फिर काउंट आगे बढ़ा देता है। इसलिए पहली बार tick(square) कॉल करने पर square(0) चलता है, अगली बार tick(square) कॉल करने पर square(1), उसके बाद square(2), और इसी तरह।
एक और उदाहरण विलंबित गणना का होगा:
section .bss
captured_fn resq 1
argument resq 1
section .text
delay:
mov qword [rel captured_fn], rdi ; saves the function
mov qword [rel argument], rsi ; saves the argument
lea rax, [rel invoke] ; returns the `invoke` function
ret
invoke:
mov rdi, qword [rel argument] ; loads the saved argument into `rdi`
jmp qword [rel captured_fn] ; tail-calls the saved function
delay एक फंक्शन और एक वैल्यू लेता है, उन दोनों को संग्रहीत करता है, और invoke लौटाता है। जब invoke को कॉल किया जाता है, तो यह कैप्चर किए गए फंक्शन को सहेजे गए आर्गुमेंट के साथ चलाता है।
हायर-लेवल भाषाओं में आम कई पैटर्न, जैसे कॉलबैक, वर्चुअल मेथड, जनरेटर, करीइंग, फंक्शन कंपोज़िशन और ऐसे ही कई और, स्थायी स्थिति के साथ जोड़े गए थंक पर ही टिके होते हैं।
आप एक छोटे से गाँव के बैंक में हिसाब-किताब रखते हैं। हर ग्राहक का एक खाता होता है, और आप उसका बैलेंस अपनी बही में रखते हैं। साल भर इन बैलेंसों पर तरह-तरह के लेन-देन लागू होते हैं: ब्याज जमा होता है, फीस कटती है, बोनस बाँटा जाता है, और जुर्माना लगाया जाता है। हर लेन-देन एक बैलेंस लेता है और एक नया बैलेंस बनाता है।
आपके सामने चार काम हैं।
आप यह मान सकते हैं कि इस अभ्यास में हर थंक (लेन-देन और गार्ड) एक ऐसा फंक्शन है जो:
कैशियर दिन की शुरुआत में एक नया लेन-देन जान लेता है और उसे लिख लेता है, ताकि ग्राहक के आने पर उसे बाद में लागू किया जा सके।
दो फंक्शन बनाइए:
remember_transaction एक लेन-देन लेता है और उसे मेमोरी में संग्रहीत करता है।apply_remembered एक बैलेंस लेता है और पहले संग्रहीत लेन-देन उस पर लागू करता है।उदाहरण, यह मानते हुए कि add_interest एक ऐसा लेन-देन है जो पाँच यूनिट ब्याज जमा करता है:
remember_transaction(add_interest);
apply_remembered(100);
// => 105
remember_transaction(service_fee);
apply_remembered(100);
// => 98 (assuming service_fee deducts 2)
remember_transaction के लिए:
apply_remembered के लिए:
बैंक के मैनुअल में बार-बार होने वाले लेन-देन की एक सूची होती है, जो एक डिस्पैच टेबल में रखी जाती है। हर शाखा सूची की अपनी प्रति रखती है, और स्थानीय नीति के अनुसार अलग-अलग लेन-देन दर्ज कर सकती है।
कॉलर द्वारा दी गई डिस्पैच टेबल पर काम करने वाले दो फंक्शन बनाइए:
register_transaction एक डिस्पैच टेबल का मेमोरी एड्रेस, एक इंडेक्स और एक लेन-देन लेता है।
यह उस लेन-देन को टेबल में दिए गए इंडेक्स पर संग्रहीत करता है।select_transaction एक डिस्पैच टेबल का मेमोरी एड्रेस, एक इंडेक्स और एक बैलेंस लेता है।
यह दिए गए इंडेक्स पर मौजूद लेन-देन को ढूँढकर उसे बैलेंस पर लागू करता है, और नया बैलेंस लौटाता है।select_transaction को ढूँढे गए लेन-देन तक सिर्फ एक अप्रत्यक्ष टेल कॉल से पहुँचना चाहिए।
उदाहरण, यह मानते हुए कि manual चार खाली स्लॉट वाली एक डिस्पैच टेबल का मेमोरी एड्रेस है:
register_transaction(manual, 0, monthly_interest);
register_transaction(manual, 1, service_fee);
select_transaction(manual, 0, 100);
// applies monthly_interest to 100
select_transaction(manual, 1, 100);
// applies service_fee to 100
register_transaction के लिए:
select_transaction के लिए:
महीने के अंत में ग्राहक के खाते का मिलान किया जाता है। महीने भर में हुए हर लेन-देन को शुरुआती बैलेंस पर एक के बाद एक लागू किया जाता है, और इसका नतीजा नया बैलेंस होता है।
एक फंक्शन process_statement बनाइए।
यह तीन चीज़ें लेता है: एक शुरुआती बैलेंस, लेन-देन के एक ऐरे का मेमोरी एड्रेस, और ऐरे में मौजूद लेन-देन की संख्या।
यह क्रम में हर लेन-देन को चालू बैलेंस पर लागू करता है, और फिर नतीजे को अगले लेन-देन के लिए बैलेंस बना लेता है।
अंतिम बैलेंस लौटाया जाता है।
स्यूडोकोड में, process_statement(balance, transactions, n) यह गणना करता है:
for each transaction in transactions:
balance = transaction(balance)
return balance
उदाहरण, यह मानते हुए कि transactions उस ऐरे का मेमोरी एड्रेस है जिसमें इसी क्रम में add_interest, service_fee और add_interest लेन-देन हैं, जहाँ add_interest 5 जोड़ता है और service_fee 2 घटाता है:
process_statement(100, transactions, 3);
// add_interest(100) = 105
// service_fee(105) = 103
// add_interest(103) = 108
// => 108
पहला आर्गुमेंट एक 64-बिट ऋणेतर पूर्णांक है। दूसरा आर्गुमेंट लेन-देन के एक ऐरे का मेमोरी एड्रेस है। तीसरा आर्गुमेंट एक 64-बिट ऋणेतर पूर्णांक है (ऐरे की लंबाई)। रिटर्न वैल्यू एक 64-बिट ऋणेतर पूर्णांक है।
बैंक की नीति के अनुसार कुछ लेन-देन को अंतिम रूप देने से पहले जाँचना ज़रूरी है। एक गार्ड एक ऐसा फंक्शन है जो प्रस्तावित बैलेंस को देखकर तय करता है कि वह स्वीकार्य है या नहीं। यह गार्ड फंक्शन स्वीकृति के लिए गैर-शून्य वैल्यू लौटाता है, और अस्वीकार करने के लिए शून्य लौटाता है।
process_with_guard बनाइए।
यह चार चीज़ें लेता है: एक शुरुआती बैलेंस, लेन-देन के एक ऐरे का मेमोरी एड्रेस, ऐरे में लेन-देन की संख्या, और एक गार्ड फंक्शन।
क्रम में हर लेन-देन के लिए:
सारे लेन-देन संसाधित हो जाने के बाद, अंतिम बैलेंस के साथ-साथ स्वीकृत लेन-देन की संख्या भी लौटाइए।
स्यूडोकोड में, process_with_guard(balance, transactions, n, guard) यह गणना करता है:
approved = 0
for each transaction in transactions:
tentative = transaction(balance)
if guard(tentative) is non-zero:
balance = tentative
approved = approved + 1
return balance, approved
उदाहरण के लिए, मान लीजिए कि:
add_interest एक ऐसा लेन-देन है जो 5 जोड़ता है, और service_fee एक और लेन-देन है जो 2 घटाता हैat_least_10 एक ऐसा गार्ड है जो बैलेंस 10 या उससे अधिक होने पर गैर-शून्य वैल्यू लौटाता हैतो:
process_with_guard(5, {add_interest, service_fee, add_interest}, 3, at_least_10);
// add_interest(5) = 10; at_least_10(10) != 0;
// => balance = 10, approved = 1
//
// service_fee(10) = 8; at_least_10(8) = 0;
// => balance = 10, approved = 1
//
// add_interest(10) = 15; at_least_10(15) != 0;
// => balance = 15, approved = 2
//
// final balance (15) is returned in rax
// number of approved transactions (2) is returned in rdx
process_with_guard के लिए:
rax में, और स्वीकृत लेन-देन की संख्या rdx में।Exercism पर साइन अप कीजिए और x86-64 Assembly को 22 कॉन्सेप्ट130 अभ्यास तथा असली इंसानों से मिलने वाली मेंटरिंग के साथ सीखिए और उसमें महारत हासिल कीजिए, वह भी बिल्कुल मुफ्त।