曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 5946|回复: 0
打印 上一主题 下一主题

offsetof 宏

[复制链接]

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
跳转到指定楼层
楼主
发表于 2009-8-17 17:26:59 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
在 <stddef.h> 中定义了个 offsetof(s,m)宏,这个宏用来取得结构体中元素的偏移量很方便,下面是此宏的具体定义:
#define offsetof(s, m) (size_t)&(((s *)0)->m)

ofssetof(s, m) 其中,s 是结构体名,m 是它的一个成员。s 和 m 同是宏  offsetof() 的形参,这个宏返回的是结构体 s 的成员 m 在结构体中的偏移地址。

(s *)0  :  这里的用法实际上是欺骗了编译器,使编译器认为 "0" 就是一个指向 s 结构体的指针(地址),换句话说 s 结构体就是位于 0x0 这个地址处。

(s *)0-> m : 自然就是指向这个结构体的 m 元素。

&((s *)0)->m :  表示 m 元素的地址。这里,如上面所说,因为编译器认为结构体 s 被认为是处于 0x0 地址处,所以 m 的地址自然的就是 m 在 s 中的偏移地址了。

最后将这个偏移值转化为 size_t 类型。

可能会感到迷惑,这样强制转换后的结构指针怎么可以用来访问结构体字段?呵呵,其实这个表达式根本没有也不打算访问m字段。ANSIC标准允许任何值为0的常量被强制转换成任何一种类型的指针,并且转换结果是一个NULL指针,因此((s*)0)的结果就是一个类型为s*的NULL指针。如果利用这个NULL指针来访问s的成员当然是非法的,但&(((s*)0)->m)的意图并非想存取s字段内容,而仅仅是计算当结构体实例的首址为((s*)0)时m字段的地址。聪明的编译器根本就不生成访问m的代码,而仅仅是根据s的内存布局和结构体实例首址在编译期计算这个(常量)地址,这样就完全避免了通过NULL指针访问内存的问题。又因为首址的值为0,所以这个地址的值就是字段相对于结构体基址的偏移。

size_t是针对系统定制的一种数据类型。它不是固定位数,在不同的系统里这个值都有可能不同( 它实际上是 unsigned int 类型 );而且在内存里,对于数据是高位对齐存储还是低位对齐存储各系统都不一样。所以,为了提高代码的可移植性,就有必要定议这样的数据类型。一般这种类型都会定义到它具体占几位内存等。当然,有些是编译器或系统已经给定义好的。具体要查看技手册。

下面是自定义 offsetof() 宏的测试代码:
#include <stdio.h>

struct demo {
     char c;
     int  i;
     int  k;
     char u;
     char name[10];
};

int main()
{

     size_t pos;
     
     pos = (size_t)&(((struct demo *)0)->k);   
         

     printf("%d\n",pos);
     return 0;
}
运行及输出
beyes@linux-beyes:~/C/base> ./offsetof.exe
8
为什么是 8 而不是 char c (1) + int i (4) = 5 呢?这是因为计算机为了方便存储数据进行了数据对齐,把 char 类型也再另外填充了 3 个字节变成了 4 个字节。
参考:http://www.groad.net/bbs/read.php?tid=1037
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2024-5-14 03:17 , Processed in 0.062390 second(s), 22 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表