c++之内存对齐(1)-结构体大小及内存对齐

本文章参考https://www.cnblogs.com/xylc/p/3780907.html

一,什么是内存对齐?内存对齐用来做什么?
所谓内存对齐,是为了让内存存取更有效率而采用的一种编译阶段优化内存存取的手段。
比如对于int x;(这里假设sizeof(int)==4),因为cpu对内存的读取操作是对齐的,如果x的地址不是4的倍数,那么读取这个x,需要读取两次共8个字节,然后还要将其拼接成一个int,这比存取对齐过的x要麻烦很多。

二,怎么算内存对齐大小(理论)?
对于简单类型,如int,char,float等,其对齐大小为其本身大小,即align(int) == sizeof(int),align(char)==sizeof(char),等等。
对于复合类型,如struct,class,其本身并无所谓对齐,因为CPU没有直接存取一个struct的指令。对于struct而言,它的对齐指的是它里面的所有成员变量都是对齐的,class同理。

下面就讲讲struct对齐是怎么回事。

首先要明白三个点:
1,内存对齐是指首地址对齐,而不是说每个变量大小对齐;
2,结构体内存对齐要求结构体内每一个成员变量都是内存对齐的;
3,结构体对齐除了第2点之外还要求结构体数组也必须是对齐的,也就是说每个相邻的结构体内部都是对齐的。

程序员可自己指定某些数据的对齐大小,通过使用下面的预处理指令,指定对齐大小为x。(这里需要注意:只能指定2的n次方作为对齐大小,对于指定对齐大小为6,9,10这样的编译器可能会不予理会)
#pragma pack(x)
//…
#pragma pack()

编译器中提供了#pragma pack(n)来设定变量以n字节对齐方式。
n字节对齐就是说变量存放的起始地址的偏移量有两种情况:
第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,
第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。
结构的总大小也有个约束条件,分下面两种情况:
如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为n的倍数。

三,怎么算内存对齐大小(示范)?

#include <cassert>


int main(int argc, char* argv[])
{
    //此处指定对齐大小为1
    //对于a,实际对齐大小为min(sizeof(int),1)=min(4,1)=1
    //对于b,实际对齐大小为min(sizeof(char),1)=min(1,1)=1
    //编译器会确保TEST_A首地址即a的地首址是1字节对齐的,此时a对齐
    //对于b,由于b要求首地址1字节对齐,这显然对于任何地址都合适,所以a,b都是对齐的
    //对于TEST_A数组,第一个TEST_A是对齐的(假设其地址为0),则第二个TEST_A的首地址为(0+5=5),对于第二个TEST_A的两个变量a,b均对齐
    //OK,对齐合理。因此整个结构体的大小为5
#pragma pack(1)
    struct TEST_A
    {
        int a;
        char b;
    };
#pragma  pack()
    assert(sizeof(TEST_A) == 5);

    //此处指定对齐大小为2
    //对于a,实际对齐大小为min(sizeof(int),2)=min(4,2)=2
    //对于b,实际对齐大小为min(sizeof(char),2)=min(1,2)=1
    //编译器会确保TEST_A首地址即a的地首址是2字节对齐的,此时a对齐
    //对于b,由于b要求首地址1字节对齐,这显然对于任何地址都合适,所以a,b都是对齐的
    //对于TEST_B数组,第一个TEST_B是对齐的(假设其地址为0),则第二个TEST_B的首地址为(0+5=5),对于第二个TEST_B的变量a,显然地址5是不对齐于2字节的
    //因此,需要在TEST_B的变量b后面填充1字节,此时连续相连的TEST_B数组才会对齐
    //OK,对齐合理。因此整个结构体的大小为5+1=6
#pragma pack(2)
    struct TEST_B
    {
        int a;
        char b;
    };
#pragma  pack()
    assert(sizeof(TEST_B) == 6);

    //此处指定对齐大小为4
    //对于a,实际对齐大小为min(sizeof(int),2)=min(4,4)=4
    //对于b,实际对齐大小为min(sizeof(char),2)=min(1,4)=1
    //编译器会确保TEST_A首地址即a的地首址是4字节对齐的,此时a对齐
    //对于b,由于b要求首地址1字节对齐,这显然对于任何地址都合适,所以a,b都是对齐的
    //对于TEST_C数组,第一个TEST_C是对齐的(假设其地址为0),则第二个TEST_C的首地址为(0+5=5),对于第二个TEST_C的变量a,显然地址5是不对齐于4字节的
    //因此,需要在TEST_C的变量b后面填充3字节,此时连续相连的TEST_C数组才会对齐
    //OK,对齐合理。因此整个结构体的大小为5+3=8
#pragma pack(4)
    struct TEST_C
    {
        int a;
        char b;
    };
#pragma  pack()
    assert(sizeof(TEST_C) == 8);

    //此处指定对齐大小为8
    //对于a,实际对齐大小为min(sizeof(int),8)=min(4,8)=4
    //对于b,实际对齐大小为min(sizeof(char),8)=min(1,8)=1
    //编译器会确保TEST_A首地址即a的地首址是4字节对齐的,此时a对齐
    //对于b,由于b要求首地址1字节对齐,这显然对于任何地址都合适,所以a,b都是对齐的
    //对于TEST_D数组,第一个TEST_D是对齐的(假设其地址为0),则第二个TEST_D的首地址为(0+5=5),对于第二个TEST_D的变量a,显然地址5是不对齐于4字节的
    //因此,需要在TEST_D的变量b后面填充3字节,此时连续相连的TEST_D数组才会对齐
    //OK,对齐合理。因此整个结构体的大小为5+3=8
#pragma pack(8)
    struct TEST_D
    {
        int a;
        char b;
    };
#pragma  pack()
    assert(sizeof(TEST_D) == 8);


    //此处指定对齐大小为8
    //对于a,实际对齐大小为min(sizeof(int),8)=min(4,8)=4
    //对于b,实际对齐大小为min(sizeof(char),8)=min(1,8)=1
    //对于c,这是一个数组,数组的对齐大小与其单元一致,因而align(c)=align(double)=min(sizeof(double),8)=min(8,8)=8
    //对于d,实际对齐大小为min(sizeof(char),8)=min(1,8)=1
    //编译器会确保TEST_A首地址即a的地首址是4字节对齐的,此时a对齐
    //对于b,由于b要求首地址1字节对齐,这显然对于任何地址都合适,所以a,b都是对齐的
    //对于c,由于c要求首地址8字节对齐,因此前面的a+b=5,还要在c后面补上3个字节才能对齐
    //对于d,显而易见,任何地址均对齐,此时结构体大小为4+1+3+10*8+1=89
    //对于TEST_E数组,第一个TEST_E是对齐的(假设其地址为0),则第二个TEST_E的首地址为(0+89=89),对于第二个TEST_E的变量a,显然地址89是不对齐于4字节的
    //因此,需要在TEST_E的变量d后面填充7字节(如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为n的倍数。),此时连续相连的TEST_E数组才会对齐 
    //(注意:此处不仅要确保下一个TEST_E的a,b变量对齐,还要确保c也对齐,所以这里不是填充3字节,而是填充7字节)
    //OK,对齐合理。因此整个结构体的大小为(4)+(1+3)+(10*8)+(1+7)=96
#pragma pack(8)
    struct TEST_E
    {
        int a;
        char b;
        double c[10];
        char d;
    };
#pragma  pack()
    assert(sizeof(TEST_E) == 96);

    return 0;
}

四、简单示例

#include<iostream>
using namespace std;
struct mybitfields
{
	unsigned short a;
	unsigned short b;
	unsigned short c;
	mybitfields()
	{
		a = 3;
		b = 4;
		c = 6;
	}
}test;

int main()
{
	char i;
	i = *((char *)&test);//
	printf("i=%d\n", *((char *)&test));//类型转换与解引用
	printf("i=%d\n", i);
	cout << sizeof(test) << endl;
	system("pause");
	return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值