奇怪的数据对齐
Strange data alignment
据我所知,数据对齐是将数据放入内存中的 64 位/32 位块中以获得 CPU 性能,我使用的是 64 位 linux 机器,我做了一些测试和得到了一些奇怪的结果(我无法解释这种行为)。
以下是我使用的结构:
class A {
long l0,l1,l2;
};
class B {
long l0,l1,l2,l3;
};
class C {
long l0,l1,l2,l3,l4;
};
测试:
int main() {
C* newC = new C();
B* newB = new B();
A* newA = new A();
int* i = new int();
std::cout << sizeof(A) << std::endl;
std::cout << sizeof(B) << std::endl;
std::cout << sizeof(C) << std::endl;
std::cout << "C : " << newC << std::endl;
std::cout << "B : " << newB << std::endl;
std::cout << "A : " << newA << std::endl;
std::cout << "i : " << i << std::endl;
delete (i);
delete (newC);
delete (newA);
delete (newB);
return 0;
}
只是将每个对象的1个对象放入堆中,我在末尾添加了一个指针以查看newA
占用的内存
结果是这样的:
24
32
40
C : 0x603010
B : 0x603040
A : 0x603070
i : 0x603090
3*16 bytes
介于 newC
和 newB
的地址之间:C 是 40 字节,已经是 64 位的倍数,为什么还要多 8 个字节??
3*16 bytes
在 newB
和 newA
之间?? B 只有 32 个字节,我预计:A : 0x603060
2*16 bytes
在newA
和i
的地址之间??
当你在堆上分配时,你不能对地址做出任何明确的声明。
内存分配函数很有可能维护有关已分配块的内联信息,这将影响后续块的地址,例如:
+--------+-------------+--------+-------------+
| header | alloced mem | header | alloced mem | ...
+--------+-------------+--------+-------------+
此外,为了提高效率,这些函数可能会将您的内存四舍五入为(例如)八或十六的倍数(您仍然不允许使用它,因为您不 知道)。这可能会进一步影响您看到的分配内存地址。
双方的经典案例可以看到:
#include <iostream>
#include <cstdlib>
int main (int argc, char *argv[]) {
char *one = new char[std::atoi(argv[1])];
char *two = new char[std::atoi(argv[1])];
std::cout << static_cast<void*>(one) << '\n';
std::cout << static_cast<void*>(two) << '\n';
return 0;
}
和脚本:
#!/usr/bin/bash
for i in {01..37}; do
echo $i $(./qq $i)
done
在我的系统上,输出:
01 0x800102e8 0x800102f8
02 0x800102e8 0x800102f8
:: (all the same address pairs in here and in gaps below)
12 0x800102e8 0x800102f8
13 0x800102e8 0x80010300
::
20 0x800102e8 0x80010300
21 0x800102e8 0x80010308
::
28 0x800102e8 0x80010308
29 0x800102e8 0x80010310
::
36 0x800102e8 0x80010310
37 0x800102e8 0x80010318
当您只分配一个字符时,两者之间的字节数高达 16。
它 一直保持 十六个字节,一直到 new char[12]
并且每次添加八个字符后增加八个 似乎 表示四字节 header,header+ 数据最少十六字节,header+ 数据区域的八字节分辨率。
请记住,这是基于我对这些东西的书写方式的了解,而 受过教育 的猜测,仍然 一个猜测,你不应该依赖它。它可能会使用与我想象的完全不同的策略,或者它可能会针对更大的块改变策略。
如果您想知道 space 类型 真正 占用多少,请制作一个包含两个的数组并计算 x[0]
之间的内存地址差异] 和 x[1]
。你会发现它应该和你从 sizeof
.
得到的一样
您不能假定对 new 的后续调用会 return 内存中的连续块。
如果您想尝试测试用例,我建议您执行以下操作:
struct D{
C c;
B b;
A a;
}
现在您可以开始打印地址了。但是,只有花车,我预计不会出现对齐问题。
据我所知,数据对齐是将数据放入内存中的 64 位/32 位块中以获得 CPU 性能,我使用的是 64 位 linux 机器,我做了一些测试和得到了一些奇怪的结果(我无法解释这种行为)。
以下是我使用的结构:
class A {
long l0,l1,l2;
};
class B {
long l0,l1,l2,l3;
};
class C {
long l0,l1,l2,l3,l4;
};
测试:
int main() {
C* newC = new C();
B* newB = new B();
A* newA = new A();
int* i = new int();
std::cout << sizeof(A) << std::endl;
std::cout << sizeof(B) << std::endl;
std::cout << sizeof(C) << std::endl;
std::cout << "C : " << newC << std::endl;
std::cout << "B : " << newB << std::endl;
std::cout << "A : " << newA << std::endl;
std::cout << "i : " << i << std::endl;
delete (i);
delete (newC);
delete (newA);
delete (newB);
return 0;
}
只是将每个对象的1个对象放入堆中,我在末尾添加了一个指针以查看newA
结果是这样的:
24
32
40
C : 0x603010
B : 0x603040
A : 0x603070
i : 0x603090
3*16 bytes
介于 newC
和 newB
的地址之间:C 是 40 字节,已经是 64 位的倍数,为什么还要多 8 个字节??
3*16 bytes
在 newB
和 newA
之间?? B 只有 32 个字节,我预计:A : 0x603060
2*16 bytes
在newA
和i
的地址之间??
当你在堆上分配时,你不能对地址做出任何明确的声明。
内存分配函数很有可能维护有关已分配块的内联信息,这将影响后续块的地址,例如:
+--------+-------------+--------+-------------+
| header | alloced mem | header | alloced mem | ...
+--------+-------------+--------+-------------+
此外,为了提高效率,这些函数可能会将您的内存四舍五入为(例如)八或十六的倍数(您仍然不允许使用它,因为您不 知道)。这可能会进一步影响您看到的分配内存地址。
双方的经典案例可以看到:
#include <iostream>
#include <cstdlib>
int main (int argc, char *argv[]) {
char *one = new char[std::atoi(argv[1])];
char *two = new char[std::atoi(argv[1])];
std::cout << static_cast<void*>(one) << '\n';
std::cout << static_cast<void*>(two) << '\n';
return 0;
}
和脚本:
#!/usr/bin/bash
for i in {01..37}; do
echo $i $(./qq $i)
done
在我的系统上,输出:
01 0x800102e8 0x800102f8
02 0x800102e8 0x800102f8
:: (all the same address pairs in here and in gaps below)
12 0x800102e8 0x800102f8
13 0x800102e8 0x80010300
::
20 0x800102e8 0x80010300
21 0x800102e8 0x80010308
::
28 0x800102e8 0x80010308
29 0x800102e8 0x80010310
::
36 0x800102e8 0x80010310
37 0x800102e8 0x80010318
当您只分配一个字符时,两者之间的字节数高达 16。
它 一直保持 十六个字节,一直到 new char[12]
并且每次添加八个字符后增加八个 似乎 表示四字节 header,header+ 数据最少十六字节,header+ 数据区域的八字节分辨率。
请记住,这是基于我对这些东西的书写方式的了解,而 受过教育 的猜测,仍然 一个猜测,你不应该依赖它。它可能会使用与我想象的完全不同的策略,或者它可能会针对更大的块改变策略。
如果您想知道 space 类型 真正 占用多少,请制作一个包含两个的数组并计算 x[0]
之间的内存地址差异] 和 x[1]
。你会发现它应该和你从 sizeof
.
您不能假定对 new 的后续调用会 return 内存中的连续块。
如果您想尝试测试用例,我建议您执行以下操作:
struct D{
C c;
B b;
A a;
}
现在您可以开始打印地址了。但是,只有花车,我预计不会出现对齐问题。