起因
x86/ARM 上,變數在記憶體上並不是起始於隨意的位置。而會是該類類型大小的整數倍,這樣讀取資料(變數)可以比較快。
為什麼不是隨意 bytes 位置
CPU 通常是讀 Word (4/8 bytes),假如是隨意的 bytes 位置,那就有可能為了讀一個uint16_t
(2 bytes) 去讀兩個 words,會比較慢。
所以有時會在「連續」的變數之間出現 gap。
uint32_t a; // 4 bytes
char gap[N]; // N bytes
uint64_t b; // 8 bytes
這個 N ,可以是 0 或 4(因為uint32_t
只要求起始於 4 的倍數的位置)。 另外,gap 的值並沒有保證是 0。
Struct (Not c++ class)
通常一個 Struct 的 Alignment (起始位置是 k倍數) 會去考慮裡面最寬的成員。
- k 是最寬的寬度
- Struct 的起始位置會是 k倍數
- 尾巴也有可能補足 bytes 使得整個 Struct 大小是 k倍數
主要原因是考量到 Struct 被作為陣列使用時,每個成員的對齊仍然需要滿足前面所說的條件。
Struct Example
首先來看一個範例:
struct bar {
char c; // 1 byte
uint64_t v1; // 8 bytes
uint16_t v2; // 2 bytes
};
struct bar
會是 8-byte alignment 的(位置是 8 的倍數),因為裡面有 uint64_t
。
並且 padding 會長得像是這樣,因為 uint64_t
是 8-byte alignment。
由於 Struct 本身是 8-byte alignment,所以尾巴會有 6 bytes。
struct bar {
char c; // 1 byte
char gap[7]; // 7 bytes
uint64_t v1; // 8 bytes
uint16_t v2; // 2 bytes
char unused[6]; // 6 bytes
};
假如是一個 2-byte alignment 的 struct:
struct foo {
uint16_t a; // 2 bytes
char c; // 1 byte
char pad[1]; // 1 byte
}
重排成員
適當重排 Struct 成員可以減少 Struct 的 padding。
本來 Padding 用掉: 7 + 6 = 13
struct bar {
char c; // 1 byte
uint64_t v1; // 8 bytes
uint16_t v2; // 2 bytes
};
調換成員的位置後Padding: 5
struct bar {
uint64_t v1; // 8 bytes
uint16_t v2; // 2 bytes
char c; // 1 byte
};
對 Struct 重排成員是也有著壞處,程式也是要給人看的,這可能會影響到可讀性。
效能也可能有影響, 本來擺在一起的成員是紀錄同一個功能的, 快取本來可以處理到這塊,但分開後快取效果就消失了。
Packed Struct
不過 c/c++ 並不是沒有把 padding 排除的選項,下編譯參數或者使用巨集都可以讓這 Padding 消失。
struct __attribute__((__packed__)) foo3 {
char c;
uint64_t v1;
uint16_t v2;
};
// sizeof(foo3) == 9
測試
offsetof(TYPE, MEMBER)
可以取得 type 裡面 member 的位置。sizeof
C/C++
#include <iostream>struct foo1 {
char c;
uint64_t v1;
uint16_t v2;
};struct foo2 {
uint64_t v1;
uint16_t v2;
char c;
};int main() {
std::cout << "foo1:" << sizeof(foo1) << "\\\\n";
std::cout << "foo2:" << sizeof(foo2) << "\\\\n"; std::cout << "offset of foo1::c: " << offsetof(foo1, c) << "\\\\n";
std::cout << "offset of foo1::v1: " << offsetof(foo1, v1) << "\\\\n";
std::cout << "offset of foo1::v2: " << offsetof(foo1, v2) << "\\\\n"; std::cout << "offset of foo2::v1: " << offsetof(foo2, v1) << "\\\\n";
std::cout << "offset of foo2::v2: " << offsetof(foo2, v2) << "\\\\n";
std::cout << "offset of foo2::c: " << offsetof(foo2, c) << "\\\\n"; return 0;
}
Golang❓
不支援 packed struct,請參考 cgo#struct-alignment-issues