編寫可移植代碼而值得考慮的最后一個問題是如何存取不對齊的數據 -- 例如, 如何讀取 一個存儲於一個不是 4 字節倍數的地址的 4 字節值. i386 用戶常常存取不對齊數據項, 但是不是所有的體系允許這個. 很多現代的體系產生一個異常, 每次程序試圖不對齊數據 傳送時; 數據傳輸由異常處理來處理, 帶來很大的性能犧牲. 如果你需要存取不對齊的數 據, 你應當使用下列宏:
#include <asm/unaligned.h> get_unaligned(ptr); put_unaligned(val, ptr);
這些宏是無類型的, 並且用在每個數據項, 不管它是 1 個, 2 個, 4 個, 或者 8 個字節 長. 它們在任何內核版本中定義.
關於對齊的另一個問題是跨平台的數據結構移植性. 同樣的數據結構( 在 C-語言 源文件 中定義 )可能在不同的平台上不同地編譯. 編譯器根據各個平台不同的慣例來安排結構成 員對齊.
為了編寫可以跨體系移動的數據使用的數據結構, 你應當一直強制自然的數據項對齊, 加 上對一個特定對齊方式的標准化. 自然對齊意味着存儲數據項在是它的大小的整數倍的地 址上(例如, 8-byte 項在 8 的整數倍的地址上). 為強制自然對齊在阻止編譯器以不希望 的方式安排成員量的時候, 你應當使用填充者成員來避免在數據結構中留下空洞.
為展示編譯器如何強制對齊, dataalign 程序在源碼的 misc-progs 目錄中發布, 並且一 個對等的 kdataalign 模塊是 misc-modules 的一部分. 這是程序在幾個平台上的輸出以 及模塊在 SPARC64 的輸出:
249
arch Align: |
char |
short |
int |
long |
ptr |
long-long |
u8 |
u16 |
u32 |
u64 |
i386 |
1 |
2 |
4 |
4 |
4 |
4 |
1 |
2 |
4 |
4 |
i686 |
1 |
2 |
4 |
4 |
4 |
4 |
1 |
2 |
4 |
4 |
alpha |
1 |
2 |
4 |
8 |
8 |
8 |
1 |
2 |
4 |
8 |
armv4l |
1 |
2 |
4 |
4 |
4 |
4 |
1 |
2 |
4 |
4 |
ia64 |
1 |
2 |
4 |
8 |
8 |
8 |
1 |
2 |
4 |
8 |
mips |
1 |
2 |
4 |
4 |
4 |
8 |
1 |
2 |
4 |
8 |
ppc |
1 |
2 |
4 |
4 |
4 |
8 |
1 |
2 |
4 |
8 |
sparc |
1 |
2 |
4 |
4 |
4 |
8 |
1 |
2 |
4 |
8 |
sparc64 |
1 |
2 |
4 |
4 |
4 |
8 |
1 |
2 |
4 |
8 |
x86_64 |
1 |
2 |
4 |
8 |
8 |
8 |
1 |
2 |
4 |
8 |
kernel: arch Align: char short int long ptr long-long u8 u16 u32 u64 kernel: sparc64 1 2 4 8 8 8 1 2 4 8
有趣的是注意不是所有的平台對齊 64-位值在 64-位邊界上, 因此你需要填充者成員來強 制對齊和保證可移植性.
最后, 要知道編譯器可能自己悄悄地插入填充到結構中來保證每個成員是對齊的, 為了目 標處理器的良好性能. 如果你定義一個結構打算來匹配一個設備期望的結構, 這個自動的 填充可能妨礙你的企圖. 解決這個問題的方法是告訴編譯器這個結構必須是"緊湊的", 不 能增加填充者. 例如, 內核頭文件 <linux/edd.h> 定義幾個與 x86 BIOS 接口的數據結 構, 並且它包含下列的定義:
struct
{
}
u16 id; u64 lun;
u16 reserved1;
u32 reserved2;
attribute ((packed)) scsi;
如果沒有 attribute ((packed)), lun 成員可能被在前面添加 2 個填充者字節或者
6 個, 如果我們在 64-位平台上編譯這個結構.