---
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]` 便可以同時達到這兩個目的。