在前面的概念中提過,global指示詞會讓定義在某個原始碼檔案中的函式或變數,能被其他檔案看見。
同樣地,extern指示詞會告訴組譯器,目前這個原始碼檔案裡用到的函式或變數是定義在另一個檔案中。
這兩個指示詞就是組合語言程式碼與其他原始碼檔案互動、實現模組化的主要方式。
對這兩個指示詞而言,外部程式碼是用組合語言還是高階語言定義或使用,並沒有差別。
所以舉例來說,即使是高階語言定義的函式,只要宣告成extern,就能在 x86-64 程式碼中使用。
同樣地,呼叫慣例是所有這些函式共通的,無論它們是用組合語言還是用高階語言定義,甚至連作業系統(OS)也一樣。
不過,由於在組合語言中資料只是一連串的位元組,因此了解與之介接的高階語言如何管理記憶體就很重要。
C 語言是搭配組合語言程式碼時很常見的高階語言選擇。 它讓與作業系統互動的工作變得更容易,為許多重要任務提供高階的包裝,例如輸出到螢幕、從鍵盤讀取輸入,或配置動態記憶體。
在這個軌道中,測試是用 C 撰寫的,因此有必要對這個語言做個概括介紹。
下面提到的任何型別都可以加上const修飾。
這會讓它們變成唯讀。
C 有許多基本型別,它們的大小(以位元組計)可能各不相同。 下表整理了其中一些,以及它們在 x86-64 系統上常見的大小:
| 型別 | 位元組數 | 整數/浮點數 |
|---|---|---|
| _Bool | 1 | 整數 |
| char | 1 | 整數 |
| short | 2 | 整數 |
| int | 4 | 整數 |
| long | 4 或 8 | 整數 |
| long long | 8 | 整數 |
| float | 4 | 浮點數 |
| double | 8 | 浮點數 |
這些整數型別每一個都可以是有號(預設)或無號。
如果是無號,就必須明確指定,例如:unsigned int。
有些整數型別的別名,其定義是為了能精確控制它們的大小:
| 別名 | 位元組數 |
|---|---|
| int8_t | 1 |
| int16_t | 2 |
| int32_t | 4 |
| int64_t | 8 |
這些別名可以加上u前綴來表示無號型別,例如:uint64_t。
其他值得注意的別名還有:
| 別名 | 型別 |
|---|---|
| bool | _Bool |
| size_t | 通常是無號的 8 位元組整數 |
_Bool(或其別名bool)特別的地方在於,雖然它佔了整整 1 個位元組,卻只能取兩個值的其中一個:true(1)和false(0)。
這些基本型別的值在傳入函式和從函式回傳時,都遵循整數和浮點數的一般規則。
列舉是一種型別,可以取數個明確指定元素中的其中一個:
enum example {
example_1,
example_2,
example_3,
example_4
};
在 C 中,列舉會隱式轉換成整數型別,通常是int,也就是 4 位元組的整數。
預設情況下,列舉中第一個可能元素會轉換成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的記憶體位置。
位址照慣例會被當成 8 位元組的整數。
陣列在傳入函式和從函式回傳時,都是以指向其第一個元素的指標形式。
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有 3 個元素,每個 8 位元組,總共 24 位元組。
由於陣列不會儲存自己的長度,通常需要另外用一個值來表示元素數量,除非是以哨兵值標示結尾。
C 的字串是由char組成的陣列,而char是 1 位元組的型別。
大多數字串由 ASCII 字元組成並以 NUL 結尾,也就是說,當遇到值為0的位元組時就結束。
這表示字串的長度通常不需要另外當作引數傳遞。
你的朋友 Chandler 打算走訪世界各地充滿異國風情的國家。 可惜的是,Chandler 的數學不太好。 他很擔心旅途中會被兌換所坑騙,所以希望你幫他做一個貨幣計算機。
以下是他對這個應用程式的規格要求。
這個練習中的函式會宣告在 C 原始檔裡,並附有已提供的簽章。 你的 x86-64-assembly 程式碼必須遵循這些規格。
你已經建立了一個 enum currency_t,用來記錄 Chandler 旅途中會用到的各種貨幣:
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 引數代表 Chandler 要兌換成 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 是以整數表示、作為兌換手續費_收取的百分比_。
它需要除以 100 換算成小數。
注意,價差會加到匯率上,所以價差 10 會讓匯率提高 10%。
這個函式應該計算由價差調整後的匯率,然後回傳新貨幣的最大價值。 記住,貨幣的面額是整數,不能再細分。
exchangeable_value(127.25, 0,873350884, 10, 20);
// => 120
exchangeable_value(127.25, 0,873350884, 10, 5);
// => 130