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中的偏移量,编译器会报错。
 
 
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • SSDP协议_Smb协议

    SSDP协议_Smb协议SSDP就是简单服务发现协议(SimpleServiceDiscoveryProtocol)是一种应用层协议,它是构成通用即插即用(也就是UPnP,UPnP是各种各样的智能设备、无线设备和个人电脑等实现遍布全球的对等网络连接的结构)技术的核心协议之一。    简单服务发现协议提供了在局部网络里面发现设备的机制。控制点(也就是接受服务的客户端)能够直接通过使用简单服务发现协议,根据自己的需要查询…

    2022年10月11日
  • VS配置PCL“无法解析外部符号”

    VS配置PCL“无法解析外部符号”一开始报错:一般原因是没有包括需要的.lib报错说明可能出现在vtk和pcl_visualization的lib上。在依赖库中添加pcl_visualization.lib或者在.cmake文件中添加visualization重新编译,如下:===========================================================之后,报错只有两条:可知,现在只缺少vtk相关的lib。本人没有找到vtkLODActor和vtkShpe…

  • CTF——流量分析题型整理总结

    CTF——流量分析题型整理总结我见过的流量分析类型的题目总结:一,ping报文信息(icmp协议)二,上传/下载文件(蓝牙obex,http,难:文件的分段上传/下载)三,sql注入攻击四,访问特定的加密解密网站(md5,base64)五,后台扫描+弱密码爆破+菜刀六,usb流量分析七,WiFi无线密码破解八,根据一组流量包了解黑客的具体行为例题:一,ping报文信息(icm…

  • springboot上传文件到文件夹

    springboot上传文件到文件夹springboot上传文件至项目当前路径下的文件夹关键代码,之后会分享完整代码到gitee默认上传文件到文件夹/***默认上传文件到文件夹**@paramfolder默认文件夹*@paramfile上传的文件*@return*/privateStringmyfileUp(Stri…

  • C++——STL中三种顺序容器的简要差别「建议收藏」

    C++——STL中三种顺序容器的简要差别

  • android游戏开发引擎_android主题引擎

    android游戏开发引擎_android主题引擎随着Android系统的使用越来越广泛,了解一下Android平台下的游戏引擎就非常有必要。而同时因为基于Intelx86的移动设备越来越多,我也非常关注支持x86的移动游戏引擎。然而就目前为止游戏引擎的数量已经非常之多,每个引擎都有不同的特征、价格、成熟度等。通过一些调研之后,我发现有非常多的游戏引擎可用于开发运行在android移动设备端的游戏,其中有些还支持x86系统,另外还有些通过简单的

    2022年10月22日

发表回复

您的电子邮箱地址不会被公开。

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