offsetof(s,m)解析「建议收藏」

offsetof(s,m)解析「建议收藏」使用实例:typedefstruct{constAVClass*class;char*expr_str;AVExpr*expr;doublevar_values[VAR_VARS

大家好,又见面了,我是你们的朋友全栈君。

使用实例:
typedef struct {

    const AVClass *class;

    char *expr_str;

    AVExpr *expr;

    double var_values[VAR_VARS_NB];

    enum AVMediaType type;

} SetPTSContext;
 
 
 
#define OFFSET(x) offsetof(SetPTSContext, x)

#define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_FILTERING_PARAM

static const AVOption options[] = {

    { “expr”, “Expression determining the frame timestamp”, OFFSET(expr_str), AV_OPT_TYPE_STRING, { .str = “PTS” }, .flags = FLAGS },

    { NULL }

};

 
 
http://blog.chinaunix.net/uid-13701930-id-336445.html
 
offsetof 求某个结构体的特定成员在结构体里面的偏移量
 
(s *)0 是骗编译器说有一个指向类(或结构)s的指针,其值为0   

&((s *)0)->m   是要取得类s中成员变量m的地址   

由于这个类(或结构)的基址为0,这时m的地址当然就是m在s中的偏移了
 
(s *)0 是把0地址转换为s指针类型,然后从这个指针上“取”m成员再取址,而m成员的地址转换后结果就是m成员相对于整个对象的偏移量(我们既然是从0地址开始算的,就不用再减去起始地址0)。
 
在嵌入式应用中,或许你对offsetof接触不多甚至根本没见过。如果是这样,那么从这一刻起就好好地掌握它,让它成为你的又一杀手锏吧。

1. offsetof与EEPROM

  我们许多人可能都使用过一些非挥发性的存储器,如常见的EEPROM。我们经常使用它们在存储一些系统的配置参数和设备信息。在所有的EEPROM中,通过串口访问的占了大多数。一般来说,对串口的访问都是按字节进行的,这使得我们不可避免会设计出下面的

接口去访问EEPROM的信息:
/*从EEPROM 偏移量offset处读取nBytes到RAM地址dest*/

ee_rd(uint16_t offset, uint16_t nBytes, uint8_t * dest);
然而,这种接口必须要知道偏移量offset和读取字节数nBytes。可能你会采用下面的方法解决方法解决这个问题:

定义一个数据结构和一个指向这个数据结构的指针,并初始化这个指针为EEPROM的起始地址EEPROM_BASE.
—————————-  <-EPPROM_BASE:0x0000000     

|
i |
f |
c |   |   |   |…

—————————-         

|   |   |   |   |   |   |…

—————————-         

|   |   |   |   |   |   |…

—————————-         



—————————-
#define EEPROM_BASE 0x0000000/*配置信息的起始地址*/
typedef struct

{   

     int   
i;  

     float 
f

     char  
c

} EEPROM;
EEPROM * const pEE = EEPROM_BASE
ee_rd(&(pEE->f), sizeof(pEE->f), dest);
没错,这种方法的确可以达到访问指定地址的信息。不过这种方法也存在下面的问题:

a.容易使代码维护人员人误以为在ee_rd接口内部也存在EEPROM的数据结构。

b.当你编写一些自己感觉良好编译器不报错的代码,比如pEE->f = 3.2,你可能意想不到灾难将要来临。

c.这个接口没有很好地体现EEPROM所隐含的硬件特性。
到这里,有人可能会想到offsetof(那些没用过甚至没见过的朋友别急,后面马上会详解offsetof)来解决这个问题:
/*offsetof获取数据成员在数据结构中的偏移量

比如成员f在EEPROM数据结构中的偏移量,这里为什么

要强制转化0,这是个有深度的问题,在后面也会详细说明*/

#define
offsetof(type, f) ((size_t) \

    ((char *)&((type *)0)->f – (char *)(type *)0))
typedef struct

{

     int    i; 

     float  f; 

     char   c; 

} EEPROM;
ee_rd(offsetof(EEPROM,f), 4, dest);
如果你能想到这里说明你对offsetof有一定程度的理解,不过还可以改进。如果让编译器来计算nBytes而不是我们自己给出4那就更好 了。这时,一定有人会马上提到sizeof。是的。可是怎么使用呢,我们不能用sizeof(EEPROM.f)来计算nBytes吧?!我想那些对 offsetof有较深理解的同志一定会这么办:
/*类似于offsetof的定义*/

#define SIZEOF(s,m) ((size_t) sizeof(((s *)0)->m))
ee_rd(offsetof(EEPROM, f), SIZEOF(EEPROM, f), &dest);
很不错! 其实还可以精简为下面的最终形式:
#define EE_RD(M,D)   ee_rd(offsetof(EEPROM,M), SIZEOF(EEPROM,M), D)
EE_RD(f, &dest);
哈哈,这样我们只用传递两个参数,不用再考虑应该从那里读取数据以及读取多少的问题。
先打住,有人会说这种简化都是建立在EEPROM_BASE为0x0000000基础之上的,可能会反问,如果配置信息不是从0地址开始的呢?

Good question.其实我们可以通过下面的方法解决。
#define EEPROM_BASE 0x00000a10
typedef struct

{

     char   pad[EEPROM_BASE];/*使数据结构的前EEPROM_BASE个字节填”空”*/ 

     int   
i

     float 
f

     char  
c

} EEPROM;

—————————-  0x00000000

|   |   |   |   |   |   |…

—————————-   



—————————- <-EPPROM_BASE:0x00000a10             

|
i |
f |
c |   |   |   |…

—————————-         

|   |   |   |   |   |   |…

—————————-         

使用offsetof简化EEPROM的串口访问的确很妙。这里还有一个很好的例子。
在嵌入式应用中,
我们时常将一些I/O寄存器映射到内存地址空间进行访问。
这种映射使原本复杂的寄存器访问变得象访问普通的RAM地址一样方便。


在我们视频会议系统中,PowerPC 8250访问外部的ROM控制器(ROM controller)的

寄 存器就是通过这种方式实现的。ROM控制器所有的寄存器被映射到从I/O寄存器空间基地址0x10000000(IO_BASE)偏移 0x60000(ROMCONOffset)字节的一段内存。每个寄存器占用四个字节,并有一个数据结构与它们对应。比如控制ROM控制器工作状态的寄存 器对应数据结构
ROMCON_ROM_CONTROL,配置PCI总线A的寄存器对应数据结构
ROMCON_CONFIG_A,下面先看看这些数据结构的定义:
#define IO_BASE      0x10000000
#define ROMCONOffset 0x60000
typedef unsigned int NW_UINT32;
typedef struct _ROMCON_CONFIG_A {

    union {

        struct {

            UINT32 pad4:21;         /* unused   */

            UINT32 pad3:2;          /* reserved */

            UINT32 pad2:5;          /* unused   */

            UINT32 EnablePCIA:1;

            UINT32 pad1:1;          /* reserved */

            UINT32 EnableBoot:1;         

            UINT32 EnableCpu:1;     /*bit to enable cpu*/

        } nlstruct;
        struct {

            UINT32 ConfigA;

        } nlstruct4;

    } nlunion;

}
ROMCON_CONFIG_A, *PROMCON_CONFIG_A;
typedef struct _ROMCON_ROM_CONTROL {

    union {

        struct {

            UINT32 TransferComplete:1;

            UINT32 pad3:1;            /* unused */

            UINT32 BondPad3To2:2;

            UINT32 Advance:3;

            UINT32
VersaPortDisable:1;

            UINT32 pad2:1;            /* unused */

            UINT32 FastClks:1;

            UINT32 pad1:7;            /* unused */

            UINT32 CsToFinClks:2;

            UINT32 OeToCsClks:2;

            UINT32 DataToOeClks:2;

            UINT32 OeToDataClks:3;

            UINT32 CsToOeClks:2;

            UINT32 AddrToCsClks:2;         

            UINT32 AleWidth:2;

        }
nlstruct;
        struct {

            UINT32 RomControl;

        }
nlstruct4;

    } nlunion;

}
ROMCON_ROM_CONTROL, *PROMCON_ROM_CONTROL;
typedef struct

{

    ROMCON_CONFIG_A     ConfigA;

    ROMCON_CONFIG_B     ConfigB;

    ROMCON_ROM_CONTROL  RomControl;

    …

}
ROMCON, *PROMCON;
—————————-  <-IO_BASE:0x10000000    

|   |   |   |   |   |   |…

—————————-         

|   |   |   |   |   |   |…



—————————-  <-ROMCONOffset(ROMCON):0x60000      

|   |   |   |   |   |   |…

—————————-  <-ROMCON_ROM_CONTROL             



—————————-
那么如何访问ROMCON_ROM_CONTROL对应寄存器呢,比如ROMCON_ROM_CONTROL对应寄存器的
VersaPortDisable位?

估计有人可能会这样做:

事先定义成员RomControl(ROMCON中用ROMCON_ROM_CONTROL定义的实例)相对与ROMCON的偏移量,
#define ROMCONRomControlOffset 0x8
然后设计访问ROM的接口如下:
/*读取ROM控制器位于src位置的寄存器数据到dest*/
typedef unsigned long
dword_t;

void rom_read(dword_t* src, uint32_t* dest);

void rom_write(dword_t* src, uint32_t* dest);
最后利用这个偏移量做下面的操作:
ROMCON_ROM_CONTROL tRomCtrl={0};
dword_t* pReg=(dword_t*)(IO_BASE+ROMCONOffset+\
     ROMCONRomControlOffset);
rom_read(pReg,(uint32_t)*(&tRomCtrl));
/*查看寄存器的
VersaPortDisable位,如果该位没有启用就启用它*/

if(!tRomCtrl.nlunion.nlstruct.VersaPortDisable)

{

  tRomCtrl.nlunion.nlstruct.VersaPortDisable = 1;
  rom_write(pReg,(uint32_t)*(&tRomCtrl));

}

 

这样做确实可以达到访问相应寄存器的目的。但是,如果和ROM相关的寄存器很多,那么定义、记忆和管理那么多偏移量不是很不方便吗?到这里,如果你对前面关于offsetof还有印象的话,我想你可能会作下面的优化:

#define
ROMCON_ADDR(m)   (((size_t)IO_BASE+\

                         (size_t)ROMCONOffset+\

                         (size_t)
offsetof(ROMCON,m))
ROMCON_ROM_CONTROL tRomCtrl={0};

dword_t* pReg=(dword_t*)
ROMCON_ADDR(ConfigA);
rom_read(pReg,(uint32_t)*(&tRomCtrl));
/*查看寄存器的
VersaPortDisable位,如果没有启动就启动它*/

if(!tRomCtrl.nlunion.nlstruct.
VersaPortDisable)

{

  tRomCtrl.nlunion.nlstruct.VersaPortDisable = 1;
  rom_write(pReg,(uint32_t)*(&tRomCtrl));

}

2.offsetof的来龙去脉

  通过前面的举例,你可能对如何使用offsetof已经不陌生了吧。offsetof对那些搞

C++ 的人可能很熟悉,因为offsetof类似于sizeof,也是一种系统操作符,你不用考虑它是怎么定义的。这个操作符offsetof的定义可以在 ANSI C 编译器所带的stddef.h中找到。在嵌入式系统里,不同开发商,不同架构处理器和编译器都有不同的offsetof定义形式:
/* Keil 8051 */

#define offsetof(s,m) (size_t)&(((s *)0)->m)
/* Microsoft x86 */

#define offsetof(s,m) (size_t)(unsigned long)&(((s *)0)->m)
/* Motorola coldfire */

#define offsetof(s,memb) ((size_t)((char *)&((s *)0)->memb-(char *)0))
/* GNU GCC 4.0.2 */

#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)
虽然定义形式不同,但功能都是返回成员在数据结构中的偏移量,都是为了提高代码的可移植性。
下面拿KEIL 8051的定义来作点解释:

((s *)0):强制转化成数据结构指针,并使其指向地址0;

((s *)0)->m:使该指针指向成员m

&(((s *)0)->m):获取该成员m的地址

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

{

  unsigned int a:3;

  unsigned int b:13;

  unsigned int c:16;

}foo;
使用offset(foo,a)计算a在foo中的偏移量,编译器会报错。
 
 
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/155355.html原文链接:https://javaforall.net

(0)
上一篇 2022年7月2日 上午10:36
下一篇 2022年7月2日 上午10:46


相关推荐

  • 常见大数据框架

    常见大数据框架文件存储 HadoopHDFS Tachyon KFS 离线计算 HadoopMapRed Spark 流式 实时计算 Storm SparkStreami S4 HeronK V NOSQL 数据库 HBase Redis MongoDB 资源管理 YARN Mesos 日志收集 Flume Scribe Logstash Kibana 消息系统 Kafka StormMQ ZeroMQ R

    2026年3月18日
    3
  • c程序中整形变量只能存放整数实型变量只能存放浮点数_c语言合法的实型常量

    c程序中整形变量只能存放整数实型变量只能存放浮点数_c语言合法的实型常量vb中,以下变量类型1,数字型变量(numeric)2,字符串型变量(string)3,日期型变量(date)4,对象型变量(object)5,变体型变量(variant)这几个vb变量类型中,最最主要的就是前面两个,数字型变量和字符串型变量.意思很简单,数字型可以用来存放数字,字符串型存放文本.下面就来详细介绍这几种变量.1.数字型数字型变量有多种类型,在咱们的vb里,有3中数字数据类型1;整形…

    2025年7月24日
    5
  • DeepSeek,突传大消息!高盛发声!

    DeepSeek,突传大消息!高盛发声!

    2026年3月13日
    2
  • nginx 配置ssl

    nginx 配置sslnginx配置ssl

    2022年7月14日
    25
  • win10中使用sqlserver2008r2 SQL Server 配置管理器[通俗易懂]

    win10中使用sqlserver2008r2 SQL Server 配置管理器[通俗易懂]win10打开sqlserver2008r2的SQLServer配置管理器,直接运行次文件就可:“C:\Windows\SysWOW64\SQLServerManager10.msc”在win10中是安装sqlserver2008r2,有时候安装成功之后会发现sqlserver的1433端口访问不了,通过sql语句查看端口号,发现找不到1433端口号–查询端口号

    2022年7月20日
    17
  • jmap的使用以及内存溢出分析

    jmap的使用以及内存溢出分析jmap 的使用以及内存溢出分析 jmap java 内存映像工具 jmap MemoryMapfor 命令用于生成堆转储快照 一般称为 heapdump 或 dump 文件 还有几种方式获取 dump 文件 使用 JVM 参数选项 XX HeapDumpOnOu 参数 可以让虚拟机在 OOM 异常出现之后自动生成 dump 文件 通过 XX HeapDumpPath path 设置 dump 文件路径 有时候 dump 文件比较大的时候可能无法自动导出 这时候就需要使用 jmap dump 手动导

    2025年12月6日
    5

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号