这里分析一下这个CONTAINING_RECORD宏,该宏的作用是通过结构体类型内部某一成员的位置来获取该结构体的位置。结构体内容如下:
// // Calculate the address of the base of the structure given its type, and an // address of a field within the structure. // #define CONTAINING_RECORD(address, type, field) ((type *)( \ (PCHAR)(address) - \ (ULONG_PTR)(&((type *)0)->field)))看上去有点复杂,这里分为三部分讲。
首先看下面的问题:
这段代码计算一个名为INFO的结构体大小,在32位编译条件下,理论上来讲short + char + int应该是2 + 1 + 4 = 7字节的总大小,为什么这里会显示8字节?
操作系统管理内存是以偶数位对齐的,当计算一个结构体大小时会从结构体第一个位置开始往下扫描扫描到一个成员后先看该成员类型占空间大小,如果比之前都大,就以这个大小为基准进行对齐。比如之前都是char, short类型,突然碰到了一个int,那就以int为基准,简而言之就是以占空间最大的类型为标准所以这个结构体中,首先碰到的是short,就是2字节,接下去是char,发现比short小,依旧以short为基准进行对齐,char占2个字节,所以总共为4字节。最后碰到的是int,所以一共是8字节。可能有人会问,为什么short和char不分别扩充为4字节? 那样就是12字节。出于节约空间考虑,基准现在是4字节,而short + char的总字节数只要为4就可以了再看一个例子:
这个结构体一共24字节,来分析一下。
首先碰到short类型的x,所以大小计算为2,接着碰到char类型的y,由于char比short类型小,所以扩充为short型的大小,所以这个y占据2字节空间
接着就是int。int比short和char都大,所以现在基准就变成了int了,未来的内容都以4字节对齐。可以怪的是之后扫描到的3个char类型变量分别为1, 1, 2字节。为什么不是4,4, 4字节呢? 这是出于节约内存的考虑,显然只要能满足基准4字节对齐的要求就行,以尽量保持原有变量占据空间大小为原则,所以除了最后一个外的另外两个按照原本的内存大小进行计算,也就分别是1字节。最后一个char计算成2字节大小,正好凑齐了4字节的基准。说到这里应该都能看明白了吧,其他都是这样子来计算的,注意一下最后一个char,就是那个zz,它是以4字节来计算的,原因是之前所有变量总和是20字节了,多出来的一个字节必须以4字节基准对齐,所以就变成4字节了。结果就是24字节。
Type一般是一个结构体的类型,Mem是这个结构体其中的一员,这个代码求的是Mem这个成员距离结构体首位置的偏移。看下面的这段代码:
按照第一部分的内存空间计算方法,可以得出short占据4字节, int占据4字节, (char + short)占据4。一共12字节。整个结构体中int占据空间最大为4字节,所以以此作为基准,可以得出来(&((EXT *)0)->x)得到的是x距离EXT结构体首地址的偏移
可以看得出其是由(PCHAR)(address)与(ULONG_PTR)((&(type *)0)->field)两部分相减在强转成type类型的指针。
(PCHAR)(address) 这一部分好理解,就是一个地址,转成以一个字节对齐的PCHAR类型 (ULONG_PTR)((&(type *)0)->field) 首先把&(((type *)0)->field)拿出来,按照之前的理解,这个的用意是求field成员到Type类型结构体首地址的偏移。再把这个偏移转成ULONG_PTR类型(实际上就是ULONG类型)。field是type类型结构体的一员,address代表的就是field这个成员的地址,把field成员的地址减去field成员相对于type类型结构体的偏移不就是该结构体首地址嘛(完)