--- tags: linux2022 --- # 2022q1 Homework4 (quiz4) contributed by < `Xx-oX` > > [題目](https://hackmd.io/@sysprog/linux2022-quiz4) ## 測驗 `5` 巨集 `ACCESS_ONCE` 作用是確實地讀取所指定記憶體位址的內容值,且限這一次。 常用於防止編譯器最佳化導致讀取變數的指令被省略。 下方是可能的實作 ```c #include <stdint.h> #include <string.h> #include <stdlib.h> #define __READ_ONCE_SIZE \ ({ \ switch (size) { \ case 1: \ *(uint8_t *) res = *(volatile uint8_t *) p; \ break; \ case 2: \ *(uint16_t *) res = *(volatile uint16_t *) p; \ break; \ case 4: \ *(uint32_t *) res = *(volatile uint32_t *) p; \ break; \ case 8: \ *(uint64_t *) res = *(volatile uint64_t *) p; \ break; \ default: \ memcpy((void *) res, (const void *) p, size); \ } \ }) static inline void __read_once_size(const volatile void *p, void *res, int size) { __READ_ONCE_SIZE; } #define READ_ONCE(x) \ ({ \ union { \ typeof(x) __val; \ DECL0; \ } __u; \ __read_once_size(&(x), __u.__c, sizeof(x)); \ __u.__val; \ }) ``` 其中 `DECL0` 應填入 `char[1] __c` 觀察 union `__u` 可以發現其中有兩個成員 `__val` 以及 `__c` (由下方程式碼得知名稱) 而 union 的特性為所有成員的初始記憶體位置均對齊,並且大小由最大的成員決定 ```graphviz digraph g{ rankdir = LR node[shape=record] __u [label="{__c|__val}|...|...|..."] } ``` `__read_once_size()` 中使用 `volatile` 關鍵字告知編譯器不要最佳化這段程式碼, 並且將 `__u.__c` 的大小開成 `x` 所佔的大小, 如此一來讀取跟 `__c` 共用記憶體的 `__val` 就可以保證讀取一次 `x` ,達到想要的效果。 而之所以將 `__c` 的型態設成 `char[1]` 則是因為 * 要給他最小的預設空間,以便日後隨著 `x` 所需之空間擴張 => 大小設為 1 byte * `__c` 要是一個 pointer (從 `__read_once_size()` 的呼叫中可以得知) 使用 `char[1]` 便可以同時達到這兩個目的。