【发布时间】:2018-07-28 14:27:55
【问题描述】:
我一直在阅读有关数据对齐和结构填充的内容。我从here 开始,为了表达我的理解,我可以说T 类型的元素或数据e 是自然对齐的,如果它的地址a是它的数据大小的倍数[sizeof T],
让我们考虑
struct test{
short a;
char b;
};
constexpr auto size = sizeof test 是 4,因为 struct test 的最大对齐方式(short)
然后我去了here,第一个答案很好地解释了数据对齐,作者使用了这个例子
data1: "ab"
data2: "cdef"
|a b c d| |e f 0 0|
鉴于内存访问粒度为 4 字节,我将在这里假设 data1 是 2 字节,data2 是 4 byte,因为 data2 开始于一个奇数地址或者它的地址不是4 byte的倍数,这被认为是未对齐的数据,因此访问data2会根据架构产生不同的效果。
这是通过添加 junkies(或纯 0)使 data2 自然对齐的填充,最后我们有
|a b 0 0| |c d e f|
到目前为止很好,现在让我们将两个数据包装到一个结构中
struct wrap{
data1: "ab"
data2: "cdef"
};
现在考虑到最大对齐,wrap 大小将变为 8,并带有两个字节的填充。
我在这里,在struct wrap 的情况下,无论是填充还是不填充,处理器实际上都需要两个周期(假设 WORD 大小为 4 字节)来获取 wrap 对象,对吗?
那么为什么我们需要填充呢?
没有填充:
|a b c d| |e f 0 0|
假设处理器获取了 8 个字节 wrap object,data1 是两个字节,因此它可以容纳“ab”,剩余的将被截断(就像填充位将被截断一样)
而data2 可以从它的地址读取连续的 4 个字节(读取“cdef”),其余的被截断了,那么为什么我们需要在这里填充或者我弄错了?
第二个问题和上面差不多
struct test2{
short a; //2 byte
char _3,_4,_5,_6,_7,_8,_9,_10,_11 ;
};
而sizeof test2 将给出 12,假设现在内存访问粒度(更改 WORD = 8)是 8 字节,那么 test2 的对象将占用两个 WORD,无论是否有填充,它都会占用两个 WORD,因此无论test2 的对齐方式如何,都需要两个周期来获取其对象,如果是这种情况,为什么填充在这里很重要?为什么是 12,而不是 11,虽然两者都需要在 8 字节边界内有两个周期?
最后一个问题,
struct final{
char a;
int b;
};
假设 struct final 已打包(无对齐),而 var test::b 将被放置在奇数地址
内存(只是一种理论表示)
0 1 2 3 4 5 6 7
tets::a:1 test::b:1 test::b:2 test::b:3 test::b:4
test::b:x这里不是位字段的意思
我了解到,只要我通过 ` 使用对象访问 test::b。或 ->' 会很好,但是当我获取它的地址时
int * p = &(test::b) ; (void)*p; 此语句将影响性能或崩溃。
同样,如果 WORD = 8 字节,那么 struct final 的对象将在一个周期内进入内存,假设指针 p 持有地址 1,这很奇怪,但不能只是尊重的*p 将从地址1 开始直到指向元素类型(4 字节)的位复制到内存中?为什么这里有问题?我错过了什么?
【问题讨论】: