<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
<CONTAINING_RECORD宏>引发的<结构体深度剖析(内存对齐,对齐参数,偏移量)>的更多相关文章
- 指针直接赋值为整型AND利用宏定义求结构体成员偏移量
首先我们要更正一个很熟悉的概念,那就是指针不仅仅是“地址”,指针还有一个很重要的特性,那就是“类型”. 指针初始化时,“=”的右操作数; 除外,该语句表示指针为空): 所以 ; 这样的代码是不允许的. ...
- Delphi 给结构体指针分配内存,用new(p),释放用dispose(p)
来自:http://blog.163.com/zhangzhifeng688%40126/blog/static/1652627582010102261748481/ 给结构体指针分配内存 但在很多 ...
- C++中的结构体所占内存空间总结
因为结构体有时候需要字节对齐.一般而言,struct 的 sizeof 是所有成员字节对齐后长度相加,而 union 的 sizeof 是取最大的成员长度. 在默认情况下,编译器为每一个变量或数据单元 ...
- C/C++编程笔记:C语言对齐问题【结构体、栈内存以及位域对齐】
引言 考虑下面的结构体定义: 假设这个结构体的成员在内存中是紧凑排列的,且c1的起始地址是0,则s的地址就是1,c2的地址是3,i的地址是4. 现在,我们编写一个简单的程序: 运行后输出: 为什么会这 ...
- C/C++ 结构体成员在内存中的对齐规则(转载)
这几天在看王艳平的<windows 程序设计>,第5章讲解了MFC框架是怎么管理窗口句柄到窗口实例之间的映射,用到了两个类CPlex和CMapPtrToPtr,用于管理内存分配的类(避免因 ...
- 关于C语言中结构体中的结构体成员导致的字节对齐问题
关于结构体的字节对齐是什么,就不赘述,再此附上一篇文章,介绍字节对齐:http://www.linuxsong.org/2010/09/c-byte-alignment/ 这里的结构体字节对齐的数据类 ...
- C/C++ 结构体成员在内存中的对齐规则
这几天在看王艳平的<windows 程序设计>,第5章讲解了MFC框架是怎么管理窗口句柄到窗口实例之间的映射,用到了两个类CPlex和CMapPtrToPtr,用于管理内存分配的类(避免因 ...
- [c/c++] programming之路(28)、结构体存储和内存对齐+枚举类型+typedef+深拷贝和浅拷贝
一.结构体存储 #include<stdio.h> #include<stdlib.h> struct info{ char c; //1 2 4 8 double num; ...
- C 语言结构体 struct 及内存对齐
struct 结构体 对于复杂的数据类型(例如学生.汽车等),C 语言允许我们将多种数据封装到一起,构成新类型. 跟面向对象语言中的对象相比,结构体只能包含成员变量,不支持操作. #include & ...
- 结构体sizeof()问题与字节对齐
32位机器上定义如下结构体: struct xx { long long _x1; char _x2; int _x3; char _x4[2]; static int _x5; }; int xx: ...
随机推荐
- C++ 地球人口承载力
题目描述 假设地球上的新生资源按恒定速度增长.照此测算,地球上现有资源加上新生资源可供 xx 亿人生活 aa 年,或供 yy 亿人生活 bb 年. 为了能够实现可持续发展,避免资源枯竭,地球最多能够养 ...
- Spring04-AOP(Debug查看执行流程)
1 AOP的几个核心技术 AOP-面向切面编程的实现的核心技术:jvm运行期间对字节码进行修改或者动态生成新的字节码文件(asm技术). 2 AOP的几个核心概念 AOP在运行期间我们要对class文 ...
- Windows/office常用的激活工具有哪些
Windows/office常用的激活方式 Windows激活方式有两种 Kms激活与数字权利永久激活,这两种激活方式各有优势,KMS激活通用性强(支持Windows+Office),但只能激活180 ...
- SqlServer Express 自动备份数据库(没有sql代理服务的情况下)
一.由于Express版本的SQLServer没有SQL代理服务(SQLSERVERAGENT),所以就不能通过SQLServer作业来进行自动备份了,那么我们可以用Windows计划任务来定时处理 ...
- Android JetPack~ LiveData (一) 介绍与使用
一般情况下LiveData都是搭配这ViewModel使用,这里先介绍一下LiveData,再结合ViewModel使用 Android数据绑定技术一,企业级开发 Android数据绑定技术二,企业级 ...
- mysql怎么设计库、设计表
一. 设计表/库 设计思想就是要分析表每个字段的具体参数,包括但不限于以下几点: 约束? NOT NULL, PRIMARY KEY, UNIQUE KEY, DEFAULT, FOREIGN KEY ...
- 精华推荐 |【算法数据结构专题】「延时队列算法」史上非常详细分析和介绍如何通过时间轮(TimingWheel)实现延时队列的原理指南
时间轮的介绍 时间轮(TimeWheel)是一种实现延迟功能(定时器)的精妙的高级算法,其算法应用范围非常广泛,在Java开发过程中常用的Dubbo.Netty.Akka.Quartz.ZooKeep ...
- EL 表达式直接显示在页面上
如上图,如果你使用 el 表达式直接显示在页面上,那么很有可能是因为你没有给 jsp 页面添加一项配置,重新部署项目,EL 表达式就可以生效了: <%@page contentType=&quo ...
- JUC学习笔记
1 进程与线程:进程指正在运行的程序,进程拥有一个完整的.私有的基本运行资源集合.它有自己的内存空间.为了便于进程之间的通信,大多数操作系统都都支持进程间通信(IPC). IPC通信包括管道.消息队列 ...
- Python elasticsearch 使用心得
一.配置 python == 3.6/3.8 # 更高版本的elasticsearch会出现doc_type被统一成_doc导致旧版语句报错的情况 pip install elasticsearch= ...