首页 > 代码库 > 内存对齐与自定义类型

内存对齐与自定义类型

一、内存对齐


  (一)、为什么会有内存对齐?

    1、为了提高程序的性能,数据结构(尤其是栈)应该尽可能的在自然边界上对齐。原因是为了访问未对齐的内存,处理器需要进行两次访问,而访问对齐的内存,只需要一次就够了。这种方式称作“以空间换时间”在很多对时间复杂度有要求问题中,会采用这种方法。


  技术分享

  2、内存对齐能够增加程序的可移植性,因为不是所有的平台都能随意的访问内存,有些平台只能在特定的地址处处读取内存。


   一般情况下内存对齐是编译器的事情,我们不需要考虑,但有些问题还是需要考虑的,毕竟c/c++是直接操作内存的语言,需要了解程序在内存中的分布和运行原理。



(二)、内存对齐:那么如何对齐呢?

    对齐原则:数据存放的起始位置是自身大小的整数倍处。

    例:内存从0地址处开始。

       char1个字节,所以对齐后它可以存放到地址是1的倍数处。

       short2个字节,所以对齐后它可以存放到地址是2的倍数处。

       int是4个字节,所以对齐后它可以存放到地址是4的倍数处。

       double是8个字节,所以对齐后它可以存放到地址是8的倍数处。


现在相信你已经明白了内存对齐的原则,接下来我们看看结构体内存对齐。


(三)、在了解结构体内存对齐之前先来了解几个概念:

    1、默认对齐数:在vs下内存对齐数默认是8,linux是4.可以通过#program pack  ()来修改默认对齐数。

    2、偏移:相对于起始位置的的位置。例如起始位置是2,那么2就是0偏移处,3就是1偏移处。

技术分享


   3、对齐数:变量自身的大小和默认对齐数之中的最小值。假设默认对齐数是8,int类型的对齐数就是4.因为int大小是4,小于8



二、结构体内存对齐原则:

  1、结构或联合的数据成员,第一个成员放到0偏移的地方,以后每个数据成员都放到自身对齐数的整数倍偏移处。

  2、结构体的大小必须是最大对齐数的整数倍。

   例1:

       struct stu

       {

         char   c;          //对齐数是1

         short  b;          //对齐数是2

         double d;           //对齐数是8

         int     i;         //对齐数是4

       };


技术分享


   例2:

       struct stu

       {

          char   c;          //对齐数是1

          short  b;          //对齐数是2

          struct A

          {

             double d;        //对齐数是8

          };

          int     i;         //对齐数是4

       }s;

    嵌套结构体的大小,其分析方法还是一样,最大对齐数是8,sizeof(s)=24。



三、自定义类型 


(一)、结构体声明

   1、没有标签,不完整的声明。同时还定义一个变量。

                    struct

                   {

                          charc;

                           shortb;

                           inti;

                    }t1;


   2、有标签的声明,但没定义变量的声明。

                   struct    A

                  {

                      charc;

                      shortb;

                      inti;

                  };

     //定义一个变量struct A *s1; 

     //注意,在同一个程序中,同时声明1、2两个结构体,则1、2两个结构体会被认为是不同类型的。所以 s1=&t1是错误的。



   3、有标签的声明,同时还定义一个变量。

                    struct    A

                   {

                        charc;

                        shortb;

                        inti;

                    }t3;


   4、声明的同时对结构体重命名为A.

                     typedef  struct    A

                    {

                         charc;

                         shortb;

                         inti;

                     }A;


   5、先有鸡还是先有蛋

                       struct B                                     //无论哪个放到前面都不对

                      {

                           structAa;

                       }s;

                       structA

                        {

                           structBb;

                         };


   如果两个结构体相互嵌套,则在声明的时候需要对其中一个结构体进行不完整的声明。

                          structA;

                          struct B

                             {

                                   structAa;

                              }s;

                         structA

                             {

                                    structBb;

                             };



(二)、结构体的初始化:

     例如:

                   typedef  struct    A

                  {

                       charc;

                       shortb;

                       inti;

                  }A;

                 As1 = {‘c‘, 2 ,  4  };



(三)、结构体的自引用:(结构体的自引用通常会用在链表这种线性结构中用到)

   

  1、错误的自引用方式,很容易理解的,结构体里面又有结构体,这样一直循环下去。(从前有座庙,庙里有个老和尚,老和尚给小和尚讲故事..........^v^)

                     typedef struct   A

                      {

                             intdata;

                             structAn;                                         //死循环

                       }A;


  2、错误的只引用,因为结构体被重新命名为A是在引用之后。

                     typedef struct                                         //在结构体自引用的时候标签不能省略。

                     {

                               intdata;

                           An;                                                         //必须使用完整的结构体名称

                     }A;


  3、正确的方式

                  typedef struct   A

                 {

                         intdata;

                         structA *n;            //用完整的结构体名称,声明一个结构体指针,

                  }A;



(四)、结构体做参数传递的效率:

   当结构体很大时,结构体在作为参数传递时,我们传递它的地址,这样能够提高效率,如果你不想改变结构体内容,则在形参处加上const就行。


(五)、柔性数组:

   在结构体中最后一个成员允许是未知大小的数组,这个数组成为柔性数组(柔性数组之前至少有一个成员变量)

             typedef  struct    A

               {

                     inti;

                      char  a[];

               }A;

   含有柔性数组的结构体大:这样的结构体,它的大小不包括柔性数组,所以sizeof(A)=4;空结构体的大小是1;



(六)、位段(位域):


  1、概念:在一个结构体中以位为单位来指定成员所占内存的实际大小,这种以位为单位的成员我们称为位段,位段是一种特殊的结构体,位段的声明和任何普通的结构体成员声明类似,如下:


         Struct 位段结构体名

             {

                   Unsigned 位段名:位段长度;

                   Unsigned 位段名:位段长度;

………………..

                   Unsigned 位段名:位段长度;


             }位段结构体变量名;


   但有两个例外,首先位段成员必须声明成int ,unsigned int, signed int,。其次,在成员的后面是一个冒号和一个整数,这个整数指定该位段所占用位的个数。(实际验证后发现char类型也可以,但是注意,位段中不能将int 和char 混合使用)。


  2、 位段使用时需要注意是:

        1、位段结构体中的成员不能使用数组和指针,但结构体变量可以使数组或者指针。

        2、因为数组和指针都是以字节为单位的,同理也不能用&获取位段的地址。

        3、位段不支持移植。

  例1:声明一个位段,我们先来分析一下他在计算机里面是如何存储的(一个无符号的int是4字节)。        

               struct tagAAA

                {

                  unsigned int a : 1;

                  unsigned int b : 2;

                  unsigned int c : 6;

                  unsigned int d : 4;

                  unsigned int e;

                 }AAA_S;

技术分享


   由此我们可以明白位段的优点,本来定义了5个成员,需要5个存储单位,但是使用位段后只需要4个存储空间就足够了。


  3、优点:

    但它的成员是一个或多个位的字段,这些不同长度的字段实际上是存储于一个或多个整形变量中,他的优点是能够以较少的内存单元存储数据。位段可以用整形形式输出。



例2:

     struct tagAAA

                {

                  unsigned int a : 1;

                  unsigned int  : 2;           //没有声明变量,但是却指定位段大小,称为占位。

                  unsigned int c : 6;

                  unsigned int d : 4;

                  unsigned int e;             //没有指定位段大小,默认为自身类型的大小

                 }AAA_S;                


             




(七)、联合


  1、联合的声明:

          typedefunionA

           {

                inti;

                charc;

            }A;


  2、联合的特点:

    联合成员之间共用同一块空间。联合的大小等于成员中所占内存最大变量大小。可以用来测大小端。



(八)、枚举:

    1、声明:

           typedefenumA

           {

                   zero,

                   one,

                    two

            }A;

如果没有对枚举成员进行初始化时,则默认枚举成员从0开始依次递增


 注意:

      1、在同一个程序中,不能不能声明同名的枚举类型

      2、在同一个程序中,不同的枚举类型的枚举成员不能同名。

      3、任何枚举的大小都是4


  2、枚举与#define 标识符之间区别:

      1、#define 标识符在预编译期间进行简单替换。枚举类型在编译的时候确定其值。

      2、枚举常量可以调试,#define 标识符不可以。

      3、枚举一次可以定义大量的枚举量。


本文出自 “11132019” 博客,转载请与作者联系!

内存对齐与自定义类型