C++内存字节对齐与位域【你可能不知道的C++】

内存字节对齐

什么是内存对齐呢,先来看一个对比

#include
using namespace std;

#pragma pack(show)//16

struct PackA {
 char a;
 int b;
 short c;
};

struct PackB
{
 int b;
 char a;
 short c;
};

int main() {
 cout << sizeof(PackA) << endl;//12
 cout << sizeof(PackB) << endl;//8
 return 0;
}

我们可能会说What fuck? Why?,为了CPU高效的读取内存,系统可不是一个字节一个字节读取的,如果每次读取四个字节

0x0 char c1
0x1 char c2
0x2 char c3     //读取内存可不是从0x3然后读取4个字节,而是先读取0x0四个字节
0x3 int a       //再读0x3四个字节,然后拼凑出想要的4个字节
0x4 a           //这样想一下,效率比较低,有没有好一点的办法呢
0x5 a
0x6 a

可以将,a后移动

0x0 char c1
0x1 char c2
0x2 char c3
0x3 
0x4 int a   //可见占用的总空间是不一样的
0x5 a
0x6 a
0x7 a

首先有一个基本的原则,变量的地址只能是其类型大小的整数倍,这是直接为变量时的简单情况

#include
using namespace std;

int main() {
 int a, b, c;
 double e;
 short f;
 cout << (long long) & a << endl;//184479120452 % 4 = 0
 cout << (long long) & b << endl;//184479120484 % 4 = 0
 cout << (long long) & c << endl;//184479120516 % 4 = 0
 cout << (long long)&e << endl;//184479120552 % 8 = 0
 cout << (long long)&f << endl;//184479120580 % 2 = 0
 return 0;
}

下面重新回到结构体的例子中

#include
using namespace std;

#pragma pack(show)//16

struct PackA {
 char a; //0x00
   //0x01
   //0x02
   //0x03
 int b; //0x04
   //0x05
   //0x06
   //0x07
 short c;//0x08
   //0x09         //这不是10个吗,为什么是12呢,因为结构体本身也有字节对齐大小
   //0x0A     //其对齐大小为,内部最大的对齐大小,此案例则为int 4byte
   //0x0B     //所以其对齐大小应该为4byte的整数倍
             //最小满足的大小为12byte
};

struct PackB
{
 int b;//0x00
    //0x01
       //0x02
       //0x03
 char a;//0x04
     //0x05
 short c;//0x06
   //0x07        //同理结构体也要字节对齐大小为int 大小的整数倍,正好满足要求
};

int main() {
 cout << sizeof(PackA) << endl;//12
 cout << sizeof(PackB) << endl;//8

 char* buffer = new char[sizeof(PackA)];
 PackA packa;
 cout << "packa_addr " << (long long)&packa % sizeof(int) << endl;//永远为0因为PackA字节对齐大小为4byte
 packa.a = 'a';
 packa.b = 1;
 packa.c = 2;//内存序列化
 memcpy(buffer, &packa, sizeof(packa));
 //解包操作
 PackA packa_copy;
 memcpy(&packa_copy, buffer, sizeof(packa_copy));
 cout << packa_copy.a << " " << packa_copy.b << " " << packa_copy.c << endl;
 //a 1 2
 delete[] buffer;
 return 0;
}

内存中如果有数组呢,数组按照其元素类型的对齐大小对每一项对齐,数组内内存地址连续

#include
using namespace std;

#pragma pack(show)//16

struct PackA {
 char a;//0x00
     //0x01
     //0x02
     //0x03
 int arr_int[2];//0x04 0x05 0x06 0x07
       //0x08 0x09 0x0A 0x0B
   //0x0C
   //0x0D
   //0x0E
   //0x0F
 int b;//0x10 0x11 0x12 0x13
 short arr_short[2]; //0x14 0x15 0x16 0x17 现在为18大小 结构体对齐大小为4
   //0x18
   //0x19 20%4=0 所以大小为20
};

int main() {
 cout << sizeof(PackA) << endl;//20
 return 0;
}

如果结构体嵌套结构体呢,结构体对齐大小则为其内存最大的对齐大小

#include
using namespace std;

#pragma pack(show)//16

struct PackA {
 char a;//0x00
     //0x01
     //0x02
     //0x03
 int arr_int[2];//0x04 0x05 0x06 0x07
       //0x08 0x09 0x0A 0x0B
   //0x0C
   //0x0D
   //0x0E
   //0x0F
 int b;//0x10 0x11 0x12 0x13
 short arr_short[2]; //0x14 0x15 0x16 0x17 现在为18大小 结构体对齐大小为4
   //0x18
   //0x19 20%4=0 所以大小为20
};

struct PackB {
 char a;//0x00 因为PackA对齐大小为4
     //0x01
        //0x02
        //0x03
 PackA packa;//0x04 ... 0x15
 //PackB 的对齐大小则为PackA的对齐大小 4
 //因为a对齐大小1<4
 //24%4=0 正好对齐
};

int main() {
 cout << sizeof(PackB) << endl;//24
 return 0;
}

对齐大小是根据不同的平台变化的,有些时候我们会一劳永逸,使用预处理命令进行指定pack的大小

#include
using namespace std;

#pragma pack(show)//16
#pragma pack(1) //设置pack大小

struct PackA {
 char a;//0x00
 int arr_int[2];//0x02 0x03 0x04 0x05 min(4,1) 选择1作为对齐大小
       //0x06 0x07 0x08 0x09
 int b;//0x0A 0x0B 0x0C 0x0D min(4,1)做对齐大小
 short arr_short[2]; //0x0E 0x0F 0x10 0x11
};//则PackA的对齐大小为1 内部使用的最大的对齐大小为1

struct PackB {
 char a;//0x00 min(1,1)做对齐大小
 PackA packa;//0x01 ... 0x12 min(PackA,1)做对齐大小
};//同理PackB的对齐大小为1

int main() {
 cout << sizeof(PackB) << endl;//18
 //如果将pack设置为2,就大于2的对齐大小则必须使用2作为对齐大小
 //小于2的对齐大小则使用其本身即可
 return 0;
}

上面的默认pack大小为16一般没有那种数据类型的对齐大小超过16

位域

什么是位域?如果你是一位嵌入式工程师可能会更熟悉,类或结构体可以将非静态数据成员定义为位域,每个位域含有一定的二进制位,通常用于串口通信等,位域在内存的布局与机器相关

//example41.cpp
#include 
using namespace std;

//位域的类型必须为整形或者枚举类型
typedef unsigned int Bit;
class Block
{
public:
    Bit mode : 2; //占两个二进制位
    Bit modified : 1;
    Bit prot_owner : 3;
    Bit prot_group : 3;
    Bit prot_world : 3;
};

int main(int argc, char **argv)
{
    Block block;
    &block.modified;
    // error: attempt to take address of bit-field
    return 0;
}

取址运算符&,不能作用于位域,任何指针都不能指向位域

使用位域

位域的访问方式,与其他数据成员类似

//example42.cpp
#include 
using namespace std;

//位域的类型必须为整形或者枚举类型
typedef unsigned int Bit;
class Block
{
public:
    Bit mode : 2;     //占两个二进制位 存储的大小范围为[0,3]
    Bit modified : 1; //存储的大小范围为[0,1]
    enum modes
    {
        READ = 1,
        WRITE = 2,
        EXECUTE = 3
    };
    Block &open(modes);
    bool isRead();
    void setWrite();
    void write();
};

void Block::write()
{
    modified = 1;
}

Block &Block::open(Block::modes mode_)
{
    mode |= READ;
    if (mode_ & WRITE)
        setWrite();
    return *this;
}

bool Block::isRead()
{
    return mode & READ;
}

void Block::setWrite()
{
    mode |= WRITE;
}

int main(int argc, char **argv)
{
    Block block;
    block.setWrite();
    cout << block.mode << endl;
    return 0;
}

位域与字节对齐

上面有学习过结构体中的字节对齐问题,那么含有位域的结构体的内存对齐情况又是怎样的呢?

#include
using namespace std;

//位域只能方整形或枚举

struct PackA {
 int a : 4;//4bit
 int : 2; //2bit
 int b : 4;//4bit
 unsigned c : 2;//2bit
 char d;//8bit
};

int main() {
 cout << sizeof(PackA) << endl;//8
 //What fuck. 怎么回事
 return 0;
}

下面来分析一下

//错误的分析方法
#include
using namespace std;

struct PackA {
 int a : 4;//4bit 0 1 2 3
 int : 2; //2bit  4 5
 //因为前面用了6bit 还有2bit凑一个字节
 //凑不下b了故直接跳过这2bit
 // 6 7
 int b : 4;//8 9 10 11
 unsigned c : 2;//12 13
    //跳过 14 15
 char d;//8bit 
 //16 17 18 19 20 21 22 23
};//这么分析也就才3字节啊

int main() {
 cout << sizeof(PackA) << endl;//8
 //What fuck. 怎么回事
 
 return 0;
}

1、一个位域必须存储在同一个字节中,不能跨字节存储。如一个字节所剩空间不能存储下一个位域的时候,应从下一个字节开始存储。也可以有意使某个位域从下一单元开始
2、由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,
也就是说位域的不能超过8bit;
3、位域可以无位域名,这时它只用作填充或调整位置。无名的位域是不能使用的
4、位域结构的成员不能单独被取sizeof值
5、如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
6、如果相邻位域字段的类型相同,且其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
7、如果相邻位域字段的类型不同,则各个编译器的具体实现有所差异,VC不压缩,而Dev C++压缩;
8、如果位域字段之间插着非位域字段,则不进行压缩;
9、整个位域结构体的总体大小为最宽的基本类型成员大小的整数倍。

VC正确的分析方法

#include
using namespace std;

struct PackA {
 int a : 4;//4bit 0 1 2 3
 int : 2; //2bit  4 5
 //因为前面用了6bit 还有2bit凑一个字节
 //凑不下b了故直接跳过这2bit
 // 6 7
 int b : 4;//8 9 10 11
 unsigned c : 2;//12 13
     //14 15
     //16 17 18 19
     //20 21 22 23
     //24 25 26 27
     //28 29 30 31 跳过 前面大小共4byte
 char d;//8bit 
};//这么分析也就才5字节啊,因为要结构体对齐,最大为int的4byte,如为4的整数倍
//为对齐大小为8

struct PackB {
 short a : 4;//开启2byte
 int b : 4;//开启4byte int与short不能共用
};//结构体对齐大小为8

struct PackC {
 char a : 2;
 char : 1;
 char b : 5;
};

struct PackD {
 long a : 4;
 int b : 4;
 unsigned c : 8;//因为long int unsigned的对齐大小都为4,
 //所以即使其类型不同也可以共享内存
};

int main() {
 cout << sizeof(PackA) << endl;//8
 //What fuck. 怎么回事
 cout << sizeof(PackB) << endl;//8
 cout << sizeof(PackC) << endl;//1
 cout << sizeof(PackD) << endl;//4
 return 0;
}

你可能感兴趣的:(C++,就该这么学,c++,开发语言)