• C++位域和内存对齐问题


    1. 位域:

    1. 在C中,位域可以写成这样(注:位域的数据类型一律用无符号的,纪律性)。
    1 struct bitmap
    2 {
    3   unsigned a : 1;
    4   unsigned b : 3;
    5   unsigned c : 4;
    6 }bit;
      sizeof(bitmap) == 4;(整个struct的大小为4,因为位域本质上是从一个数据类型分出来的,在我们的例子中数据类型就是unsigned,大小为4,并且位域也是满足C和C++的结构体内存对齐原则的,等下我们会说到)。
    2. 当然了位域也可以有空域。
    1 struct bitmap
    2 {
    3   unsigned a:4;
    4   unsigned :0; /*空域*/
    5   unsigned b:4; /*从下一单元开始存放*/
    6   unsigned c:4;
    7 }
    8 sizeof(bitmap) == 8;
    3. 在这个位域定义中,a占第一字节的4位,后4位填0表示不使用,b从第二字节开始,占用4位,c占用4位。这里我们可以看到空域的作用是填充数据类型的剩下的位置,有时候我们只是想调整一下内存分配,则我们可以使用无名位域:
    1 struct bitmap
    2 {
    3   unsigned a:1;
    4   unsigned :2;
    5   unsigned b:3;
    6   unsigned c:2;
    7 };
    8 sizeof(bitmap) == 4;
    4. 如果一个位域的位的分配超过了该类型的位的总数,则从下一个单元开始继续分配,这个很好理解:
    1 struct bitmap
    2 {
    3   unsigned a : 8;
    4   unsigned b : 30;
    5   unsigned c : 4;
    6 };
    7 sizeof(bitmap) == 12;
      注意这个位域的大小是12而不是8,说明如果超了大小是立马从下一个单元开始分配而不是cast在后面。
     
    5. C++的位域:
      由于C++有类,所以呢位域可以写进类里面,很简单比如:
    1 class Demo
    2 {
    3   unsigned mode : 8;
    4   unsigned modeifed : 2;
    5   unsigned protA : 3;
    6   unsigned protB : 4;
    7 public:
    8 ...
    9 };
      另外就是需要注意的是,取地址运算符不能作用于位域,所以任何指针都无法指向类的位域,访问位域的方法和访问类的成员的方法一样,这里就不多说了。
     
    2. 内存对齐:

    1. 说到位域就不得说下内存对齐的东西,其实内存对齐也很简单,只是不同的编译器实现不一样,至于为什么要内存对齐,这个要从CPU的基本工作原理说起,但是首先要明白,无论我们是否内存对齐,CPU大多数情况都是能正常工作的(前提:对于大多数IA32指令都可以这么说,但是部分指令,如SSE多媒体指令这些就不行,这些指令有特殊内存对齐要求,比如16字节对齐,任何不满足内存对齐的地址访问储存器都是会导致异常,对于这些指令,编译器必须在编译的时候采取强制内存对齐)。
     
      实现内存对齐可以提高CPU的性能,比如处理器能一次取出8个字节,这个时候必须要求数据地址要8字节对齐,这个是和CPU和储存器的外围电路决定的,在内存对齐的情况下,CPU从储存器取出这8个字节只需要一个时钟周期,但是如果这个地址不是8字节对齐,那么CPU可能就需要两个时钟周期才能取出这8个字节。
      对于IA32,每个栈帧都惯例16字节对齐,编译器一般也会那么做,但是对于数据类型不同的编译器表现可能不一样,对于Windows(VC编译器),任何K字节的基本对象的地址都必须是K的倍数(比如对于int,必须4字节对齐,对于double,必须8字节对齐),这很大程度上提高了储存器和CPU的工作性能,但是对存储空间的浪费比较严重;对于Linux,惯例是8字节数对齐4字节边界(比如double可以4字节对齐)。对于Windows好Linux,数据类型long double都有4字节对其的要求,对于GCC,long double分配12字节(虽然它只占10字节大小)。
      所以我们有一般规则(在知乎找了个例子):
    1 struct X
    2 {
    3   char a;
    4   float b;
    5   int c;
    6   double d;
    7   unsigned e;
    8 };
    9 sizeof(X) == 32;
      内存对齐状况应该是下面这个样子:
     1 struct X
     2 {
     3   char a; // 1 bytes
     4   char padding1[3]; // 3 bytes
     5   float b; // 4 bytes
     6   int c; // 4 bytes
     7   char padding2[4]; // 4 bytes
     8   double d; // 8 bytes
     9   unsigned e; // 4 bytes
    10   char padding3[4]; // 4 bytes
    11 };
    12 sizeof(X) == 32;
      (其中最后的4个字节的填充是因为规则4,看下面)。
     
    2. 如果自定义数据类型含有位域,则内存对齐满足以下原则:
      1. 如果相邻的位域的数据类型相同,则按照分配位的大小来,详情看我上面写的位域的第5个情况。
      2. 如果相邻的位域的数据类型不相同,则不同编译器实现不一样,有些编译器选择不压缩。
      3. 如果位域不连续,中间含非位域,则按标准数据类型大小划分,比如:
    1 struct bitmap
    2 {
    3   unsigned a : 2;
    4   int b;
    5   unsigned c : 3;
    6 };
    7 sizeof(bitmap) == 12;
    3. 另外可以通过添加#pragma pack(n)来强制改变内存分配情况,比如在VC编译器中:
    1 struct bitmap
    2 {
    3   unsigned a;
    4   double c;
    5 };
    6 sizeof(bitmap) == 16;
      加了#pragma pack(4),则强制内存对齐4字节,再测试下其大小:
    1 #pragma pack(4)
    2 struct bitmap
    3 {
    4   unsigned a;
    5   double c;
    6 };
    7 sizeof(bitmap) == 12;
      当然,如果#pragma pack(n)的n大于本身数据类型的宽度,则按数据类型的宽度来分配:
    1 struct bitmap
    2 {
    3   double c;
    4   int k;
    5   int m;
    6 };
    7 sizeof(bitmap) == 16 != 32
    4. 自定义类型(C结构体,C++聚合类)的最后的内存对齐,是按照自定义类型内的最大类型的宽度来的,比如上面那个例子去掉int m:
    1 struct bitmap
    2 {
    3   double c;
    4   int k;
    5 };
    6 sizeof(bitmap) == 16
      必须以double进行8字节对齐(VC编译器)。
     
    5. 对于C++如果类内有虚函数,则如果存在虚函数,则需要添加一个指针的大小(因为需要一个指针指向虚函数指针表,注意如果存在多个虚函数,也只有一个指向虚函数表指针),比如在32位系统则为4字节,在64位则为8字节。
    1 class Test
    2 {
    3 public:
    4     virtual void Hi();
    5  
    6     int c;
    7     double d;
    8 };
    9 sizeof(Test) == 16(IA32)或者 24(x86-64
      特别的,在C++内,空类大小为1(C++不允许0的内存空间)。
    class Test
    {
    };
    sizeof(Test) == 1;
    6. 如果一个类(结构体)A内嵌套着另一个类(结构体)B,则B按B内的最大类型的方式对齐,A的对齐要考虑B(总大小的对齐要考虑B的最大类型)
     1 class A
     2 {
     3     double c;
     4 public:
     5     class B
     6     {
     7         int i;
     8         double c;
     9     }b;
    10     int d;
    11 };
    12 sizeof(A) == 32   sizeof(A::B) == 16

    7. C++的类静态成员不会被sizeof计算,这个要注意:

    class C
    {
    public:
        static char b;
        static int *c; 
    };

    sizeof(C)的结果是1

     
  • 相关阅读:
    进程和线程
    yum安装apache后更改worker模式
    (转)top命令详解
    虚拟机linux在关机不正常的情况下出现的问题
    mysql赋予用户权限grant all privileges on
    lampp自带mysql远程访问问题
    扫盲
    linux常用命令使用
    剑指Offer对答如流系列
    剑指Offer对答如流系列
  • 原文地址:https://www.cnblogs.com/Philip-Tell-Truth/p/5805242.html
Copyright © 2020-2023  润新知