در مفاهیم پیشین اشاره شد که دستور global باعث میشود یک تابع یا متغیر که در یک فایل مبدأ تعریف شده است، برای فایلهای دیگر قابل مشاهده باشد.
به همین ترتیب، دستور extern به اسمبلر اطلاع میدهد که یک تابع یا متغیر که در فایل مبدأ جاری استفاده میشود، در فایل دیگری تعریف شده است.
این دو دستور، راه اصلی تعامل کد اسمبلی با سایر فایلهای مبدأ برای رسیدن به ماژولارسازی هستند.
برای این دستورها تفاوتی نمیکند که کد خارجی در اسمبلی یا در یک زبان سطح بالا تعریف شده باشد یا استفاده شود.
بنابراین، برای مثال، حتی تابعی که در یک زبان سطح بالا تعریف شده است، اگر extern اعلام شود، میتواند در کد x86-64 استفاده شود.
به همین ترتیب، قرارداد فراخوانی میان همهی این توابع، چه در اسمبلی و چه در زبانهای سطح بالا تعریف شده باشند، و حتی با سیستمعامل (OS) مشترک است.
با این حال، چون در اسمبلی داده فقط دنبالهای از بایت است، مهم است که بدانید زبان سطح بالایی که با آن تعامل دارید چگونه حافظه را مدیریت میکند.
زبان C گزینهای رایج برای یک زبان سطح بالا جهت تعامل با کد اسمبلی است. این زبان کار تعامل با سیستمعامل را آسانتر میکند و برای بسیاری از کارهای مهم، مانند چاپ روی صفحه، خواندن از صفحهکلید یا تخصیص حافظهی پویا، پوششهای سطح بالا ارائه میدهد.
در این مسیر، آزمونها به زبان C نوشته شدهاند، پس مهم است که یک نمای کلی از این زبان ارائه شود.
هر یک از انواعی که در ادامه میآید را میتوان با const مقید کرد.
این کار آنها را فقطخواندنی میکند.
انواع دادهی اولیهی زیادی در C وجود دارد و اندازهی آنها، بر حسب بایت، میتواند متفاوت باشد. برخی از آنها با اندازهی معمولشان در یک سیستم x86-64 در جدول زیر خلاصه شدهاند:
| نوع | تعداد بایت | صحیح/اعشاری |
|---|---|---|
| _Bool | ۱ | صحیح |
| char | ۱ | صحیح |
| short | ۲ | صحیح |
| int | ۴ | صحیح |
| long | ۴ یا ۸ | صحیح |
| long long | ۸ | صحیح |
| float | ۴ | اعشاری |
| double | ۸ | اعشاری |
هر یک از این انواع صحیح میتواند علامتدار (حالت پیشفرض) یا بیعلامت باشد.
اگر بیعلامت باشند، باید این موضوع مشخص شود، برای مثال: unsigned int.
برای انواع صحیح نامهای مستعاری تعریف شده است تا کنترل دقیقی روی اندازهی آنها فراهم شود:
| نام مستعار | تعداد بایت |
|---|---|
| int8_t | ۱ |
| int16_t | ۲ |
| int32_t | ۴ |
| int64_t | ۸ |
این نامهای مستعار میتوانند با u شروع شوند تا نوع بیعلامت را نشان دهند، برای مثال: uint64_t.
نامهای مستعار دیگر قابل توجه عبارتاند از:
| نام مستعار | نوع |
|---|---|
| bool | _Bool |
| size_t | معمولاً یک عدد صحیح بیعلامت ۸ بایتی |
_Bool، یا نام مستعارش bool، از این نظر بیهمتاست که با اینکه یک بایت کامل را اشغال میکند، فقط میتواند یکی از دو مقدار را بگیرد: true (1) و false (0).
مقادیر این انواع اولیه، طبق قواعد معمول برای اعداد صحیح و مقادیر اعشاری، به توابع فرستاده و از آنها بازگردانده میشوند.
enumenum یک نوع است که میتواند یکی از چند عنصر مشخصشده را بگیرد:
enum example {
example_1,
example_2,
example_3,
example_4
};
در زبان C، enumها بهطور ضمنی به یک نوع صحیح تبدیل میشوند، که معمولاً یک int است، یعنی یک عدد صحیح ۴ بایتی.
بهطور پیشفرض، اولین عنصر ممکن در یک enum به 0 تبدیل میشود و همهی عناصر بعدی به عدد صحیح بعدی تبدیل میشوند.
بنابراین، در enum example که در بالا تعریف شد، example_1 مقدار 0 و example_2 مقدار 1 خواهد داشت و به همین ترتیب.
میتوان مقدار هر عنصر یک enum را مشخص کرد و در این صورت، بهطور پیشفرض، عنصر بعدی عدد صحیح بعدی را بهعنوان مقدار خود خواهد داشت:
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 پایان مییابند، بنابراین وقتی بایتی با مقدار 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);
این تابع مقدار بازگشتی ندارد و باید یک رشته به سبک C را در مکانی که buffer مشخص میکند ذخیره کند.
این رشته باید بهصورت بصری نشان دهد چه نوع ارزی به تابع داده شده است:
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 درصدی است که بهعنوان کارمزد تبدیل گرفته میشود و بهصورت عدد صحیح نوشته میشود.
باید آن را با تقسیم بر ۱۰۰ به عدد اعشاری تبدیل کرد.
توجه کنید که spread به نرخ تبدیل اضافه میشود؛ بنابراین spread برابر با 10 نرخ تبدیلی ۱۰٪ بالاتر به دست میدهد.
این تابع باید بیشینهی ارزش ارز جدید را پس از محاسبهی نرخ تبدیل تعدیلشده بر اساس spread برگرداند. یادتان باشد که ارزش اسمی ارز یک عدد کامل است و نمیتوان آن را به بخشهای کوچکتر تقسیم کرد.
exchangeable_value(127.25, 0,873350884, 10, 20);
// => 120
exchangeable_value(127.25, 0,873350884, 10, 5);
// => 130
در Exercism ثبتنام کنید تا x86-64 Assembly را همراه با 22 مفهوم130 تمرین و مربیگری انسانی واقعی یاد بگیرید و در آن استاد شوید، همهی اینها رایگان.