结构体的sizeof 问题以及如何求每个元素的偏移量

struct S1
{
char c;
int i;
};
问sizeof(s1)等于多少聪明的你开始思考了,char占1个字节,int占4个字节,那么加起来就应该是5。是这样吗你在你机器上试过了吗也许你是对的,但很可能你是错的!VC6中按默认设置得到的结果为8。

Why为什么受伤的总是我
请不要沮丧,我们来好好琢磨一下sizeof的定义——sizeof的结果等于对象或者类型所占的内存字节数,好吧,那就让我们来看看S1的内存分配情况:
S1 s1 = { 'a', 0xFFFFFFFF };
定义上面的变量后,加上断点,运行程序,观察s1所在的内存,你发现了什么
以我的VC6.0为例,s1的地址为0x0012FF78,其数据内容如下:
0012FF78: 61 CC CC CC FF FF FF FF

发现了什么怎么中间夹杂了3个字节的CC看看MSDN上的说明:
When applied to a structure type or variable, sizeof returns the actual size, which may include padding bytes inserted for alignment.
原来如此,这就是传说中的字节对齐啊!一个重要的话题出现了。
为什么需要字节对齐计算机组成原理教导我们这样有助于加快计算机的取数速度,否则就得多花指令周期了。为此,编译器默认会对结构体进行处理(实际上其它地方的数据变量也是如此),让宽度为2的基本数据类型(short等)都位于能被2整除的地址上,让宽度为4的基本数据类型(int等)都位于能被 4整除的地址上,以此类推。这样,两个数中间就可能需要加入填充字节,所以整个结构体的sizeof值就增长了。
让我们交换一下S1中char与int的位置:
struct S2
{
int i;
char c;
};
看看sizeof(S2)的结果为多少,怎么还是8再看看内存,原来成员c后面仍然有3个填充字节,这又是为什么啊别着急,下面总结规律。

字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。

对于上面的准则,有几点需要说明:
1) 前面不是说结构体成员的地址是其大小的整数倍,怎么又说到偏移量了呢因为有了第1点存在,所以我们就可以只考虑成员的偏移量,这样思考起来简单。想想为什么。

结构体某个成员相对于结构体首地址的偏移量可以通过宏offsetof()来获得,这个宏也在stddef.h中定义,如下:
#define offsetof(s,m) (size_t)&(((s *)0)->m)
例如,想要获得S2中c的偏移量,方法为
size_t pos = offsetof(S2, c);// pos等于4

2) 基本类型是指前面提到的像char、short、int、float、double这样的内置数据类型,这里所说的“数据宽度”就是指其sizeof的大小。由于结构体的成员可以是复合类型,比如另外一个结构体,所以在寻找最宽基本类型成员时,应当包括复合类型成员的子成员,而不是把复合成员看成是一个整体。但在确定复合类型成员的偏移位置时则是将复合类型作为整体看待。
这里叙述起来有点拗口,思考起来也有点挠头,还是让我们看看例子吧(具体数值仍以VC6为例,以后不再说明):
struct S3
{
char c1;
S1 s;
char c2;
};
S1的最宽简单成员的类型为int,S3在考虑最宽简单类型成员时是将S1“打散”看的,所以S3的最宽简单类型为int,这样,通过S3定义的变量,其存储空间首地址需要被4整除,整个sizeof(S3)的值也应该被4整除。
c1的偏移量为0,s的偏移量呢这时s是一个整体,它作为结构体变量也满足前面三个准则,所以其大小为8,偏移量为4,c1与s之间便需要3个填充字节,而c2与s之间就不需要了,所以c2的偏移量为12,算上c2的大小为13,13是不能被4整除的,这样末尾还得补上3个填充字节。最后得到 sizeof(S3)的值为16。

通过上面的叙述,我们可以得到一个公式:
结构体的大小等于最后一个成员的偏移量加上其大小再加上末尾的填充字节数目,即:

sizeof( struct ) = offsetof( last item ) + sizeof( last item ) + sizeof( trailing padding )

到这里,朋友们应该对结构体的sizeof有了一个全新的认识,但不要高兴得太早,有一个影响sizeof的重要参量还未被提及,那便是编译器的 pack指令。它是用来调整结构体对齐方式的,不同编译器名称和用法略有不同,VC6中通过#pragma pack实现,也可以直接修改/Zp编译开关。#pragma pack的基本用法为:#pragma pack( n ),n为字节对齐数,其取值为1、2、4、8、16,默认是8,如果这个值比结构体成员的sizeof值小,那么
该成员的偏移量应该以此值为准,即是说,结构体成员的偏移量应该取二者的最小值,
公式如下:
offsetof( item ) = min( n, sizeof( item ) )
再看示例:
#pragma pack(push) // 将当前pack设置压栈保存
#pragma pack(2) // 必须在结构体定义之前使用
struct S1
{
char c;
int i;
};
struct S3
{
char c1;
S1 s;
char c2;
};
#pragma pack(pop) // 恢复先前的pack设置
计算sizeof(S1)时,min(2, sizeof(i))的值为2,所以i的偏移量为2,加上sizeof(i)等于6,能够被2整除,所以整个S1的大小为6。
同样,对于sizeof(S3),s的偏移量为2,c2的偏移量为8,加上sizeof(c2)等于9,不能被2整除,添加一个填充字节,所以sizeof(S3)等于10。

现在,朋友们可以轻松的出一口气了,:)
还有一点要注意,“空结构体”(不含数据成员)的大小不为0,而是1。试想一个“不占空间”的变量如何被取地址、两个不同的“空结构体”变量又如何得以区分呢于是,“空结构体”变量也得被存储,这样编译器也就只能为其分配一个字节的空间用于占位了。如下:
struct S5 { };
sizeof( S5 ); // 结果为1

 

 

附:(struct A*)0

 

这个一般是用来求 struct A 结构体成员的偏移量的, 0做为空指针转换成(struct A*)类型

比如: struct A{ int i; char c; int j; }; 如果要得到里面j在结构中的位置可以这样: int position =(unsigned int) &((A*)0)->j; 这样可以省不少事情






http://bbs.51cto.com/thread-804402-1.html

一个获取结构体元素偏移量的宏(系统有定义好的ofsetof(t, d))

今天审查代码,发现一个获取结构体元素偏移量的宏挺有创意,分享给大家,给初学者写了两行示例代码,C语言开发中对内存的操作非常频繁,等大家学几个月C语言就会发现,C语言的数据结构,算法,逻辑,大部分是对内存进行操作。
宏:
#define offset(type, member) (unsigned int)(&(((type *)0)->member))

例子:

 

#include <stdio.h>

#define offset(type, member) (unsigned int)(&(((type *)0)->member))

int main(void)
{
unsigned int ofst;

typedef struct pc_s{
int number;
char name[10];
char owner[10];
} pc_t;

ofst = offset(pc_t, number);
printf("ofst = %d\n",ofst); /* 输出0*/

ofst = offset(pc_t, name);
printf("ofst = %d\n",ofst); /* 输出一个int类型数据内存长度的字节数 */

ofst = offset(pc_t, owner);
printf("ofst = %d\n",ofst); /* 输出一个int类型数据加10个字符型数据内存长度的字节数 */

return 0;
}



 

  方法:调用宏offsetof(struct_name,struct_element),

返回值就是偏移量,跟踪发现:
         #define offsetof(struct_name,struct_element) (size_t)&(((struct_name *)0)->struct_element)。
可以看出来它先是将整形的0强制类型转换为结构体指针,但是值还是0不变,但表示的是结构体首地址为0,那么((struct_name *)0)->struct_element

访问的就是元素的值了,我们在它前面加上取地址符&,这样得到的就是它的地址了,然后把地址强制转换为unsigned int型即可了。

如果将0变成1或者其他数呢?只要再减去这个数就行了:(unsigned int)&(((struct_name *)1)->struct_element)-1。

 

 

 
 

 来自:

零指针和结构体偏移量
2011-07-20 11:12
 

定义一个结构体

struct A
{
...

int a;
char b;
short c;

...

}

其中已知int a=100;

取得a的地址 int* address=&a

把0强制转换为A类型的指针(A*)0,则得到一个理论上的以基址0开始的A结构体,

则可以通过&(((A*)0)->a)的到这个假想结构体的a的地址,由于基址为0,也就是

得到了a元素在结构体中的偏移量,结合之前取得的a的地址int* address;

address-&(((A*)0)->a),即得到该结构体的基址。强制转换为A类型即可,如下:

(A*)(address-&(((A*)0)->a)),

----------------------------------------------------------------------------------------------------

DDK中有这样一个宏:

#define CONTAINING_RECORD(address,type,field) ((type*)((PCHAR)(address)-(ULONG_PTR)(&((type*)0)->field)))

就是根据这个原理来获取结构实例基地址的。

CONTAINING_RECORD 宏返回一个结构实例的基地址,该结构的类型和结构所包含的一个域(成员)地址已知。

PCHAR CONTAINING_RECORD(
IN PCHAR Address,
IN TYPE Type,
IN PCHAR Field
);

Parameters

参数

Address
Pointer to a field in an instance of a structure of type Type.
指向Type类型结构实例中某域(成员)的指针。
Type
The name of the type of the structure whose base address is to be returned. For example, type IRP.
需要得到基地址的结构实例的结构类型名。
Field
The name of the field pointed to by Address and which is contained in a structure of type Type.
Type类型结构包含的域(成员)的名称。
Return Value

返回值

Returns the address of the base of the structure containing Field.

返回包含Field域(成员)的结构体的基地址

转载于:https://www.cnblogs.com/pengyingh/articles/2436318.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值