我找不到一个体面的文档来解释对齐系统如何工作以及为什么某些类型比其他类型更严格一致。
答案 0 :(得分:22)
我会试着解释一下。
计算机中的体系结构由处理器和内存组成。 内存按单元格组织,因此:
0x00 | data |
0x01 | ... |
0x02 | ... |
每个存储器单元具有指定的大小,它可以存储的位数。这取决于架构。
在C / C ++程序中定义变量时,程序会占用一个或更多个不同的单元格。
例如
int variable = 12;
假设每个单元格包含32位且int
类型大小为32位,则在内存中的某处:
variable: | 0 0 0 c | // c is hexadecimal of 12.
当您的CPU必须对该变量进行操作时,需要将其置于其寄存器中。 CPU可以从内存中获取少量位“ 1 clock ”,该大小通常称为 WORD 。这个维度也取决于架构。
现在假设你有一个变量,由于一些偏移而存储在两个单元格中。
例如,我有两个不同的数据要存储(我将使用“字符串表示来更清晰”):
data1: "ab"
data2: "cdef"
所以内存将以这种方式组成(2个不同的单元格):
|a b c d| |e f 0 0|
也就是说,data1
只占用单元格的一半,因此data2
占据剩余部分,占据第二个单元格的一部分。
现在假设您想要读取data2
。 CPU需要 2个时钟来访问数据,因为在一个时钟内它读取第一个单元,而在另一个时钟内它读取第二个单元中的剩余部分。
如果我们按照这个记忆示例对齐 data2
,我们可以
在第二个单元格中引入一种填充并移位data2
。
|a b 0 0| |c d e f|
---
padding
这样,CPU只会丢失“ 1个时钟”才能访问data2
。
对齐系统只是引入 padding 以便将数据与系统内存对齐,请记住根据体系结构。当数据在存储器中对齐时,您不会浪费CPU周期来访问数据。
这是出于性能原因(99%的次数)。
答案 1 :(得分:15)
这是"实现定义",即对齐要求不是语言规范的一部分。
不同的CPU对对齐有不同的要求。有些人无法解决不平坦地址上的16位值问题。有些人无法解决浮点值,除非与可被大小整除的地址对齐,有些可以。等等。有些人会比未正确对齐的数据对象更慢地访问未对齐的数据对象,有些则会因未对齐的访问而跳过。
这就是为什么语言标准没有详细说明哪种类型需要以哪种方式对齐(因为它不能),而是将其留给"实现" - 在这种情况下编译器后端。
如果你对指针进行类型转换,则可能会强制代码在无法寻址的地址处寻址给定对象。您需要确保" old"的对齐要求。类型至少与" new"的类型一样严格。类型。
在C ++中(C ++ 11以上版本),您可以使用alignof运算符来告诉您给定类型的对齐要求。您还可以使用alignas运算符对给定类型或对象强制执行更严格的对齐。
在C (C11向上)中,您获得了_Alignof和_Alignas个运算符,其中<stdalign.h>
包含在alignof
/ {{{ 1}} convenience macros。 (谢谢,Lundin - C11不是我的强项。)
答案 2 :(得分:7)
某些系统可以部分访问内存,例如32位字(4字节)。这是硬件限制。这意味着进入内存控制器的实际地址应该可以被四整除(因为它仍在寻址字节)。所以一旦你尝试在一个不能被4整除的地址上找到一个单词,就会有两个选项 - 编译器会尝试生成一些奇特的代码来组成两个内存访问中的单词,但情况并非总是如此。有时它只会生成一个代码来访问给定地址的4个字节。然后处理器将因数据对齐错误而失败。
这会导致语言的限制。
考虑代码(不好的代码):
uint8_t a[] = {1,2,3,4,5,6};
uint32_t b = *(uint32_t*)&a[1];
并假设a
与四边界可整除。
然后第二行试图从它的第二元素的地址中读出一个字,即地址不可被4整除。这将导致对齐错误。但在C
中,严格别名规则禁止使用它。