内存对齐与自定义类型

一、 内存对齐

  (一)、为什么会有内存对齐?
      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;

wKioL1bcLJeTuONgAAB6qp0nT98079.png

      由此我们可以明白位段的优点,本来定义了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、枚举一次可以定义大量的枚举量。
本文转自:http://blog.csdn.net/LF_2016/article/details/51406579?ADUIN=1418749937&ADSESSION=1463239135&ADTAG=CLIENT.QQ.5425_.0&ADPUBNO=26496
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值