C++内存对齐

0X00变量偏移位置

内存对齐简单的例子

#include <iostream>

using namespace std;

class A{
    char a;//1字节
    int b;//4字节
};

int main() {
    A a;
    cout << "a sizeof = " << sizeof(a);
    return 1;
}

在classA中总共花费5个字节。但是输出的结果是

对象a的大小
为什么大小会是8?这个就需要归功于c++的内存对齐了,我们看下类A中内存布局
类A的内存布局
在a变量后面插入了3个字节,然后才是b变量。
这个原因是在字节对齐的作用下,其中规律是:类每个成员相对类首地址的偏移量(offset)都是有效对齐值的整数倍,如有需要编译器会在成员之间加上填充字节(alignment)

我们看下什么叫做 有效对齐位 ,一般情况(先说一般情况)下这个值就是基本数据类型所占内存的大小,一般就是char为1、short为2、int为4、long long为8,也就是说在类中,char变量的有效对齐位是1,那么它的相对偏移只要是1的倍数也就是任意值,short的相对偏移必须是2的倍数,int相对偏移必须是4的倍数。
上述情况a的偏移量为0是1的倍数,b的有效对齐位大小为4,所以起始位置需要是4才能满足。

在程序中有效对齐位 = min(默认对齐位,字节宽度)
默认对齐位在程序通过#prapam pack(n)设置。
我们在上述的代码中插入默认对齐位的定义2

#include <iostream>

using namespace std;

#pragma pack(2)//默认对齐位

class A{
    char a;//1字节
    int b;//有效对齐位 2 = min(2, 4)
};

int main() {
    A a;
    cout << "a sizeof = " << sizeof(a);
    return 1;
}

输出的内容为
a变量的大小
这个时候类A中的内存布局为
类A的内存布局
因为b的内存对齐位为2,所以它的偏移只需要为2的倍数2,在a变量后面插入了1个字节。

0X01类大小的字节填充

在类中大小也需要内存对齐。
我们在上述的例子中A加入一个元素

#include <iostream>

using namespace std;

class A{
    char a;
    int b;
    char c;
};

int main() {
    A a;
    cout << "a sizeof = " << sizeof(a);
    return 1;
}

如果只按照上面的只需要满足起始地址的条件,那么输出大小应该是4(a)+4(b)+1© = 9
我们编译后输出结果是12
输出结果
内存布局为
内存布局
编译器还在类的末尾填充了3个字节。
一个类的内存对齐值等于这个类中所有成员有效对齐值的最大值,这个类的所占内存大小必须是类内存对齐值的倍数
在这个类中最大额内存对齐值是int b为4,当4(a) + 4(b)+c(1)= 9最少还差3个字节为12,所以需要在末尾填充3个字节大小就可以满足这个条件。

0X02虚函数表指针,虚继承指针内存对齐

在c++类中不单单有成员的变量,如果类含有虚函数,那么在类中会有一个虚函数表指针。在使用虚继承的时候每个虚继承类都会有一个虚继承指针类(如果不清楚的这些的可以了解c++的内存布局)

当然虚函数表指针(vfptr)和虚基类表指针(vbtpr)也遵循这个规律。
我们看下以下代码的内存布局

#include <iostream>

using namespace std;

class A{
    virtual void fun() {

    };
    long long lo;
};

class B : virtual public A {

};

int main() {
    B b;
    cout << "b sizeof = " << sizeof(b);
    return 1;
}

编译后内存布局是
在这里插入图片描述
Base A 中vfptr指针本来只占4个字节(在32位机器下),因为lo有效内存对齐值为8个内存,起始地址需要是8的倍数,所以vfptr后面填充了4个字节补充。在vbptr后面也是相同,因为birtual base A内存对齐值为8所以所以vbptr指针,起始地址为8的倍数。

0X03变量在栈中的内存对齐

内存对齐不仅仅是体现在类成员的偏移和大小中,同样也出现在栈,堆中,在栈和堆中都会符合起始地址和内存对齐值的倍数。
在堆中连续申请两块内存不一定是连续的地址,我们采用下面的代码验证是否符合。

#include <iostream>

using namespace std;

//类A的内存对齐值是8
class A{
    short a;
    long long lo;
};

int main() {
    
    int zCount = 0; //是8的倍数
    int nZCount = 0; //不是8的倍数

    for (int i = 0; i < 1000000; i++) {
        int p = (int)(new A);
        if ((p % 8) == 0) {//检验是否是8的倍数
            zCount++;
        } else {
            nZCount++;
        }
    }

    cout << "zCount = " << zCount << endl;
    cout << "nZcount = " << nZCount << endl;

    return 1;
}

在堆上申请1百万个元素,计算地址是否能被8整除。最后输出结果为
堆上内存对齐
可以看出内存对齐在堆上也是相同的

在栈中也是符合的,因为栈中内存是连续的,我们可以简单的把输出地址查看是否有内存填充的情况。

#include <iostream>

using namespace std;

//类A的内存对齐值是8
class A{
    short a;
    long long lo;
};

int main() {
    
    char c;
    char c1;
    char c2;

    A a;

    cout << "c address = " << (int)&c << endl;
    cout << "c1 address = " << (int)&c1 << endl;
    cout << "c2 address = " << (int)&c2 << endl;
    cout << "a address = "<< (int)&a << endl;

    return 1;
}

我们可以看这段代码输出结果是
栈中内存对齐
前面char内存都是连续递减(栈内存增长是由高到底),到A的时候,相差17个字节。我们用下面图表示。其实c2和a只相差1个字节,c2和a的起始地址相差17个字节。就是因为这1个字节的填充,a的起始地址才能是内存对齐值8整除。
栈中内存示意图

0X04附录

我们可以在不同的编译器中强制设置内存对齐。
参考文献
gcc中
https://blog.csdn.net/huanghui167/article/details/6921550
vc中
https://msdn.microsoft.com/library/xh3e3fd0(v=vs.110).aspx

内存对齐相关博客:
https://www.cnblogs.com/clover-toeic/p/3853132.html

转载请声明:https://blog.csdn.net/lqq_419/article/details/83347106
欢迎大家提出建议

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值