const AVClass *class;
char *expr_str;
AVExpr *expr;
double var_values[VAR_VARS_NB];
enum AVMediaType type;
} SetPTSContext;
#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 }
};
&((s *)0)->m 是要取得类s中成员变量m的地址
由于这个类(或结构)的基址为0,这时m的地址当然就是m在s中的偏移了
我们许多人可能都使用过一些非挥发性的存储器,如常见的EEPROM。我们经常使用它们在存储一些系统的配置参数和设备信息。在所有的EEPROM中,通过串口访问的占了大多数。一般来说,对串口的访问都是按字节进行的,这使得我们不可避免会设计出下面的
接口去访问EEPROM的信息:
ee_rd(uint16_t offset, uint16_t nBytes, uint8_t * dest);
定义一个数据结构和一个指向这个数据结构的指针,并初始化这个指针为EEPROM的起始地址EEPROM_BASE.
| i | f | c | | | |...
----------------------------
| | | | | | |...
----------------------------
| | | | | | |...
----------------------------
...
----------------------------
{
int i;
float f;
char c;
} EEPROM;
a.容易使代码维护人员人误以为在ee_rd接口内部也存在EEPROM的数据结构。
b.当你编写一些自己感觉良好编译器不报错的代码,比如pEE->f = 3.2,你可能意想不到灾难将要来临。
c.这个接口没有很好地体现EEPROM所隐含的硬件特性。
比如成员f在EEPROM数据结构中的偏移量,这里为什么
要强制转化0,这是个有深度的问题,在后面也会详细说明*/
#define offsetof(type, f) ((size_t) \
((char *)&((type *)0)->f - (char *)(type *)0))
{
int i;
float f;
char c;
} EEPROM;
#define SIZEOF(s,m) ((size_t) sizeof(((s *)0)->m))
Good question.其实我们可以通过下面的方法解决。
{
char pad[EEPROM_BASE];/*使数据结构的前EEPROM_BASE个字节填"空"*/
int i;
float f;
char c;
} EEPROM;
---------------------------- 0x00000000
| | | | | | |...
----------------------------
...
---------------------------- <-EPPROM_BASE:0x00000a10
| i | f | c | | | |...
----------------------------
| | | | | | |...
----------------------------
...
这种映射使原本复杂的寄存器访问变得象访问普通的RAM地址一样方便。
在我们视频会议系统中,PowerPC 8250访问外部的ROM控制器(ROM controller)的
寄 存器就是通过这种方式实现的。ROM控制器所有的寄存器被映射到从I/O寄存器空间基地址0x10000000(IO_BASE)偏移 0x60000(ROMCONOffset)字节的一段内存。每个寄存器占用四个字节,并有一个数据结构与它们对应。比如控制ROM控制器工作状态的寄存 器对应数据结构ROMCON_ROM_CONTROL,配置PCI总线A的寄存器对应数据结构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;
UINT32 ConfigA;
} nlstruct4;
} nlunion;
} ROMCON_CONFIG_A, *PROMCON_CONFIG_A;
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;
UINT32 RomControl;
} nlstruct4;
} nlunion;
} ROMCON_ROM_CONTROL, *PROMCON_ROM_CONTROL;
{
ROMCON_CONFIG_A ConfigA;
ROMCON_CONFIG_B ConfigB;
ROMCON_ROM_CONTROL RomControl;
...
}ROMCON, *PROMCON;
| | | | | | |...
----------------------------
| | | | | | |...
...
---------------------------- <-ROMCONOffset(ROMCON):0x60000
| | | | | | |...
---------------------------- <-ROMCON_ROM_CONTROL
...
----------------------------
估计有人可能会这样做:
事先定义成员RomControl(ROMCON中用ROMCON_ROM_CONTROL定义的实例)相对与ROMCON的偏移量,
void rom_read(dword_t* src, uint32_t* dest);
void rom_write(dword_t* src, uint32_t* dest);
if(!tRomCtrl.nlunion.nlstruct.VersaPortDisable)
{
tRomCtrl.nlunion.nlstruct.VersaPortDisable = 1;
}
这样做确实可以达到访问相应寄存器的目的。但是,如果和ROM相关的寄存器很多,那么定义、记忆和管理那么多偏移量不是很不方便吗?到这里,如果你对前面关于offsetof还有印象的话,我想你可能会作下面的优化:
#define ROMCON_ADDR(m) (((size_t)IO_BASE+\
(size_t)ROMCONOffset+\
(size_t)offsetof(ROMCON,m))
dword_t* pReg=(dword_t*)ROMCON_ADDR(ConfigA);
if(!tRomCtrl.nlunion.nlstruct.VersaPortDisable)
{
tRomCtrl.nlunion.nlstruct.VersaPortDisable = 1;
}
2.offsetof的来龙去脉
通过前面的举例,你可能对如何使用offsetof已经不陌生了吧。offsetof对那些搞
C++ 的人可能很熟悉,因为offsetof类似于sizeof,也是一种系统操作符,你不用考虑它是怎么定义的。这个操作符offsetof的定义可以在 ANSI C 编译器所带的stddef.h中找到。在嵌入式系统里,不同开发商,不同架构处理器和编译器都有不同的offsetof定义形式:
#define offsetof(s,m) (size_t)&(((s *)0)->m)
#define offsetof(s,m) (size_t)(unsigned long)&(((s *)0)->m)
#define offsetof(s,memb) ((size_t)((char *)&((s *)0)->memb-(char *)0))
#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)
((s *)0):强制转化成数据结构指针,并使其指向地址0;
((s *)0)->m:使该指针指向成员m
&(((s *)0)->m):获取该成员m的地址
(size_t)&(((s *)0)->m):转化这个地址为合适的类型
{
unsigned int a:3;
unsigned int b:13;
unsigned int c:16;
}foo;