<CONTAINING_RECORD宏>引发的<结构体深度剖析(内存对齐,对齐参数,偏移量)>

什么是结构体内存对齐?为什么要对齐?怎样对齐?
  结构体内存对齐:元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。

  从结构体存储的首地址开始,每个元素放置到内存中时,它都会认为内存是按照自己的大小来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始。

struct T
{
  int a;
  char b;
  double c;
  char d;
};


解析:
在windows系统32位平台上:
int占4个字节
char占1个字节
float占4个字节
double占8个字节

int a从0偏移开始,占四个字节,即占用0,1,2,3,

现在可用偏移为4偏移,接下来存char b; 由于4是1的倍数,故而,b占用4偏移,

接下来可用偏移为5偏移,接下来该存double c; 由于5不是8的倍数,所以向后偏移5,6,7,都不是8的倍数,偏移到8时,8是8的倍数,故而c从8处开始存储,占用8,9,10,11,12,13,14,15偏移,

现在可用偏移为16偏移,最后该存char d ;因为16是1的倍数,故d占用16偏移,

接下来在整体向后偏移一位,现处于17偏移,min(默认对齐参数,类型最大字节数)=8;因为17不是8的倍数,所以继续向后偏移18…23都不是8的倍数,到24偏移处时,24为8的整数倍,

故而,该结构体大小为24个字节。

  

 

 

 

方法总结:
a、从零偏移处开始,按字节大小计算,判断此偏移地址是否为该成员变量和对齐参数两者之间的最小值,即min(对齐参数,sizeof()成员);
b、若是,则从此处开始占用内存,大小为该类型所占字节数值,若不是,则内存向后偏移到最小值整数倍处,再开始占用空间。
c、按a、b、两步骤算出结构体实际所占内存时,为了方便后面类型的存储,再向后偏移一位,然后判断该地址是否是默认对齐数与该结构体中最大类型所占字节数的最小值 ,即:min(默认对齐参数,类型最大字节数)的整数倍,若是,则当前偏移地址的字节数便是结构体大小,若不是,继续向后偏移,直至为最小值整数倍为止。

 

对齐参数如何设置?可以设置为按照任意字节数对齐吗? 

解析:

在windows中,VS编译器下,默认对齐数为8; 在Linux中,默认对齐数为4 

设置对齐参数可在结构体struct之前加上#pragma pack(对齐数),在struct之后加上#pragma pack;便可以设置对齐参数。

#pragma pack(4)
struct T
{
  int a;
  char b;
  double c;
  char d;
};
#pragma pack;


对齐参数不能任意设置,只能是内置类型已有的字节数,如:char(1)、short(2),int(4),double(8)…不能是3,5…任意数。

 

如何知道结构体某个成员相对于结构体起始位置的偏移量?

使用offsetof宏来判断结构体中成员的偏移地址。使用offsetof宏需要包含stddef.h头文件,该宏定义如下:

#define offsetof(type,menber) (size_t)&(((type*)0)->member)

巧妙之处在于将地址0强制转换为type类型的指针,从而定位到member在结构体中偏移位置,编译器认为0是一个有效的地址,从而认为0是type指针的起始地址。 

 

如何根据结构体中的某成员的地址来推算出该结构体整体的地址?

  

#include <Windows.h>
#include <stdio.h>
int main()
{
  struct T
  {
    int a;
    int b;
    int c;
  };
  //CONTAINING_RECORD宏的作用就是根据结构体中的某成员的地址来推算出该结构体整体的地址。
  T t = { 1, 2, 3 };

 

  //假设我们知道T结构体中b的地址和名称,求t的指针
  T *pT = CONTAINING_RECORD(&t.b, T, b);
  printf("a:%d b:%d c:%d\n", pT->a, pT->b, pT->c);

 

  //CONTAINING_RECORD的定义:
  //((type *)( (PCHAR)(address) - (ULONG_PTR)(&((type *)0)->field)))
  //最后一部分(&((type *)0)->field) 将0(空指针)转成type,再取地址。
  //在本例中就是将空指针转成T*, 然后指向b这个变量, 然后再取地址。
  //这个操作的作用就是:假设T开始在0x000000内存位置上分配内存,在此基础上求b的内存地址,
  //这样等同于求得b的内存结构体对齐偏移量, 求得b的地址我们转成ULONG_PTR类型,
  //然后用实际b的内存地址减去b的结构体偏移量求得结构体首地址。
  //
  //分解开来就是:

 

  //这种情况是允许的。
  //这个大前提很重要!!!
  T *pTemp = (T*)0;

 

  //求b的内存地址,在结构体首地址为0的情况下b的内存地址其实就是自身的对齐大小偏移量!!!
  //CONTAINING_RECORD宏的核心!!!
  int *pB = &pTemp->b;

 

  ULONG_PTR Offset = (ULONG_PTR)pB; //转成数字, 就是b的偏移量。
  printf("b的偏移量:%d\n", Offset);

 

  //因为各个成员的地址是递增的,最后用实际b的地址减b的偏移量的到结构体首地址
  T *pFinal = (T*)(((char*)&t.b) - Offset);
  printf("T中a:%d b:%d c:%d\n", pFinal->a, pFinal->b, pFinal->c);

 

  return 0;
}

 

————————————————

版权声明:本文为CSDN博主「dai_wen」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/dai_wen/article/details/78304568

版权声明:本文为CSDN博主「china_jeffery」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/china_jeffery/article/details/78801331

posted @ 2020-01-10 14:39  光晕  阅读(218)  评论(0编辑  收藏  举报