我用C ++创建了一个简单的类Storer,玩内存分配。它包含六个字段变量,所有这些变量都在构造函数中分配:
int x;
int y;
int z;
char c;
long l;
double d;
我对如何存储这些变量感兴趣,所以我编写了以下代码:
Storer *s=new Storer(5,4,3,'a',5280,1.5465);
cout<<(long)s<<endl<<endl;
cout<<(long)&(s->x)<<endl;
cout<<(long)&(s->y)<<endl;
cout<<(long)&(s->z)<<endl;
cout<<(long)&(s->c)<<endl;
cout<<(long)&(s->l)<<endl;
cout<<(long)&(s->d)<<endl;
我对输出非常感兴趣:
33386512
33386512
33386516
33386520
33386524
33386528
33386536
为什么char c占用四个字节? sizeof(char)当然返回1,那么为什么程序分配的内存超出了它的需要?这证实使用以下代码分配了太多内存:
cout<<sizeof(s->c)<<endl;
cout<<sizeof(Storer)<<endl;
cout<<sizeof(int)+sizeof(int)+sizeof(int)+sizeof(char)+sizeof(long)+sizeof(double)<<endl;
打印:
1
32
29
确认确实正在不必要地分配3个字节。任何人都可以向我解释为什么会这样吗?感谢。
答案 0 :(得分:24)
数据对齐和编译器填充说嗨!
CPU没有类型的概念,它在32位(或64位,或128位(SSE)或256位(AVX)中得到什么 - 让我们保持简单32)寄存器需求正确对齐,以便正确有效地处理。想象一个简单的场景,你有一个char,然后是一个int。在32位架构中,char为1字节,整数为4字节。
32位寄存器必须在其边界上断开,只占用整数的3个字节,并将第4个字节留作“第二次运行”。它无法以这种方式正确处理数据,因此编译器将添加填充以确保所有内容都得到有效处理。这意味着根据相关类型添加一定量的填充。
计算机不是人类,它不能用一双眼睛和大脑来挑选它们。它必须是非常确定的,并且对于如何做事情持谨慎态度。首先,它加载一个包含给定信息的n个字节的块,将其移动以便修剪掉不相关的信息,然后另一个块再次移出一堆不必要的字节,这些字节与手头的操作没有任何关系。只有这样才能做必要的操作。通常你有两个操作数,这只是一个完整的操作数。当你完成所有这些工作时,才能真正处理它。当您可以简单地对齐数据时,会产生太多的性能开销(大多数情况下,如果您没有做任何花哨的事情,编译器会为您执行此操作)。
视觉上 - 第一个绿色字节是提到的字符,三个绿色字节加上第二个块的第一个红色字节是4字节的int,在4字节访问边界上进行颜色编码(我们正在谈论一个32位寄存器)。底部的“相反部分”显示了一个理想的设置,其中int正确地击中了寄存器(字符在图像的某处被填充为服从):
阅读更多有关数据对齐的内容,当您处理SSE(128位regs)或AVX(256位regs)等指令集的精彩扩展时非常方便,因此必须特别小心,以便矢量化的优化不会失败(对齐SSE的16字节边界,16 * 8 - > 128位)。
phonetagger 在评论中提出了一个有效点,即pragma指令可以通过预处理器分配给强制编译器,以便以用户程序员指定的方式对齐数据。但是像#pragma pack(...)
这样的指令是编译器的一个声明,你知道你正在做什么以及什么是最适合你的。确保你这样做,因为如果你无法适应你的环境,你可能会受到各种处罚 - 最显而易见的是使用你自己没有编写的外部库,这些库在打包数据的方式上有所不同。
当他们发生冲突时,事情就会爆炸。在这种情况下,最好是建议谨慎,并且真正与手头的问题保持密切联系。如果您不确定,请将其保留为默认值。如果你不确定但是必须使用SSE之类的东西,其中对齐为王(而不是默认也不是简单的),请在线查询各种资源或在此处提出另一个问题。
答案 1 :(得分:5)
我会做一个类比来帮助你理解。
假设有一条长条面包并且你有一台切割机可以切成相同厚度的切片。然后你给这些面包,让我们说儿童。每个孩子都会拿起他们的面包并公平地做他们想做的事情(把Nutella放在他们身上然后吃饭等)。他们甚至可以用它做出更薄的切片并像这样使用它。
如果有一个孩子来找你,并说他不想让每个人都吃到这片,而是更薄片,那么你就会遇到困难,因为你的切割机经过优化可以减少至少一个最小量,让每个人都开心但是,当一个孩子要求更薄的切片时,你必须重新发明机器或增加其它复杂性,就像引入两种切割模式一样。你不希望这样。最终你放弃了,无论如何只是给了他一大片。
这就是它发生的原因。希望你能与这个类比联系起来。
答案 2 :(得分:3)
数据对齐是char分配了4个字节的原因:Data alignement
答案 3 :(得分:2)
char
不占用四个字节:它像往常一样占用一个字节。您可以通过打印sizeof(char)
进行检查。其他三个字节是 padding ,编译器会插入这些字节以优化对类的其他成员的访问。取决于硬件,当它们位于可被4整除的地址时,访问多字节类型(例如,4字节整数)通常要快得多。编译器可以在int成员之前插入最多三个字节的填充,以使其与良好的内存地址对齐,以便更快地访问。
如果您想体验类布局,可以使用名为offsetof
的便捷操作。它需要两个参数 - 成员的名称和类的名称,它返回从结构的基址到内存中成员的位置的字节数。
cout << offsetof(Storer, x) << endl;
cout << offsetof(Storer, y) << endl;
cout << offsetof(Storer, z) << endl;
答案 4 :(得分:1)
结构成员以特定方式对齐。通常,如果您想要最紧凑的表示,请按降序排列大小列出成员。
http://en.wikipedia.org/wiki/Data_structure_alignment#Typical_alignment_of_C_structs_on_x86