# C/C++ Memory Alignment

# 起因

x86/ARM 上，變數在記憶體上並不是起始於隨意的位置。而會是該類類型大小的整數倍，這樣讀取資料（變數）可以比較快。

# 為什麼不是隨意 bytes 位置

CPU 通常是讀 **Word** (4/8 bytes)，假如是隨意的 bytes 位置，那就有可能為了讀一個`uint16_t` (2 bytes) 去讀兩個 words，會比較慢。

所以有時會在「連續」的變數之間出現 gap。

```c
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

首先來看一個範例：

```c
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。

```c
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：

```c
struct foo {  
    uint16_t a;  // 2 bytes  
    char c;      // 1 byte  
    char pad[1]; // 1 byte  
}
```

# 重排成員

適當重排 Struct 成員可以減少 Struct 的 padding。

本來 Padding 用掉： 7 + 6 = 13

```c
struct bar {  
    char c;      // 1 byte  
    uint64_t v1; // 8 bytes  
    uint16_t v2; // 2 bytes  
};
```

調換成員的位置後Padding： 5

```c
struct bar {  
    uint64_t v1; // 8 bytes  
    uint16_t v2; // 2 bytes  
    char c;      // 1 byte  
};
```

對 Struct 重排成員是也有著壞處，程式也是要給人看的，這可能會影響到可讀性。

效能也可能有影響， 本來擺在一起的成員是紀錄同一個功能的， 快取本來可以處理到這塊，但分開後快取效果就消失了。

# Packed Struct

不過 c/c++ 並不是沒有把 padding 排除的選項，下編譯參數或者使用巨集都可以讓這 Padding 消失。

```c
struct __attribute__((__packed__)) foo3 {  
    char c;  
    uint64_t v1;  
    uint16_t v2;  
};  
// sizeof(foo3) == 9
```

# 測試

*   `offsetof(TYPE, MEMBER)` 可以取得 type 裡面 member 的位置。
*   `sizeof`

# C/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](https://github.com/golang/go/wiki/cgo#struct-alignment-issues)

# 參考

*   [The Lost Art of Structure Packing](http://www.catb.org/esr/structure-packing/)
*   [https://stackoverflow.com/questions/34238217/what-is-the-importance-of-trailing-padding-bytes-in-that-case](https://stackoverflow.com/questions/34238217/what-is-the-importance-of-trailing-padding-bytes-in-that-case)
