# C語言學習筆記 [Sorting algorithm](https://en.wikipedia.org/wiki/Sorting_algorithm") ## 基本語法 :::spoiler static 在 C 語言中,static 關鍵字有幾種不同的用法,它可以用來修飾變量和函數。根據 static 用在全局變量、局部變量還是函數前,它的效果也有所不同。以下是對這些不同用法的解釋和例子: 1. static 用於局部變量 當 static 關鍵字用於函數內的局部變量時,它會改變該局部變量的存儲持續性,使得該變量在函數調用結束後不會被銷毀,而是保持其值直到下次函數調用。這意味著該變量只在首次調用時初始化一次,之後即使函數執行完畢,變量也不會失去其值。 ```cpp= #include <stdio.h> void counter() { static int count = 0; // static局部变量 count++; printf("Count: %d\n", count); } int main() { counter(); // 输出 Count: 1 counter(); // 输出 Count: 2 counter(); // 输出 Count: 3 return 0; } ``` 2. static 用於全局變量或函數 在函數之外使用 static 修飾全局變量或函數時,可以限制變量或函數的作用域,使其只在定義它的文件內可見。這意味著其他文件中的代碼不能直接訪問這個 static 全局變量或函數 ```cpp= // 文件:main.c static int globalVar = 10; // static全局变量 static void printHello() { // static函数 printf("Hello, World!\n"); } int main() { printHello(); // 正常调用 printf("GlobalVar: %d\n", globalVar); return 0; } ``` 3. static 在模塊級別 這種用法主要是為了隱藏模塊內部的實現細節,只對模塊內部可見,從而實現模塊的封裝。 總結 使用 static 修飾局部變量,可以使變量在函數調用之間保持狀態。 使用 static 修飾全局變量或函數,可以限制它們的作用域,只在定義它們的文件中可見,從而減少全局命名空間的污染,並隱藏實現細節。 static 關鍵字是 C 語言中一個非常有用的特性,它提供了變量存儲持續性的控制以及作用域和鏈接性的控制,對於模塊化編程和隱藏實現細節非常重要。 ::: :::spoiler inline 在 C 語言中,inline 關鍵字用於建議編譯器嘗試將某個函數的代碼在每個調用點"內聯"展開,而不是進行常規的函數調用。這意味著編譯器會嘗試將函數調用替換為函數體本身的副本。使用 inline 關鍵字可以減少函數調用的開銷,尤其是在函數體很小且調用頻繁的情況下。 使用 inline 的優點: 減少開銷:避免了函數調用的開銷,如參數傳遞、棧幀的創建和銷毀等。 提高性能:可能使得代碼運行更快,因為省去了函數調用的開銷,並且可能增加編譯器優化的機會。 ```cpp= #include <stdio.h> // 使用inline关键字建议编译器内联展开该函数 inline int max(int x, int y) { return x > y ? x : y; } int main() { int a = 5, b = 3; // 调用内联函数 int m = max(a, b); printf("Max: %d\n", m); return 0; } ``` 在這個示例中,max 函數被聲明為 inline。理想情況下,編譯器會在 main 函數中的調用點將 max(a, b) 替換為 a > b ? a : b 的代碼,從而避免了函數調用的開銷。 結論 inline 關鍵字是一個對編譯器的建議,用於提高小型函數的執行效率。正確使用 inline 可以提高程序的性能,但需要注意避免代碼膨脹和其他潛在問題。 ::: 對某硬體架構,像是 ARM,我們需要額外的 alignment。ARMv5 (含) 以前,若要操作 32-bit 整數 (uint32_t),該指標必須對齊 32-bit 邊界 (否則會在 dereference 時觸發 exception)。於是,當要從 void * 位址讀取 uint16_t 時,需要這麼做: ```cpp /* may receive wrong value if ptr is not 2-byte aligned */ /* portable way of reading a little-endian value */ uint16_t value = *(uint16_t *) ptr; uint16_t value = *(uint8_t *) ptr | ((*(uint8_t *) (ptr + 1)) << 8); ``` ## forward declaration :::spoiler forward declaration 當談到C語言中的"forward declaration"(前置宣告)時,通常指的是在程式中提前聲明函數或變數,以便在其實際定義之前就可以在程式中使用它們。這在某些情況下很有用,特別是當你需要在不同的文件或模塊中使用函數或變數,或者在函數之間有相互調用的情況。 1. 避免循環依賴:當你有多個函數相互調用的時候,可能會出現循環依賴的問題,也就是A函數依賴於B函數,但B函數又依賴於A函數。通過前置宣告,你可以在需要的地方提前聲明函數,從而解決這個問題。 2. 函數的前置宣告:要前置宣告一個函數,你可以在函數使用之前 3. 變數的前置宣告:前置宣告也可用於變數,以在變數的實際定義之前通知編譯器變數的存在。 前置宣告函數: 假設你有兩個函數,其中一個函數依賴於另一個函數。你可以使用前置宣告來解決這個問題。首先,你需要在使用前置宣告之前聲明這兩個函數: ```cpp= // 前置宣告第一個函數 void functionA(); // 前置宣告第二個函數 void functionB(); int main() { functionA(); // 調用 functionA return 0; } void functionA() { printf("Function A\n"); functionB(); // 調用 functionB } void functionB() { printf("Function B\n"); } ``` 前置宣告變數: 同樣,你也可以使用前置宣告來聲明變數的存在。這在全局變數或跨文件使用的變數時很有用。例如 ```cpp= // 前置宣告全局變數 extern int globalVariable; int main() { // 使用全局變數 globalVariable = 10; printf("Global variable: %d\n", globalVariable); return 0; } // 實際定義全局變數 int globalVariable; ``` ::: ## Pointers vs. Arrays * in declaration extern, 如 extern char x[]; 不能變更為 pointer 的形式 * definition/statement, 如 char x[10] 不能變更為 pointer 的形式 * parameter of function, 如 func(char x[]) 可變更為 pointer 的形式 func(char *x) * in expression array 與 pointer 可互換 ## concept x[i] 總是被編譯器改寫為 *(x + i) in expression C 提供操作多維陣列的機制 (C99 [6.5.2.1] Array subscripting),但實際上只有一維陣列的資料存取 二維陣列也只是一個語法糖,C最後仍然透過轉換轉成一維的位址做操作(透過指標的方法做降維) array subscripting 在編譯時期只確認以下兩件事: * 得知 size * 取得陣列第 0 個 (即最初的一項) 元素的指標 ```cpp= int a[3]; int *p; p = a; /* p points to a[0] */ *a = 5; /* sets a[0] to 5 */ *(a+1) = 5; /* sets a[1] to 5 * ``` Suppose we want to concatenate string s and string t into a single string ```cpp= char *r; strcpy(r, s); strcat(r, t); ``` Doesn't work cause r doesn't point anywhere.所以可能會找不到 如果stecpy或strcat導致那個r超過原本配置的空間就會出問題,可能影響資料安全 ```cpp= char *r = malloc(strlen(s) + strlen(t)); strcpy(r, s); strcat(r, t); ``` This fails for three reasons: 1. malloc() might fail to allocate the required memory 1. It is important to free the memory allocated 1. We did not allocate enough memory ```cpp= char *r = malloc(strlen(s) + strlen(t) + 1); // use sbrk; change program break if (!r) exit(1); /* print some error and exit */ strcpy(r, s); strcat(r, t); free(r); r = NULL; /* Try to reset free’d pointers to NULL */ ``` * 如果使用 "const char *string1" 又不能用"sizeof(string1)/sizeof(string1[0])"知道字串的大小,因為這邊所得到的sizeof永遠都是指標 4 的大小(32 bits) 或 8 (64 bits),所以在程式上必須使用 strlen(string1)讀取字串的大小 ```cpp= int main() { const char *string1 = "Hello, World!"; int size = strlen(string1); printf("Size of string1: %d\n", size); return 0; } ``` ## Function Pointer 函數是一等公民:在C語言中,函數被視為一等公民,這意味著它們可以像變數一樣被操作。你可以將函數的地址存儲在指標變數中,也可以將函數作為參數傳遞給其他函數。 函數指標的聲明:聲明函數指標時,你需要指定函數的返回類型和參數類型,以確定指標的類型。例如,int (*ptr)(int, int) 宣告了一個指向返回 int 且接受兩個 int 參數的函數指標。 函數指標的賦值:你可以將函數的地址賦值給函數指標,這樣該指標就可以調用相應的函數。例如,ptr = &add,其中 add 是一個接受兩個整數並返回整數的函數。 使用函數指標調用函數:使用函數指標調用函數時,你可以使用函數指標的名稱後跟括號 () 並傳遞相應的參數。例如,result = ptr(10, 20) 調用了 add 函數。 ```cpp= #include <stdio.h> // 定義兩個數學函數 int add(int a, int b) { return a + b; } int subtract(int a, int b) { return a - b; } int main() { // 宣告一個函數指標,該指標可以指向接受兩個整數並返回整數的函數 int (*mathFunc)(int, int); int x = 10, y = 5; char operation = '+'; // 根據運算符選擇要執行的函數 if (operation == '+') { mathFunc = add; } else if (operation == '-') { mathFunc = subtract; } else { printf("Invalid operation\n"); return 1; } // 使用函數指標調用選擇的函數 int result = mathFunc(x, y); printf("Result: %d\n", result); return 0; } ``` 這個示例中,我們之所以使用函數指標來調用選擇的函數,是為了實現根據運算符(如加法或減法)的不同值來動態選擇要執行的函數。這樣做有以下好處: 靈活性:使用函數指標,你可以在運行時決定要調用哪個函數,而不需要在編譯時固定函數的調用。這允許你在不修改程式碼的情況下根據不同情況執行不同的操作。 簡化程式邏輯:如果不使用函數指標,你可能需要使用一個 if-else 或 switch 語句來根據運算符的不同值執行相應的操作。使用函數指標可以使程式碼更簡潔,更容易閱讀和維護。 可擴展性:如果你希望添加更多的操作或函數,只需修改選擇函數的邏輯,而不需要更改函數調用的程式碼。這種可擴展性使得在不影響現有程式碼的情況下擴展功能變得更容易。 ## opinter and const 指標本身不可變更 (Constant pointer to variable): const 在 * 之後 char * const pContent; 指標所指向的內容不可變更 (Pointer to constant): const 在 * 之前 const char * pContent; char const * pContent; 兩者都不可變更 const char * const pContent; free() 釋放的是 pointer 指向位於 heap 的連續記憶體,而非 pointer 本身佔有的記憶體 (*ptr) glibc 提供了 malloc_stats() 和 malloc_info() 這兩個函式,可顯示 process 的 heap 資訊 ## data alignment :::spoiler data alignment 一個 data object 具有兩個特性: * value * storage location (address) data alignment 的意思就是, data 的 address 可以公平的被 1, 2, 4, 8,這些數字整除,從這些數字可以發現他們都是 2 的冪 (power of 2),亦即這些 data object 可以用 1, 2, 4, 8 byte 去 alignment。 但如果你的資料是分布在第 1-4 byte(上圖右側例子),那麼事情就不太一樣了: * 第一次取第 0-3 byte,將第 0 byte 的資料去掉,留下第 1-3 byte * 第二次取第 4-7 byte,將第 5-7 byte 的資料去掉,留下第 4 byte 最後將第一次的第 1-3 byte 與第二次的第 4 byte 合起來 可見如果資料位址不在 4 的倍數,會導致存取速度降低,編譯器在分配記憶體時,會按照宣告的型態去做 alignment。 struct 會自動做 alignment,假設宣告以下這個結構體: ```cpp= struct s1 { char c; int a; } ``` 原本推論 char 為 1 byte,而 int 為 4 byte ,兩個加起來應該為 5 byte,然而實際上為 8 byte,由於 int 為 4 byte ,所以 char 為了要能 alignment 4 byte 就多加了 3 byte 進去 ,使得 cpu 存取速度不會因 address 不是在 4 的倍數上,存取變慢。 ![image](https://hackmd.io/_uploads/BJVhOPA5a.png) ## malloc 的設置與分配 這個 "malloc" 流程描述了當你在程式中調用 malloc 函數時,系統分配記憶體的過程。以下是該流程的簡要解釋: 調整 malloc size:首先,根據你要分配的記憶體大小進行調整。這包括添加一些額外的空間(overhead)以及確保記憶體對齊。如果要分配的大小小於32位元組(在64位元系統上是最小大小,相當於4個指標的大小),則補充到至少32位元組的大小。 檢查 fastbin:快速檢查一個叫做 "fastbin" 的記憶體儲存區,看看是否有符合你要求大小的可用記憶體塊。如果有,就直接返回該塊。 檢查 smallbin:接下來檢查 "smallbin",這是一個按大小劃分的記憶體區域,看看是否有符合你要求大小的可用記憶體塊。如果有,就直接返回該塊。 合併 fastbin:如果前面的步驟都失敗,則呼叫 malloc_consolidate 函數來合併 "fastbin"。這個過程取消下一個記憶體塊的 PREV_INUSE 標誌,然後將合併的塊放入 "unsorted bin" 中。 處理 unsorted bin: 如果 "unsorted bin" 中只有一個塊(通常是上一次分配後的剩餘記憶體),並且大小足夠,則分割該塊並返回。剩餘的空間變成新的 "last_remainder"。 否則,迭代處理 "unsorted bin" 中的每個塊,如果有一個塊的大小正好符合你的需求,就返回該塊。否則,將該塊移動到對應大小的 bin 中,重複這個過程,直到 "unsorted bin" 為空或重複10000次為止。 在 small / large bin 中找 best fit:如果前面的步驟都無法滿足需求,則在 "small bin" 或 "large bin" 中尋找最合適的記憶體塊。如果找到,則將其分割並返回,剩下的部分放入 "unsorted bin"(成為 "last_remainder")。 使用 top chunk:如果仍無法滿足需求,則分割 "top chunk",如果大小不足,則合併 "fastbin",如果仍不足,則進行系統調用(system call)以獲取更多記憶體。 總之,這個 "malloc" 流程描述了當你需要分配記憶體時,系統如何在不同的記憶體區域中搜索可用的記憶體塊,並在必要時合併、分割、移動記憶體以滿足你的需求。這是一個高度優化的流程,旨在提高記憶體分配的效率。 ## free流程 檢查 : 檢查 pointer 位址、alignment、flag 等等,以確認是可 free 的 memory 合併(consolidate) fastbin size : 不進行合併 其他 : 檢查前一 chunk,若未使用則合併 檢查後一 chunk,若是 top chunk 則整塊併入 top chunk,若否但未使用,則合併 將合併結果放入 unsorted bin ::: ## function prototype ```cpp= #include <stdio.h> int factorial(int n); int main(void) { printf("%d\n", factorial()); return 0; } int factorial(int n) { if (n == 0) return 1; return n * factorial(n - 1); } ``` factorial 函式在被呼叫時,會從堆疊 (stack) 或暫存器中取出整數型態的參數,若忽略第 3 行 function prototype,編譯器將無從正確判斷,在第 6 行傳遞給 factorial 函式的參數是否符合預期的型態和數量。過往不用在 C 程式特別做 function prototype 的特性已自 C99 標準中刪除,因此省略 function prototype 將導致編譯錯誤 C 語言中的 "function prototype" 是用來聲明函數的一種方式,以便編譯器在編譯時能夠確切地了解函數的接口(即函數的名稱、參數的數量和型別以及返回值的型別)。函數原型的主要目的是告訴編譯器如何使用該函數,以便在編譯時進行類型檢查和函數呼叫的合法性檢查。 編譯器裡頭的最佳化階段 (optimizer) 可從 function prototype 得知,傳遞給函式 compare 的兩個指標型態參數,由於明確標注 const 修飾子,所以僅有記憶體地址的使用並讀取相對應的內容,但不會因而變更指標所指向的記憶體內容,從而沒有產生副作用 (side effect)。這樣編譯器可有更大的最佳化空間 ## 藏在 Heap 裡的細節 程式如下 n 會大到 107,當 arr 用 line 4 宣告會 segmentation fault 但改成動態宣告就可以正常執行了,為什麼呢? 想過會不會是 long (8 byte) * 10^7 把記憶體用完了? ```cpp= unsigned n, m; scanf("%u %u", &n, &m); long arr[n + 1]; // long *arr = malloc((n + 1) * sizeof(*arr)); for (size_t i = 1; i <= n; ++i) arr[i] = 0; ``` 使用 long arr[n+1] 其實是存於stack中,可能會爆炸導致segmentation,而動態分配的malloc是存於Heap中,記憶體可用的較大。 而malloc與calloc的差異在於,malloc是動態分配於已經用過的記憶體中,不一定記憶體會給予清0的空間,而calloc則是找到從0開始的記憶體給予,如果calloc可以編譯成功,盡量使用calloc加速運算。 從實作的觀點,calloc 和 malloc + memset 是不同的! 1. 之所以 stack 不可以 heap 可以 是因為 long arr[n + 1]; 時,作業系統會去衡量要求的記憶體足不足夠,此時發現要得太大,因此發出 segfault. 而 malloc() 是屬於妳要多少跟我說,我都給妳 (overcommit)。 所以拿到的空間可能會比要求的小,作業系統發現開始不夠用時就開始殺東殺西把空間滕出來,直到砍到沒東西能砍了 OOM。 因此發現,malloc() 存取大容量其實是有很大風險的, 先前使用 stack 會 segfault 其實是一個 "正確" 的錯誤提醒,而不是錯誤,我誤會他了。 2. calloc() 跟 malloc + memset 有很大的不同: calloc() 跟作業系統要空間時會拿到歸好零的記憶體 (for security),已歸零的就不重覆歸零。   我們無法保證 malloc 要回來的空間是否為 0,因此都需要 memset 過。(可能重工) calloc() 在要大空間 (128KB↑) 時,如果執行環境能夠配合,就會有效率得多: kernel 分配 VM 給 calloc(),而 VM 空間用完時再執行 騰出空間、歸零、swap 的動作 (amortize cost);malloc() 先執行這些動作。 free() 釋放的是 pointer 指向位於 heap 的連續記憶體,而非 pointer 本身佔有的記憶體 (*ptr)。 ```cpp! #include <stdlib.h> int main() { int *p = malloc(1024); free(p); free(p); return 0; } ``` 編譯不會有錯誤,但運作時會失敗 ```cpp! #include <stdlib.h> int main() { int *p = malloc(1024); free(p); p = NULL; free(p); return 0; } ``` 則會編譯和執行都成功。 因此,為了防止對同一個 pointer 作 free() 兩次的操作,而導致程式失敗,free() 後應該設定為 NULL。 malloc/free 實際的實作透過 sbrk 系統呼叫 ## Bins and Chunks A bin is a list (doubly or singly linked list) of free (non-allocated) chunks. Bins are differentiated based on the size of chunks they contain: * Fast bin * Unsorted bin * Small bin * Large bin 當你在 C 語言中使用 malloc 函數來分配記憶體時,記憶體管理器會使用一個複雜的機制來管理分配和釋放記憶體。這個機制包括多個元件,包括 "Bins" 和 "Chunks"。以下是針對這個機制的簡要解釋: 1. Chunks(記憶體塊):記憶體管理器使用 "chunks" 來分配和管理記憶體。一個 "chunk" 是一個大的區域,通常是從操作系統獲取的。每個 "chunk" 可以包含多個 "bins",每個 "bin" 是一個特定大小的區域。 1. Bins(記憶體區域):每個 "bin" 是一個特定大小的記憶體區域,用來存儲已分配或未分配的記憶體區塊。Bins 根據記憶體塊的大小進行分類,以便高效地分配和釋放記憶體。Bins 通常分為不同的類別,例如 "fast bins"、"small bins" 和 "large bins",每個類別處理不同大小的記憶體分配請求。 1. Malloc Process(malloc 過程):當你調用 malloc 函數請求記憶體,記憶體管理器會嘗試從適當大小的 bin 中找到一個可用的記憶體區域。如果找到了合適大小的 bin,則會分配該記憶體區域並返回給你。如果沒有足夠大的 bin,管理器可能會嘗試合併或分割現有的記憶體區塊以滿足你的需求,或者可能會向操作系統請求一個新的 chunk。 1. Free Process(free 過程):當你使用 free 函數釋放已分配的記憶體時,記憶體管理器將該記憶體區域標記為未使用,並將其返回到適當的 bin 中,以便將來再次使用。 ## bitwise 操作 算術位移的應用,若要判斷一個 int 型態的變數 n 是否為正數,可用 n >> 31 其等價於 n >=0 ? 0: -1. 若 n 是 32-bit 整數,那麼 abs(n) 等同於 ((n >> 31) ^ n) - (n >> 31) 當 n 是正數時: * n >> 31 是 0; n ^ 0 仍是 n; n - 0 仍是 n 當 n 是負數時: * n >> 31 是 -1; -1 以 2 補數表示為 0xFFFFFFFF; n ^ (-1) 等同於 1 補數運算; 最後再減 -1,得到 2 補數運算的值 [][[C_Bitwise_Operators](https://doc.callmematthi.eu/C_Bitwise_Operators.html)] 1. Set a bit ```cpp! unsigned char a |= (1 << n); ``` ```cpp! a 1 0 0 0 0 0 0 0 a |= (1 << 1) = 1 0 0 0 0 0 1 0 a |= (1 << 3) = 1 0 0 0 1 0 0 0 a |= (1 << 5) = 1 0 1 0 0 0 0 0 ``` Clear a bit: ```cpp! unsigned char b &= ~(1 << n); ``` ```cpp! b 1 1 1 1 1 1 1 1 b &= ~(1 << 1) = 1 1 1 1 1 1 0 1 b &= ~(1 << 3) = 1 1 1 1 0 1 1 1 b &= ~(1 << 5) = 1 1 0 1 1 1 1 1 ``` ```cpp! #include <stdio.h> /* bit clear: a: int, pos: bit position to clear */ #define CLEARBIT(a, pos) (a &= ~(1 << pos)) int main() { /* 'z': decimal value 122 (=01111010) */ char a = 'z'; /* clearing the 5th bit */ char aOut = CLEARBIT(a, 5); /* aOut = 'Z': decimal value 90 (=01011010) */ printf("aOut=%c\n", aOut); return 0; } ``` Toggle a bit: ```cpp! unsigned char c ^= (1 << n); ``` sign bit: ```cpp! bool sign = val & 0x8000; // sign bit ``` Setting and Clearing a Bit: ```cpp! #include <stdio.h> #include <stdbool.h> void binary(unsigned int n) { for (int i = 256; i > 0; i /= 2) { if (n & i) printf(" 1"); else printf(" 0"); } printf("\n"); } bool getBit(int n, int index) { return ((n & (1 << index)) > 0); } int setBit(int n, int index, bool b) { if (b) return (n | (1 << index)); int mask = ~(1 << index); return n & mask; } int main() { int num = 16, index; printf("Input\n"); for (int i = 7; i >= 0; i--) printf("%d ", getBit(num,i)); printf("\n"); /* set bit */ index = 6; printf("# Setting %d-th bit\n", index); num = setBit(num, index, true); for (int i = 7; i >= 0; i--) printf("%d ", getBit(num,i)); printf("\n"); /* unset (clear) bit */ index = 4; printf("# Unsetting (Clearing) %d-th bit\n", index); num = setBit(num, index, false); for (int i = 7; i >= 0; i--) printf("%d ", getBit(num,i)); printf("\n"); return 0; } ``` 嘗試不用 XOR 運算子做出等效的 XOR 運算 ```cpp! int my_xor(int x, int y) { return (x | y) & (~x | ~y); } ``` ## 作業系統 ## 即時作業系統 RTOS多任務運行的實現實際上是靠CPU在許多任務之間轉換、調度。 因此在多任務即時操作系統RTOS中涉及到任務調度,任務調度機制是嵌入式即時操作系統的一個重要概念,也是其核心技術。針對嵌入式即時系統任務的管理和調度的特點,在多任務即時內核結構中,多數採用的是基於優先順序的可搶佔式調度策略。系統為每一個任務分配一個優先順序,調度程式總是選擇優先順序最高的就緒任務運行。 在一個系統中,可能會有多個任務同時準備執行,這些任務根據優先級被調度。為了高效地找到當前具有最高優先級的任務,這個系統使用了一個特殊的查找表(OSUnMapT[255])來加速這一過程 OSRdyG:這是一個表示全局任務就緒狀態的變量,用一個字節(8位)表示,不同的位代表不同的任務組就緒狀態。 OSRdyT[n]:這是一個數組,每個元素對應一個任務組中的具體任務就緒狀態,每個元素也是一個字節(8位),不同的位代表組內不同任務的就緒狀態。 OSUnMapT[255]:這是一個查找表,用於將OSRdyG或OSRdyT[n]的值映射到具有最高優先級的任務的索引。表中的每個值都表示相應字節值中最高優先級任務的位位置。 例子解釋: 假設OSRdyG的值為二進制00100100(十六進制0x24),這表示全局就緒任務組中有兩組任務就緒,分別在位2和位5。查找OSUnMapT[0x24]得到的值是2,這意味著在OSRdyG表示的任務組中,位於第2位的任務組具有最高的優先級。 進一步,如果在這個最高優先級的任務組(第2組)中,某個任務的就緒狀態(OSRdyT[2])為二進制11000010(十六進制0xC2),這表示組內有兩個任務就緒,分別在位1和位7。查找OSUnMapT[0xC2]得到的值是1,這意味著在第2組中,位於第1位的任務具有最高的優先級。 ```cpp! high3 =OSUnMapT [OSRdyG]; low3 =OSUnMapT[OSRdyT [high3]]; highprio =(high3<<3)+low3; ```