较旧的K& R(第2版)和我读过的其他C语言文本,讨论了malloc()
和free()
风格的动态内存分配器的实现,通常也会提到,传递,关于数据类型对齐限制的东西。显然,某些计算机硬件体系结构(CPU,寄存器和内存访问)限制了如何存储和处理某些值类型。例如,可能要求必须从4的倍数地址开始存储4字节(long
)整数。
主要平台(英特尔和AMD,SPARC,Alpha)对内存分配和内存访问有什么限制(如果有的话),还是可以安全地忽略在特定地址边界上对齐内存分配?
答案 0 :(得分:6)
Sparc,MIPS,Alpha和大多数其他“经典RISC”架构只允许对内存进行对齐访问,即使在今天也是如此。未对齐的访问将导致异常,但某些操作系统将通过使用较小的加载和存储从软件中的所需地址进行复制来处理异常。应用程序代码不会知道存在问题,除了性能非常糟糕。
MIPS具有特殊指令(lwl和lwr),可用于从未对齐的地址访问32位数量。只要编译器能够判断出地址可能是未对齐的,它就会使用这两个指令序列而不是普通的lw指令。
x86可以毫无例外地处理硬件中的未对齐内存访问,但与对齐访问相比,性能仍然高达3倍。
Ulrich Drepper撰写了一篇关于这个和其他与记忆相关的主题的综合论文,What Every Programmer Should Know About Memory。这是一篇很长的文章,但充满了耐嚼的善良。
答案 1 :(得分:4)
今天对齐仍然非常重要。如果您尝试访问奇数边界上的字值,某些处理器(68k系列跳出来)会抛出异常。今天,大多数处理器将运行两个存储器周期来获取未对齐的字,但这肯定比对齐的提取慢。其他一些处理器甚至不会抛出异常,但会从内存中获取不正确的值!
如果除了性能之外没有其他原因,尝试遵循处理器的对齐首选项是明智的。通常,您的编译器会处理所有细节,但如果您正在做自己布局内存结构的任何事情,那么值得考虑。
答案 2 :(得分:1)
在C(++)中布局类或结构时,您仍需要注意对齐问题。在这些情况下,编译器会为您做正确的事情,但结构/类的整体大小可能比必要的更浪费
例如:
struct
{
char A;
int B;
char C;
int D;
};
大小为4 * 4 = 16字节(假设Windows在x86上)而
struct
{
char A;
char C;
int B;
int D;
};
大小为4 * 3 = 12个字节。
这是因为编译器对整数强制执行4字节对齐,但对于字符只执行1个字节。
通常将相同大小(类型)的包成员变量放在一起,以最大限度地减少浪费的空间。
答案 3 :(得分:1)
正如Greg所说,它今天仍然很重要(在某些方面可能更为重要),编译器通常会根据架构的目标来处理对齐。在托管环境中,JIT编译器可以根据运行时体系结构优化对齐。
您可能会看到更改对齐的pragma指令(在C / C ++中)。只有在需要非常特殊的对齐时才应该使用它。
// For example, this changes the pack to 2 byte alignment.
#pragma pack(2)
答案 4 :(得分:1)
请注意,即使在IA-32和AMD64上,某些SSE指令/内在函数也需要对齐数据。如果数据未对齐,这些指令将引发异常,因此至少您不必调试“错误数据”错误。还有相同的未对齐指令,但是像Denton说的那样,它们的速度较慢。
如果您使用的是VC ++,那么除了#pragma pack指令外,您还可以使用__declspec(align)指令进行精确对齐。 VC ++文档还提到了__aligned_malloc函数以满足特定的对齐要求。
根据经验,除非您在编译器/语言之间移动数据或使用SSE指令,否则您可能会忽略对齐问题。