zoukankan      html  css  js  c++  java
  • C语言结构体及其内存布局

    C语言结构体

    1. 结构体的定义
      结构体的定义要使用struct关键字,并以";"结尾。
      下面找个微软定义的结构体:

      typedef struct _FILETIME {
       DWORD dwLowDateTime;
       DWORD dwHighDateTime;
       } FILETIME, *PFILETIME, *LPFILETIME;
      

      可以看出在定义结构体时使用了typedef,为_FILETIME起了一个别名,并定义了指向改结构
      的指针PFILETIME。

    2. 结构体变量的初始化与赋值操作

      • 使用初始化列表进行初始化
        例如:
          FILETIME ft = { 88,99 };
        
        还可以使用memset进行清零初始化:
        FILETIME ft
        memset(&ft,0,sizeof(ft));  
        
      • 结构体变量赋值
        C++11标准之前只能在结构体变量初始化的时候可以使用列表进行初始化,
        现在支持C++11标准的编译器可以在任意场合使用列表进行赋值,编译时不会报错.
        例:
        结构体列表赋值.png
        C++11标准还可以直接在定义结构体时为每个成员指定初值,例:
        结构体初值.png
        但是最后还是不要使用新标准这两个特性,因为在不支持C++11标准的编译器上会报错,
    3. 一个空结构体的大小
      一个空结构体的大小为1字节,而不是零字节
      例:
      空结构体大小.png

    4. 计算结构体大小

      • 结构体成员对齐值
      typedef struct tagTest
      {
        char m_chTest;
        int m_nTest;
      }TEST;
      

      上面这个结构体的大小是多少呢?如果你不知道内存对齐,那么很可能认为认为其大小为5字节,
      但其实这个结构体的真实大小可能为5字节,也有可能为6字节,也有可能是8字节.

      结构体的大小与结构体的成员对齐值有关,设置不同的成员对齐值,将使得同一个结构体有不同大小,
      可以在VS的项目->属性->C/C++->代码生成,设置所有的结构体成员对齐值:
      结构体成员对齐设置.png
      VC++编译器共支持1,2,4,8,16这五个对齐值,默认对齐值为8.如果结构体成员对齐值是1,那么表示结构体
      成员不对齐,在这种情况下上面例子中的TEST的大小为5.
      还可以使用/Zp命令来这设置结构体成员对齐值:
      命令行设置结构体成员对齐值.png
      但是对齐值必须是1,2,4,8,16,如果指定为其他值编译器编译时会给出警告,并自行选择合理的编译对齐值:
      非法的结构体对齐值.png
      上面这两种设置成员对齐值的方法是设置所有结构体的成员对齐值,但是有时我只想设置单个结构体的成员对齐值,
      那么可以使用编译预处理指令#pragma pack来设置,例如:

        #pragma pack(push,1)
        typedef struct tagTest
        {
          char m_chTest;
          int m_nTest;
        }TEST;
      
        #pragma pack(pop)
      

      #pragma pack(push,1)中的push表示保存当前的结构体成员对齐值,然后将结构体成员对齐值设置1,
      #pragma pack(pop)表示恢复结构体成员对齐值为上次保存的结构体对齐值,那么加载这两条编译预
      处理命令间定义的结构体的成员对齐值全部为1

    • 结构体大小计算
      (1)设结构体成员对齐值为ZP
      (2)设结构体当前数据成员对齐值zp=min(当前数据成员类型大小,ZP)
      (3)设结构体自身对齐值stAlign=min(max(数据成员1类型大小,.....数据成员n类型大小),ZP)
      (4)设置结构体某成员距离结构体首地址的偏移为offset
      (5)每个成员的位置偏移(也就是offset)要对zp取余,如果余数不为0,则要调整位置偏移,
      在大于当前偏移值中找一个最小的位置偏移,使之能够对zp取余且余数为0,最后结构体
      的总大小要对stAlign取余,如果余数不为0,采用相同的方法调整结构体大小
      注意:如果结构体中有成员为数组,例如:

           struct tagTest
           {
               char m_ch;
               int  m_nAry[10];
           };
      
          那么m_nAry的数据类型大小为4字节,而不是40字节
      
          如果结构体A中有另一个结构体B作为作为结构体A的数据成员,例如:
      
           #pragma pack(push,8)
           struct B
           {
               char m_ch;
               int  m_nAry[10];
           };
      
           struct A
           {
               B    m_b;
               char m_ch;
               int  m_nAry[10];
           };
           #pragma pack(pop)
      
          那么在结构体A中m_b的对齐值为为结构体B的自身对齐值,也就是4,在计算结构体A  
          的自身对齐值时,并不是将sizeof(m_b)参与计算,而是取结构体B中宽度最大的基本  
          数据类型所占字节参与计算,例如结构体B中宽度最大的基本数据类型为int,也就是  
          4字节,所以结构体A的自身对齐值为min(max(4,1,4),8)=4
      

      例1:

         #pragma pack(push,8)
         typedef struct tagTestA
         {
           char m_chTest;
           int m_nTest;
           float m_fTest;
           char m_chAry[13];
         };
        #pragma pack(pop)
      
      • 结构体成员对齐值为ZP=8,结构体自身对齐值stAlign为:
        min(max(sizeof(char),sizeof(int),sizeof(int),sizeof(float),sizeof(char)),ZP)=4

      • m_chTest的对齐值zp=min(sizeof(char),ZP)=1,m_chTest为结构体第一个成员,所以其相对结
        构体首地址偏移量offset=0,offset对zp取余结果为0,所以无需调整,则下一个成员m_nTest相对
        于结构体首地址的偏移为offset+sizeof(m_chTest)=1

      • m_nTest的对齐值zp=min(sizeof(int),ZP)=4,m_nTest相对首地址的偏移量offset=1,
        offset%zp=1,取余结果不为0,当offset调整为4时,取余结果为0,则下一个成员m_fTest相对于
        结构体首地址的偏移为offset+sizeof(m_nTest)=8

      • m_fTest的对齐值zp=min(sizeof(float),ZP)=4,m_nTest相对首地址的偏移量offset=8,
        offset%zp=0,取余结果为0,无需调整,则下一个成员m_chAry相对于结构体首地址的偏移为
        offset+sizeof(m_fTest)=12

      • m_chAry的对齐值zp=min(sizeof(char),ZP)=1,m_chAry相对首地址的偏移量offset=12,
        offset%zp=0,无需调整,至此结构体tagTestA最后一个成员的偏移计算完毕,结构体大小为
        offset+sizeof(m_chAry)=25, 25%stAlign != 0,所以结构体总大小需要调整,那么大于25且能
        够对4取余为0的最小值为28,所以结构体大小为28,那么下面来验证下计算结果:
        结构体大小以及偏移计算1.png

      测试代码中计算结构体偏移使用了宏函数offsetof,其定义在stddef.h中:

      #if defined(_MSC_VER) && !defined(_CRT_USE_BUILTIN_OFFSETOF)
        #ifdef __cplusplus
          #define offsetof(s,m) ((size_t)&reinterpret_cast<char const volatile&>((((s*)0)->m)))
        #else
          #define offsetof(s,m) ((size_t)&(((s*)0)->m))
        #endif
      #else
        #define offsetof(s,m) __builtin_offsetof(s,m)
      #endif
      

      ((size_t)&(((s*)0)->m)):(s*)0先是将0地址强行解释为s类型的指针,然后(s*)0)->m引用结构体的
      成员m,&(((s*)0)->m))取得成员m的内存地址,因为该指针指向0地址,所
      以强转后就得到成员m相对于结构体首地址的偏移,虽然是指向0地址的指针
      但是并未使用该指针对结构体成员进行赋值和取值操作,所以不会引发崩溃。

    例2:

      #pragma pack(push,8)
      typedef struct tagTestA
      {
      char m_chTest;
      int m_nTest;
      float m_fTest;
      char m_chAry[13];
      };
      #pragma pack(pop)
    
    
      #pragma pack(push,4)
      struct tagTestB
      {
      double m_dbTest1;
      char   m_chTest2;
      tagTestA m_stA;
      char   m_ch[7];
      };
      #pragma pack(pop)
    

    结构体tagTestB的计算有点复杂,首先它的结构体成员对齐值设置为4,其次,它的一个数据成员为结构体tagTestA类型,下面开始计算:

    • 根据例1中的计算结构体tagTestA的自身对齐值为4,tagTestA的的大小为28;
      tagTestB的成员对齐值ZP=4
      tagTestB的自身对齐值stAlign=min(max(sizeof(double),sizeof(char),4,sizeof(char)),ZP)=4

    • m_dbTest1为结构体第一个成员,距离结构体的首地址偏移offset=0,m_dbTest1的自身对齐值
      zp=min(sizeof(double),ZP)=4,offset%zp=0,所以无需调整offset,那么下一个成员m_chTest2
      相对于结构体首地址的偏移为offset+sizeof(double)=8

    • 根据上一步,m_chTest2距离结构体首地址的偏移offset=8,m_chTest2的自身对齐值
      zp=min(sizeof(char),ZP)=1,offset%zp=0,所以offset无需调整,则下一个数据成员m_stA
      距离结构体首地址的偏移为offset+sizeof(char)=9

    • 根据上一步,m_stA距离结构体首地址的偏移offset=9,m_stA的自身对齐值zp=min(4,ZP)=4,
      offset%zp!=0,offset需要调整,将offset调整为12时满足offset%zp=0,则下一个成员m_ch
      距离结构体首地址的偏移为offset+sizeof(m_ch)=40

    • 根据上一步,m_ch距离结构体首地址的偏移offset=40,m_ch的自身对齐值为zp=min(sizeof(char),ZP)=1,
      offset%zp=0,所以offset无需调整,则结构体tagTestB的大小为offset+sizeof(m_ch)=47,
      且47%stAlign!=0,所以结构体大小需要调整,当结构体大小为48时,满足要求

      通过以上计算可以得出m_dbTest1的偏移为0,m_chTest2的偏移为8,m_stA的偏移为12,m_ch的偏移为40,
      结构体总大小为48,下面在VS中验证结果:
      结构体大小以及偏移计算2.png

    注意事项:

    • 以上是VC++编译器根据不同结构体成员对齐值实现内存布局的解析,其他编译器的实现可能有所不同

    • 当结构体成员对齐值为1时,表示结构体成员不进行内存对齐,各个成员紧紧相邻,不会有填充字节,
      那么此时结构体大小为sizeof(member1)+..........sizeof(membern)

  • 相关阅读:
    禁止后台运行
    图标的圆角和光晕效果和启动画面
    IOS 开发 有关iPhone程序的安装目录UUID 唯一标识
    NSOperation与performSelectorOnMainThread
    Java web开发学习规划
    JAVA类集
    java 四种xml操作方式的基本使用方法
    用JDOM操作XML文件
    java web 学习
    过去的,将来的
  • 原文地址:https://www.cnblogs.com/UnknowCodeMaker/p/11009637.html
Copyright © 2011-2022 走看看