ANSI C中取得结构体字段偏移量的常用方法

来自http://blog.chinaunix.net/u2/62910/showart_492571.html

假设在ANSI C程序中定义了一个名为MyStruct的结构类型,其中有一个名为MyField的字段,如何取得它在结构体中的偏移?

typedef struct MyStruct_tag
{
  // some fields
  ...

long MyField;

// other fields
  ...
} MyStruct;

最容易想到的方法应该与如下代码差不多:

size_t GetOffset()
{
  MyStruct s;

return (size_t)( (char*)(&s.MyField) - (char*)(&s) );
}

这段代码确实能完成任务,但为了取得偏移值,函数不得不定义了一个MyStruct结构体实例,可这有必要吗?仔细想想,结构体的内存布局是在什么时候由谁决定的?没错,是编译器在编译期确定的,它一旦被确定就不会改变了,而依赖于结构体内存布局的字段偏移也就随之确定并不再改变。既然在编译阶段编译器就洞悉了内幕,那么完全有理由要求它在编译期为程序提供这些信息。如何做呢?请看下面的代码:

#define MY_OFFSET (size_t)&(((MyStruct*)0)->MyField)

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

如上做法避免了一定要实例化一个MyStruct对象,并且求值是在编译期进行,没有运行期负担。实际上这种利用编译器掌握的整个程序的信息以在编译期计算某些值的方法与现在C++编程中很流行的(静态)元编程技术类似,只不过C++程序员可以利用模板技术在编译期完成非常复杂的计算,而缺乏模板支持的ANSI C在这方面的能力则要弱许多。

或许因为求结构体字段偏移很常用,ANSI C在标准头文件stddef.h中就专门定义了一个形如offsetof(s,m)的宏来求任意一个结构类型中某个字段的偏移,而且绝大多数C开发系统的实现都采用了上述的方法,例如:

// VC7.1
#ifdef  _WIN64
#define offsetof(s,m)   (size_t)( (ptrdiff_t)&(((s *)0)->m) )
#else
#define offsetof(s,m)   (size_t)&(((s *)0)->m)
#endif

//lcc-win32, last updated:Monday, 13-Dec-2004 04:05:23 EST
#define offsetof(s,m) (int)&(((s *)0)->m)

//Borland C++ 5.5.1 for WIN32
#define offsetof( s_name, m_name )  (_SIZE_T)&(((s_name _FAR *)0)->m_name)

//MinGW 3.1.0 (GCC 3.2.3)
#ifndef __cplusplus
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#else /* C++ */
/* The reference cast is necessary to thwart an operator& that might
   be applicable to MEMBER´s type.  See DR 273 for details.  */
#define offsetof(TYPE, MEMBER) (reinterpret_cast <size_t> /
    (&reinterpret_cast <char &>(static_cast <TYPE *> (0)->MEMBER)))
#endif /* C++ */

可见这种简练而有效的方法已被C程序员接纳为一种惯用法(idiom)了。

时间: 2024-10-12 10:43:52

ANSI C中取得结构体字段偏移量的常用方法的相关文章

关于c语言中的结构体使用偏移量求值问题

最近在看nginx源码,看到定时器的时候,发现一个结构体利用偏移量求值问题, 结构体相信做c开发的都遇到过,那么不知你对结构体中成员变量偏移这块是如何理解的; 首先我们先看一下nginx中的那个让我迷惑的地方 ev =    (event_t*)((char*)node - offsetof(event_t, timer)); 这里,可以得知道是利用event_t结构体的timer变量,来反求event_t结构体的地址 说明一下: event_t是一个结构体 node 也是一个结构体 timer

task_struct结构体字段介绍--Linux中的PCB

task_struct结构体 字段介绍 Linux内核通过一个被称为进程描述符的task_struct结构体来管理进程, task_struct是Linux中的[进程控制块PCB结构]的具体数据结构 这个结构体包含了一个进程所需的所有信息.它定义在linux-2.6.38.8/include/linux/sched.h文件中. 下面对task_struct这个结构体 进行各个字段的详细介绍 1. 调度数据成员(1) volatile long states;表示进程的当前状态:? TASK_RU

指针直接赋值为整型AND利用宏定义求结构体成员偏移量

首先我们要更正一个很熟悉的概念,那就是指针不仅仅是“地址”,指针还有一个很重要的特性,那就是“类型”. 指针初始化时,“=”的右操作数必须为内存中数据的地址,不可以是变量,也不可以直接用整型地址值(但是 int *p = 0; 除外,该语句表示指针为空): 所以 int *p = 10; 这样的代码是不允许的.在C++里面直接是error的,即使在一些C编译器中以warning的形式提示,但是warning有的时候也很严重.所以这种东西不要用.从const int到int*是不存在隐士转换的.

[转]C#中的结构体与类的区别

C#中的结构体与类的区别 经常听到有朋友在讨论C#中的结构与类有什么区别.正好这几日闲来无事,自己总结一下,希望大家指点. 1. 首先是语法定义上的区别啦,这个就不用多说了.定义类使用关键字class 定义结构使用关键字struct.在语法上其实类和结构有着很多相似的地方. 定义类的语法 1 class Person 2 { 3 private string name; 4 private int age; 5 6 public void SayHi() 7 { 8 Console.WriteL

浅析C#中的结构体和类

类和结构是 .NET Framework 中的常规类型系统的两种基本构造. 两者在本质上都属于数据结构.封装着一组总体作为一个逻辑单位的数据和行为. 数据和行为是该类或结构的"成员",它们包括各自的方法.属性和事件等 对于C/C++程序员来说.结构体和类的差别非常小.仅仅是结构体的默认成员变量为public,类的默认成员变量为private. 可是对于C#来说,结构体和类有非常多的不同. 首先来谈一谈为何须要结构体: 最主要的原因就是结构体有能力去管理.使用不同数据类型的组合. .NE

x264中重要结构体参数解释,参数设置,函数说明 &lt;转&gt;

x264中重要结构体参数解释http://www.usr.cc/thread-51995-1-3.htmlx264参数设置http://www.usr.cc/thread-51996-1-3.html x264中重要结构体参数解释typedef struct x264_param_t{/* CPU 标志位 */unsigned int cpu;int         i_threads;       /* 并行编码多帧 */int         b_deterministic; /*是否允许非

现代 C++ 编译时 结构体字段反射

基于 C++ 14 原生语法,不到 100 行代码:让编译器帮你写 JSON 序列化/反序列化代码,告别体力劳动.?? 本文不讨论完整的 C++ 反射技术,只讨论结构体 (struct) 的字段 (field) 反射,及其在序列化/反序列化代码生成上的应用. 正文开始于 [sec|静态反射] 部分,其他部分都是铺垫..可以略读... 背景(TL;DR) 很多人喜欢把程序员称为 码农,程序员也经常嘲讽自己每天都在 搬砖.这时候,大家会想:能否构造出一些 更好的工具,代替我们做那些无意义的 体力劳动

C语言中的结构体和C++中的结构体以及C++中类的区别

c++中结构体可以定义一个函数 C中的结构体和C++中结构体的不同之处:在C中的结构体只能自定义数据类型,结构体中不允许有函数,而C++中的结构体可以加入成员函数. C++中的结构体和类的异同: 一.相同之处:结构体中可以包含函数:也可以定义public.private.protected数据成员:定义了结构体之后,可以用结构体名来创建对象.但C中的结构体不允许有函数:也就是说在C++当中,结构体中可以有成员变量,可以有成员函数,可以从别的类继承,也可以被别的类继承,可以有虚函数. 二.不同之处

C语言中的结构体,结构体数组

C语言中的结构体是一个小难点,下面我们详细来讲一下:至于什么是结构体,结构体为什么会产生,我就不说了,原因很简单,但是要注意到是结构体也是连续存储的,但要注意的是结构体里面类型各异,所以必然会产生内存对齐的问题.也就是内存里面会有空档. 1.结构体的定义和赋值 结构体是可以直接初始化的,在定义的时候,就可以初始化,而且如果你的结构体中恰好有字符数组的话,这个时候初始化是不错的选择,原因很简单,字符数组只能定义的时候直接初始化 后来就不可以了,后来你就只能用strcpy函数来拷贝初始化了. str