Try   HackMD

2022q1 Homework4 (quiz4)

contributed by < Xx-oX >

題目

測驗 5

巨集 ACCESS_ONCE 作用是確實地讀取所指定記憶體位址的內容值,且限這一次。
常用於防止編譯器最佳化導致讀取變數的指令被省略。
下方是可能的實作

#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 的特性為所有成員的初始記憶體位置均對齊,並且大小由最大的成員決定







g



__u

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