编写可移植代码而值得考虑的最后一个问题是如何存取不对齐的数据 -- 例如, 如何读取 一个存储于一个不是 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-位平台上编译这个结构.