আগের কনসেপ্টগুলোতে উল্লেখ করা হয়েছিল যে global ডিরেক্টিভ একটি সোর্স ফাইলে ডিফাইন করা কোনো ফাংশন বা ভ্যারিয়েবলকে অন্য ফাইলগুলোর কাছে দৃশ্যমান করে তোলে।
একইভাবে, extern ডিরেক্টিভটি অ্যাসেম্বলারকে জানায় যে বর্তমান সোর্স ফাইলে ব্যবহৃত কোনো ফাংশন বা ভ্যারিয়েবল অন্য একটি ফাইলে ডিফাইন করা হয়েছে।
মডুলারাইজেশন অর্জনের জন্য অ্যাসেম্বলি কোড অন্য সোর্স ফাইলগুলোর সাথে যেভাবে ইন্টারঅ্যাক্ট করে, তার প্রধান উপায় হলো এই দুটি ডিরেক্টিভ।
এই ডিরেক্টিভগুলোর ক্ষেত্রে বাহ্যিক কোডটি অ্যাসেম্বলিতে ডিফাইন বা ব্যবহৃত হোক, কিংবা কোনো হাই-লেভেল ভাষায় হোক, তাতে কোনো পার্থক্য নেই।
তাই উদাহরণস্বরূপ, কোনো হাই-লেভেল ভাষায় ডিফাইন করা একটি ফাংশনও extern হিসেবে ডিক্লেয়ার করা থাকলে x86-64 কোডে ব্যবহার করা যেতে পারে।
একইভাবে, কলিং কনভেনশনটি এই সমস্ত ফাংশনের মধ্যেই ভাগ করা থাকে, সেগুলো অ্যাসেম্বলিতে ডিফাইন করা হোক বা হাই-লেভেল ভাষায়, এমনকি অপারেটিং সিস্টেমের (OS) সাথেও।
তবে, যেহেতু অ্যাসেম্বলিতে ডেটা কেবল বাইটের একটি ক্রম, তাই ইন্টারফেস করা হাই-লেভেল ভাষাটি কীভাবে মেমরি পরিচালনা করে সে বিষয়ে সচেতন থাকা গুরুত্বপূর্ণ।
অ্যাসেম্বলি কোডের সাথে ইন্টারফেস করার জন্য হাই-লেভেল ভাষা হিসেবে C একটি কমন পছন্দ। এটি OS-এর সাথে ইন্টারঅ্যাক্ট করার কাজটি সহজ করে তোলে এবং স্ক্রিনে প্রিন্ট করা, কীবোর্ড থেকে পড়া বা ডাইনামিক মেমরি বরাদ্দ করার মতো অনেক গুরুত্বপূর্ণ কাজের জন্য হাই-লেভেল র্যাপার সরবরাহ করে।
এই ট্র্যাকে টেস্টগুলো C-এ লেখা হয়, তাই ভাষাটির একটি সাধারণ পরিচিতি দেওয়া গুরুত্বপূর্ণ।
নিচে উল্লেখিত যেকোনো টাইপ const দিয়ে কোয়ালিফাই করা যেতে পারে।
এতে সেগুলো রিড-অনলি হয়ে যায়।
C-তে অনেক প্রিমিটিভ টাইপ রয়েছে এবং বাইটে তাদের আকার ভিন্ন হতে পারে। নিচের টেবিলে সেগুলোর কয়েকটি x86-64 সিস্টেমে তাদের সাধারণ আকারসহ সংক্ষেপে দেওয়া হয়েছে:
| type | number of bytes | integer/floating-point |
|---|---|---|
| _Bool | ১ | ইন্টিজার |
| char | ১ | ইন্টিজার |
| short | ২ | ইন্টিজার |
| int | ৪ | ইন্টিজার |
| long | ৪ বা ৮ | ইন্টিজার |
| long long | ৮ | ইন্টিজার |
| float | ৪ | ফ্লোটিং-পয়েন্ট |
| double | ৮ | ফ্লোটিং-পয়েন্ট |
এই ইন্টিগ্রাল টাইপগুলোর প্রত্যেকটি signed (ডিফল্ট) বা unsigned হতে পারে।
unsigned হলে তা উল্লেখ করতে হয়, উদাহরণস্বরূপ: unsigned int।
ইন্টিগ্রাল টাইপগুলোর জন্য অ্যালিয়াস রয়েছে, যেগুলো তাদের আকারের উপর সুনির্দিষ্ট নিয়ন্ত্রণ দিতে ডিফাইন করা হয়েছে:
| alias | number of bytes |
|---|---|
| int8_t | ১ |
| int16_t | ২ |
| int32_t | ৪ |
| int64_t | ৮ |
এই অ্যালিয়াসগুলোর আগে u যোগ করে unsigned টাইপ বোঝানো যায়, উদাহরণস্বরূপ: uint64_t।
উল্লেখযোগ্য আরও কিছু অ্যালিয়াস:
| alias | type |
|---|---|
| bool | _Bool |
| size_t | সাধারণত একটি unsigned ৮-বাইট ইন্টিজার |
একটি _Bool, বা তার অ্যালিয়াস bool, অনন্য এই অর্থে যে এটি ১টি সম্পূর্ণ বাইট দখল করলেও কেবল দুটি মানের একটি নিতে পারে: true (1) এবং false (0)।
এই প্রিমিটিভ টাইপগুলোর মান ইন্টিজার ও ফ্লোটিং-পয়েন্ট মানের স্বাভাবিক নিয়ম অনুযায়ী ফাংশনে পাস করা হয় এবং ফাংশন থেকে রিটার্ন করা হয়।
enum হলো এমন একটি টাইপ যা স্পষ্টভাবে উল্লেখ করা বেশ কয়েকটি এলিমেন্টের একটি হতে পারে:
enum example {
example_1,
example_2,
example_3,
example_4
};
C-তে এনামগুলো implicitly একটি ইন্টিজার টাইপে রূপান্তরিত হয়, যা সাধারণত একটি int, অর্থাৎ একটি ৪-বাইট ইন্টিজার।
ডিফল্টভাবে একটি এনামের প্রথম সম্ভাব্য এলিমেন্টটি 0-তে রূপান্তরিত হয় এবং পরবর্তী সব এলিমেন্ট পরের ইন্টিজারে রূপান্তরিত হয়।
তাই উপরে ডিফাইন করা enum example-এ example_1-এর মান হবে 0, example_2-এর মান হবে 1, এবং এভাবে চলবে।
এনামের যেকোনো এলিমেন্টের জন্য মান নির্দেশ করা সম্ভব, এবং তারপর ডিফল্টভাবে পরের এলিমেন্টের মান হবে পরের ইন্টিজার:
enum example_2 {
example_1 = 4,
example_2 = 8,
example_3
}; // example_3 is equal to 9
C-তে, একটি টাইপের প্রতি পয়েন্টার দিয়ে একটি মেমরি ঠিকানা রেফারেন্স করা হয় এবং * অপারেটর দিয়ে চিহ্নিত করা হয়।
তাই উদাহরণস্বরূপ, int64_t * এক বা একাধিক int64_t-এর জন্য একটি মেমরি লোকেশন বোঝায়।
ঠিকানাগুলোকে যথারীতি ৮-বাইট ইন্টিজার হিসেবে গণ্য করা হয়।
অ্যারেগুলো ফাংশনে তাদের প্রথম এলিমেন্টের পয়েন্টার হিসেবে পাস করা হয় এবং ফাংশন থেকে সেভাবেই রিটার্ন করা হয়।
int64_t example_arr[] = {1, 2, 3}; // this is an array of 3 signed 8-byte integers
fn(example_arr); // this is a function that passes a pointer (int64_t *) to the beginning of the array as an argument
অ্যারের প্রতিটি এলিমেন্টের আকার হয় অ্যারের এলিমেন্ট টাইপের আকার।
উপরে ডিফাইন করা example_arr-এর উদাহরণস্বরূপ ৩টি এলিমেন্ট রয়েছে, প্রতিটির আকার ৮ বাইট, অর্থাৎ মোট ২৪ বাইট।
যেহেতু অ্যারেগুলো তাদের দৈর্ঘ্য সংরক্ষণ করে না, তাই এলিমেন্টের সংখ্যা জানাতে সাধারণত একটি আলাদা মান প্রয়োজন হয়, যদি না কোনো সেন্টিনেল ভ্যালু শেষ চিহ্নিত করে।
C-তে স্ট্রিং হলো char-এর একটি অ্যারে, যা একটি ১-বাইট টাইপ।
বেশিরভাগ স্ট্রিং ASCII অক্ষরের হয় এবং NUL-terminated হয়, তাই 0 মানের একটি বাইট পাওয়া গেলেই সেগুলো শেষ হয়।
এর মানে হলো সাধারণত স্ট্রিংয়ের দৈর্ঘ্য আলাদা আর্গুমেন্ট হিসেবে পাস করার প্রয়োজন হয় না।
আপনার বন্ধু চ্যান্ডলার সারা বিশ্বের নানা দূরদেশে ঘুরতে যাওয়ার পরিকল্পনা করছেন। দুর্ভাগ্যবশত, চ্যান্ডলারের গণিতের দক্ষতা ভালো নয়। ভ্রমণের সময় কারেন্সি এক্সচেঞ্জের কাছে প্রতারিত হওয়ার ব্যাপারে তিনি বেশ চিন্তিত, আর তিনি চান আপনি তাঁর জন্য একটি কারেন্সি ক্যালকুলেটর বানিয়ে দিন।
অ্যাপটির জন্য তাঁর স্পেসিফিকেশনগুলো এখানে।
এই অনুশীলনীর ফাংশনগুলো একটি C সোর্স ফাইলে প্রদত্ত সিগনেচারসহ ঘোষণা করা আছে। আপনার x86-64-assembly কোডকে সেই স্পেসিফিকেশনগুলো মেনে চলতে হবে।
চ্যান্ডলার তাঁর ভ্রমণে যে যে কারেন্সি ব্যবহার করেন, সেগুলোর হিসাব রাখতে আপনি একটি enum currency_t তৈরি করেছেন:
enum currency_t {
GBP,
EUR,
JPY,
AUD,
BRL,
CNY,
CAD,
INR
};
সেই কারেন্সিগুলো ঠিক আছে কি না তা যাচাই করার জন্য মাঝে মাঝে সেগুলো স্ক্রিনে প্রিন্ট করার দরকার হয়।
stringify_currency ফাংশনটি তৈরি করুন:
void stringify_currency(char *buffer, enum currency_t currency);
এই ফাংশনের কোনো রিটার্ন ভ্যালু নেই এবং buffer দ্বারা নির্দেশিত জায়গায় একটি C-স্টাইল স্ট্রিং সংরক্ষণ করা উচিত।
স্ট্রিংটি দেখেই বোঝা উচিত ফাংশনে কোন ধরনের কারেন্সি পাঠানো হয়েছে:
char buffer[4];
stringify_currency(buffer, CAD);
// => "CAD"
exchange_rate ফাংশনটি তৈরি করুন:
double exchange_rate(enum currency_t domestic_currency, enum currency_t foreign_currency, const double *value_in_US_dollars);
domestic_currency আর্গুমেন্টটি চ্যান্ডলারের স্থানীয় কারেন্সি বোঝায়, যা একটি foreign_currency-এর বিনিময়ে বিনিময় করা হবে।
value_in_US_dollars হলো double-এর একটি অ্যারের পয়েন্টার, যেখানে প্রতি কারেন্সির এক এককের মান মার্কিন ডলারে আছে।
এই অ্যারেতে কারেন্সিগুলো হলো ইনডেক্স।
সুতরাং, যদি EUR enum currency_t-এর একটি বৈধ এলিমেন্ট হয়, তাহলে value_in_US_dollars[EUR] মার্কিন ডলারে 1 EUR-এর মান দেয়।
এই ফাংশনটি স্থানীয় কারেন্সিতে বিদেশি কারেন্সির এক এককের মান রিটার্ন করা উচিত।
const double value_in_US_dollars[8] = {
1.33329, 1.16443, 0.00654374, 0.650888,
0.185577, 0.140420, 0.714142, 0.0113863
};
exchange_rate(JPY, GBP, value_in_US_dollars);
// => 203.750454633
get_value_of_bills ফাংশনটি তৈরি করুন:
uint64_t get_value_of_bills(unsigned long long denomination, unsigned short number_of_bills);
denomination আর্গুমেন্টটি একটি একক বিলের মান, আর number_of_bills হলো মোট বিলের সংখ্যা।
এই বিনিময় বুথটি কেবল নির্দিষ্ট এককে নগদ লেনদেন করে। আপনি যে মোট মান পাবেন তা অবশ্যই একটি "বিল" বা এককের মান দিয়ে বিভাজ্য হতে হবে, যেখানে একটি ভগ্নাংশ বা ভাগশেষ থেকে যেতে পারে। আপনার ফাংশনটি কেবল বুথ যে বিলগুলো ফেরত দেবে তার মোট মান রিটার্ন করা উচিত (ভগ্নাংশের পরিমাণ বাদ দিয়ে)। দুর্ভাগ্যবশত, বুথটি ভাগশেষ/খুচরা অতিরিক্ত বোনাস হিসেবে রেখে দেয়।
get_value_of_bills(5, 128);
// => 640
get_number_of_bills ফাংশনটি তৈরি করুন:
unsigned int get_number_of_bills(float amount, unsigned long long denomination);
এই ফাংশনটি প্রদত্ত অ্যামাউন্টের মধ্যে আপনি কতগুলো কারেন্সি বিল পেতে পারেন তা রিটার্ন করা উচিত। অন্য কথায়: শুরুর অ্যামাউন্টে কতগুলো সম্পূর্ণ বিল ধরে? মনে রাখবেন, আপনি কেবল সম্পূর্ণ বিল পেতে পারেন, বিলের ভগ্নাংশ নয়, তাই সেই অনুযায়ী ভাগ করতে ভুলবেন না। কার্যত আপনি নিকটতম সম্পূর্ণ বিল/ডিনোমিনেশন-এ নিচের দিকে রাউন্ড করছেন।
get_number_of_bills(127.5, 5);
// => 25
exchangeable_value ফাংশনটি তৈরি করুন:
uint32_t exchangeable_value(float budget, double exchange_rate, uint8_t spread, unsigned long long denomination);
spread প্যারামিটারটি এক্সচেঞ্জ ফি হিসেবে নেওয়া শতাংশ, যা একটি পূর্ণসংখ্যা হিসেবে লেখা।
এটিকে ১০০ দিয়ে ভাগ করে দশমিকে রূপান্তর করতে হবে।
মনে রাখবেন, স্প্রেড এক্সচেঞ্জ রেটের সাথে যোগ করা হয়, তাই 10 স্প্রেডের ফলে এক্সচেঞ্জ রেট ১০% বেশি হয়।
এই ফাংশনটি স্প্রেড দ্বারা সমন্বিত এক্সচেঞ্জ রেট হিসাব করার পর নতুন কারেন্সির সর্বোচ্চ মান রিটার্ন করা উচিত। মনে রাখবেন, কারেন্সির ডিনোমিনেশন একটি পূর্ণসংখ্যা, এবং এটিকে আর ভাগ করা যায় না।
exchangeable_value(127.25, 0,873350884, 10, 20);
// => 120
exchangeable_value(127.25, 0,873350884, 10, 5);
// => 130
Exercism-এ সাইন আপ করুন, x86-64 Assembly ট্র্যাকের 22টি কনসেপ্ট130টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।