給定 list.h 作為〈linked list 和非連續記憶體操作〉提及的 Linux 核心風格 circular doubly-linked list 實作,欲處理的節點採用以下結構體:
用以對節點內含值比較的函式:
以下程式碼實作快速排序法,數值由小到大排列,並確保可達到 stable sorting 的目標。
請補完,使其行為符合預期。作答規範:
- AAA, BBB, CCC, DDD, EEE, FFF 應以最簡潔的形式撰寫,且符合作業一排版規範 (近似 Linux 核心程式碼排版風格),均不包含小括號 (即 ( 和 )),且不可出現 , 和 ; 這樣的分隔符號 (separator)
- BBB 為 identifier
- CCC, DDD, EEE, FFF 均為以 list_ 開頭的巨集 (macro)
程式碼原理
-
首先先來看 list_first_entry
此巨集的參數,為 list_first_entry(head, type, member)
,可知 AAA
會是 type of the entry
,即為 item
, BBB
則是 member variable , item member variable 有兩種,為 uint16_t i
與 struct list_head list;
,而 pivot
要取得 list 的第一個位置,所以 BBB
即為 list
。
-
CCC (itm, is, head, list)
為含有 4 個參數的巨集,且排序法須對每個節點走訪,所以 CCC
即為 list_for_each_entry_safe
。
-
再迴圈中使用 cmpint
來比大小,比 pivot
小的節點接到 list_less
後面,比 pivot
大的節點接到 list_greater
後面,所以 DDD
與 EEE
皆為 list_move
。
-
最後將 pivot
, list_less
, list_greater
串起來,list_greater
要串接在 list 後面,所以 FFF
即為 list_splice_tail
。
答案
AAA = item
BBB = list
CCC = list_for_each_entry_safe
DDD = list_move
EEE = list_move
FFF = list_splice_tail
程式碼
延續上方測驗 1
,參考 Optimized QuickSort — C Implementation (Non-Recursive),將以下程式碼改寫為非遞迴 (non-recursive; iterative) 的實作:
程式碼原理
-
GGGG (itm, is, head, list)
為含有 4 個參數的巨集,且排序法須對每個節點走訪,所以 GGGG
即為 list_for_each_entry_safe
。
-
再進入迴圈前,會先將 inpur list ,也就是 stack[0]
接在 partition
後,當 partition
不為空也不為單時,會初始化 list_less
與 list_greater
,並將比 pivot
小的節點接到 list_less
後面,比 pivot
大的節點接到 list_greater
後面,而 HHHH (&pivot->list, &list_less);
,因為 pivot
比 list_less
都還大,故應將 pivot
接在list_less
後,所以 HHHH
即為 list_add_tail
。
-
此時分類完的list_greater
與 list_less
應放入 stack
中,,此時 top = -1;
,所以應先將 top++;
,再將 list_greater
與 list_less
個別放入 stack
裡,所以綜合以上兩步驟, IIII
與 JJJJ
即為 stack[++top]
。
-
若 partition
為空或為單,則進入 else
裡面,若 partition
有一個值,那必定是最小值,將這個值放入最上層的 stack
裡,並將該值接到 head
後面,而 top
會指向下一層,所以須做 top--;
,綜合以上兩步驟,可知 KKKK
即為 stack[top--]
。
答案
GGGG = list_for_each_entry_safe
HHHH = list_add_tail
IIII = stack[++top]
JJJJ = stack[++top]
KKKK = stack[top--]
程式碼
XOR 運算特性:
以下程式碼是個 XOR linked list 的實作,在這樣的資料結構可表示為:
要往下一格移動時,我們需要前一格在哪和這一格的位置。例如我們現在在 HAT (1), 已知前一格是 (0),那麼下一格就是 link(1) XOR 0 = 2 XOR 0 = 2,也就是 CAT。繼續,現在位於 CAT (2),前一格在 (1),於是下一格就是 link(2) XOR 1 = 2 XOR 1 = 3,亦即 EAT,依此類推。只要當找出來的下一格是 (0) 就結束。
XOR linked list 的優勢在於空間的佔用更精簡,與尋常作法相比,佔用的儲存空間比值會趨近 67%
。
以下是學習 Linux 核心 list.h 風格的 XOR linked list 實作,其中 ## 為 C 語言前置處理器,參見 你所不知道的 C 語言:前置處理器應用篇
參考執行輸出:
程式碼列表:
#include <assert.h>
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct _xorlist_node {
struct _xorlist_node *cmp;
} xor_node_t;
typedef struct _xor_list_struct {
xor_node_t head, tail;
uint32_t count;
} xor_list_t;
#define XOR_COMP(a, b) ((xor_node_t *) (LLLL))
static inline xor_node_t *address_of(xor_node_t *n1, xor_node_t *n2)
{
assert(n1 && n2);
return XOR_COMP(n1, n2);
}
#define container_of(ptr, type, member) \
__extension__({ \
const __typeof__(((type *) 0)->member) *__pmember = (ptr); \
(type *) ((char *) __pmember - offsetof(type, member)); \
})
#define xorlist_for_each(node, rp, rn, list) \
for (rp = &(list)->head, node = rp->cmp; node != &(list)->tail; \
rn = address_of(rp, node->cmp), rp = node, node = rn)
#define xorlist_for_each_prev(node, rp, rn, list) \
for (rp = &(list)->tail, node = rp->cmp; node != &(list)->head; \
rn = address_of(rp, node->cmp), rp = node, node = rn)
#define xorlist_for_each_from(node, pos1, pos2, rp, rn, list) \
for (rp = pos2, node = pos1; node != &(list)->tail; \
rn = address_of(rp, node->cmp), rp = node, node = rn)
#define xorlist_for_each_from_prev(node, pos1, pos2, rp, rn, list) \
for (rp = pos1, node = pos2; node != &(list)->head; \
rn = address_of(rp, node->cmp), rp = node, node = rn)
#define xorlist_delete_prototype(name, node) \
int _xorlist_delete_##name(xor_node_t *node)
#define xorlist_delete_call(name) _xorlist_delete_##name
static inline xor_node_t *xornode_init(xor_node_t *n)
{
assert(n);
n->cmp = NULL;
return n;
}
#define XORNODE_INIT(n) \
do { \
(n).cmp = NULL; \
} while (0)
#define XORLIST_INIT(h) \
do { \
(h).head.cmp = &(h).tail; \
(h).tail.cmp = &(h).head; \
(h).count = 0; \
} while (0)
int xorlist_add(xor_list_t *list, xor_node_t *n)
{
xor_node_t *real_next;
if (!n)
return ENOMEM;
xor_node_t *real_prev = &list->head;
xor_node_t *node = real_prev->cmp;
if (node == &list->tail)
real_next = MMMM;
else
real_next = node;
real_prev->cmp = n;
n->cmp = XOR_COMP(real_prev, real_next);
real_next->cmp = XOR_COMP(n, XOR_COMP(real_prev, PPPP));
list->count++;
return 0;
}
int xorlist_del(xor_list_t *list,
xor_node_t *n,
xor_node_t *target,
int (*delete_func)(xor_node_t *))
{
assert(list && n && target && delete_func);
assert(&list->head != target && &list->tail != target);
xor_node_t *nn = address_of(target, n->cmp);
xor_node_t *an = address_of(n, target->cmp);
xor_node_t *ana = address_of(target, an->cmp);
n->cmp = XOR_COMP(nn, an);
an->cmp = XOR_COMP(n, ana);
delete_func(target);
list->count--;
return 0;
}
int xorlist_destroy(xor_list_t *list, int (*delete_func)(xor_node_t *))
{
assert(delete_func);
xor_node_t *real_prev = &list->head;
xor_node_t *node = real_prev->cmp;
xor_node_t *real_next = address_of(real_prev, node->cmp);
xor_node_t *tmp = real_prev;
real_prev = node;
node = real_next;
while (node != &list->tail) {
real_next = address_of(real_prev, node->cmp);
tmp = real_prev;
real_prev = node;
node = real_next;
if (delete_func(tmp) != 0)
perror("delete function failed");
}
return 0;
}
struct test_node {
int value;
xor_node_t xornode;
};
xorlist_delete_prototype(test_delete, _node)
{
struct test_node *node = container_of(_node, struct test_node, xornode);
free(node);
return 0;
}
int main(void)
{
xor_list_t list;
xor_node_t *p1, *p2;
XORLIST_INIT(list);
for (int i = 0; i < 1000; i++) {
struct test_node *new = malloc(sizeof(struct test_node));
xornode_init(&new->xornode);
new->value = i;
xorlist_add(&list, &new->xornode);
if (i == 5)
p1 = &new->xornode;
if (i == 6)
p2 = &new->xornode;
}
xor_node_t *real_prev, *real_next, *node;
int i = 0;
printf("xorlist_for_each test\n");
xorlist_for_each(node, real_prev, real_next, &list)
{
printf("node [%d] %d\n", i++,
container_of(node, struct test_node, xornode)->value);
}
i = 0;
printf("xorlist_for_from test\n");
xorlist_for_each_from(node, p1, p2, real_prev, real_next, &list)
{
printf("node %d\n",
container_of(node, struct test_node, xornode)->value);
}
i = 0;
printf("xorlist_for_each_from_prev test\n");
xorlist_for_each_from_prev(node, p1, p2, real_prev, real_next, &list)
{
printf("node [%d] %d\n", i++,
container_of(node, struct test_node, xornode)->value);
}
i = 0;
printf("xorlist_for_each_prev test\n");
xorlist_for_each_prev(node, real_prev, real_next, &list)
{
printf("node [%d] %d\n", i++,
container_of(node, struct test_node, xornode)->value);
}
printf("xorlist_del test\n");
xorlist_del(&list, p2, p1, xorlist_delete_call(test_delete));
i = 0;
xorlist_for_each(node, real_prev, real_next, &list)
{
printf("node [%d] %d\n", i++,
container_of(node, struct test_node, xornode)->value);
}
xorlist_destroy(&list, xorlist_delete_call(test_delete));
return 0;
}
請補完程式碼,使其運作符合預期。作答規範:
- LLLL 為表示式,在運算子前後要一個空白字元區隔,應包含 uintptr_t,符合 lab0-c 預期的排版風格
- MMMM 和 PPPP 為表示式
程式碼原理
-
以上例為例,現在位於 CAT (2),前一格在 (1),於是下一格就是 link(2) XOR 1 = 2 XOR 1 = 3,亦即 EAT,也就是說:前一格 XOR link = 下一格,而 前一格 XOR 下一格 = link ,所以只要 link 存的是 前一格 XOR 下一格 的值,我們就能用前一格與 link 的值去找到下一格,所以也不難看出 struct _xorlist_node *cmp;
存的是 link 的值,在函式 xorlist_add
中的 n->cmp = XOR_COMP(real_prev, real_next);
就是要將 link 值放入 cmp
中,所以 #define XOR_COMP(a, b) ((xor_node_t *) (LLLL))
中的 LLLL
為將兩個值做 XOR ,即為 (uintptr_t)(a) ^ (uintptr_t)(b)
。
-
在函式 xorlist_add
中,目的是將節點 n
加入到 list 中,當 n
為最後的節點時,它的下一個節點即為自己,因為自己 XOR 自己 = 0,也就是下一格,所以 MMMM
為 &list->tail
。
-
real_next->cmp = XOR_COMP(n, XOR_COMP(real_prev, PPPP));
,節點 n
的下一個節點的 link 值即為 前一格 XOR 下一格,也就是 n
XOR real_next->next
,而下一格可以從 前一格 XOR 自己本身的 link 值取得,所以 PPPP
為自己本身的 link 值,也就是 real_next->cmp
。
答案
LLLL = (uintptr_t)(a) ^ (uintptr_t)(b)
MMMM = &list->tail
PPPP = real_next->cmp