|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
不管是学习Objective-C语言,还是学习ios,我都只是跟着书上的例子,在苹果机上,在Xcode和InterfaceBuilder开发环境中,按部就班的“抄”些应用程序。在嵌进式使用中,也许你对offsetof打仗未几乃至基本没见过。假如是如许,那末从这一刻起就好好地把握它,让它成为你的又一杀手锏吧。1.offsetof与EEPROM
我们很多人大概都利用过一些非挥发性的存储器,如罕见的EEPROM。我们常常利用它们在存储一些体系的设置参数和设备信息。在一切的EEPROM中,经由过程串口会见的占了年夜多半。一样平常来讲,对串口的会见都是按字节举行的,这使得我们不成制止会计划出上面的
接口往会见EEPROM的信息:/*从EEPROM偏移量offset处读取nBytes到RAM地点dest*/
ee_rd(uint16_toffset,uint16_tnBytes,uint8_t*dest);但是,这类接口必需要晓得偏移量offset和读取字节数nBytes。大概你会接纳上面的办法办理办法办理这个成绩:
界说一个数据布局和一个指向这个数据布局的指针,并初始化这个指针为EEPROM的肇端地点EEPROM_BASE.----------------------------<-EPPROM_BASE:0x0000000
|i|f|c||||...
----------------------------
|||||||...
----------------------------
|||||||...
----------------------------
...
----------------------------#defineEEPROM_BASE0x0000000/*设置信息的肇端地点*/typedefstruct
{
inti;
floatf;
charc;
}EEPROM;EEPROM*constpEE=EEPROM_BASEee_rd(&(pEE->f),sizeof(pEE->f),dest);没错,这类办法切实其实能够到达会见指定地点的信息。不外这类办法也存鄙人面的成绩:
a.简单使代码保护职员人误觉得在ee_rd接口外部也存在EEPROM的数据布局。
b.当你编写一些本人感到优秀编译器不报错的代码,好比pEE->f=3.2,你大概意想不到劫难将要光降。
c.这个接口没有很好地表现EEPROM所隐含的硬件特征。到这里,有人大概会想到offsetof(那些没用过头至没见过的伴侣别急,前面即刻会详解offsetof)来办理这个成绩:/*offsetof猎取数据成员在数据布局中的偏移量
好比成员f在EEPROM数据布局中的偏移量,这里为何
要强迫转化0,这是个有深度的成绩,在前面也会具体申明*/
#defineoffsetof(type,f)((size_t)
((char*)&((type*)0)->f-(char*)(type*)0))typedefstruct
{
inti;
floatf;
charc;
}EEPROM;ee_rd(offsetof(EEPROM,f),4,dest);假如你能想到这里申明你对offsetof有必定水平的了解,不外还能够改善。假如让编译器来盘算nBytes而不是我们本人给出4那就更好了。这时候,必定有人会即刻提到sizeof。是的。但是怎样利用呢,我们不克不及用sizeof(EEPROM.f)来盘算nBytes吧?!我想那些对offsetof有较深了解的同道必定会这么办:/*相似于offsetof的界说*/
#defineSIZEOF(s,m)((size_t)sizeof(((s*)0)->m))ee_rd(offsetof(EEPROM,f),SIZEOF(EEPROM,f),&dest);很不错!实在还能够精简为上面的终极情势:#defineEE_RD(M,D)ee_rd(offsetof(EEPROM,M),SIZEOF(EEPROM,M),D)EE_RD(f,&dest);哈哈,如许我们只用传送两个参数,不必再思索应当从那边读取数据和读取几的成绩。先打住,有人会说这类简化都是创建在EEPROM_BASE为0x0000000基本之上的,大概会反问,假如设置信息不是从0地点入手下手的呢?
Goodquestion.实在我们能够经由过程上面的办法办理。#defineEEPROM_BASE0x00000a10typedefstruct
{
charpad[EEPROM_BASE];/*使数据布局的前EEPROM_BASE个字节填"空"*/
inti;
floatf;
charc;
}EEPROM;
----------------------------0x00000000
|||||||...
----------------------------
...
----------------------------<-EPPROM_BASE:0x00000a10
|i|f|c||||...
----------------------------
|||||||...
----------------------------
...利用offsetof简化EEPROM的串口会见切实其实很妙。这里另有一个很好的例子。在嵌进式使用中,我们经常将一些I/O存放器映照到内存地点空间举行会见。
这类映照使底本庞大的存放器会见变得象会见一般的RAM地点一样便利。
在我们视频集会体系中,PowerPC8250会见内部的ROM把持器(ROMcontroller)的
存放器就是经由过程这类体例完成的。ROM把持器一切的存放器被映照到从I/O存放器空间基地点0x10000000(IO_BASE)偏移0x60000(ROMCONOffset)字节的一段内存。每一个存放器占用四个字节,并有一个数据布局与它们对应。好比把持ROM把持器事情形态的存放器对应数据布局ROMCON_ROM_CONTROL,设置PCI总线A的存放器对应数据布局ROMCON_CONFIG_A,上面先看看这些数据布局的界说:#defineIO_BASE0x10000000#defineROMCONOffset0x60000typedefunsignedintNW_UINT32;typedefstruct_ROMCON_CONFIG_A{
union{
struct{
UINT32pad4:21;/*unused*/
UINT32pad3:2;/*reserved*/
UINT32pad2:5;/*unused*/
UINT32EnablePCIA:1;
UINT32pad1:1;/*reserved*/
UINT32EnableBoot:1;
UINT32EnableCpu:1;/*bittoenablecpu*/
}nlstruct;struct{
UINT32ConfigA;
}nlstruct4;
}nlunion;
}ROMCON_CONFIG_A,*PROMCON_CONFIG_A;typedefstruct_ROMCON_ROM_CONTROL{
union{
struct{
UINT32TransferComplete:1;
UINT32pad3:1;/*unused*/
UINT32BondPad3To2:2;
UINT32Advance:3;
UINT32VersaPortDisable:1;
UINT32pad2:1;/*unused*/
UINT32FastClks:1;
UINT32pad1:7;/*unused*/
UINT32CsToFinClks:2;
UINT32OeToCsClks:2;
UINT32DataToOeClks:2;
UINT32OeToDataClks:3;
UINT32CsToOeClks:2;
UINT32AddrToCsClks:2;
UINT32AleWidth:2;
}nlstruct;struct{
UINT32RomControl;
}nlstruct4;
}nlunion;
}ROMCON_ROM_CONTROL,*PROMCON_ROM_CONTROL;typedefstruct
{
ROMCON_CONFIG_AConfigA;
ROMCON_CONFIG_BConfigB;
ROMCON_ROM_CONTROLRomControl;
...
}ROMCON,*PROMCON;----------------------------<-IO_BASE:0x10000000
|||||||...
----------------------------
|||||||...
...
----------------------------<-ROMCONOffset(ROMCON):0x60000
|||||||...
----------------------------<-ROMCON_ROM_CONTROL
...
----------------------------那末怎样会见ROMCON_ROM_CONTROL对应存放器呢,好比ROMCON_ROM_CONTROL对应存放器的VersaPortDisable位?
估量有人大概会如许做:
事前界说成员RomControl(ROMCON顶用ROMCON_ROM_CONTROL界说的实例)绝对与ROMCON的偏移量,#defineROMCONRomControlOffset0x8然后计划会见ROM的接口以下:/*读取ROM把持器位于src地位的存放器数据到dest*/typedefunsignedlongdword_t;
voidrom_read(dword_t*src,uint32_t*dest);
voidrom_write(dword_t*src,uint32_t*dest);最初使用这个偏移量做上面的操纵:ROMCON_ROM_CONTROLtRomCtrl={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另有印象的话,我想你大概会作上面的优化:
#defineROMCON_ADDR(m)(((size_t)IO_BASE+
(size_t)ROMCONOffset+
(size_t)offsetof(ROMCON,m))ROMCON_ROM_CONTROLtRomCtrl={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的界说能够在ANSIC编译器所带的stddef.h中找到。在嵌进式体系里,分歧开辟商,分歧架构处置器和编译器都有分歧的offsetof界说情势:/*Keil8051*/
#defineoffsetof(s,m)(size_t)&(((s*)0)->m)/*Microsoftx86*/
#defineoffsetof(s,m)(size_t)(unsignedlong)&(((s*)0)->m)/*Motorolacoldfire*/
#defineoffsetof(s,memb)((size_t)((char*)&((s*)0)->memb-(char*)0))/*GNUGCC4.0.2*/
#defineoffsetof(TYPE,MEMBER)__builtin_offsetof(TYPE,MEMBER)固然界说情势分歧,但功效都是前往成员在数据布局中的偏移量,都是为了进步代码的可移植性。上面拿KEIL8051的界说来作点注释:
((s*)0):强迫转化成数据布局指针,并使其指向地点0;
((s*)0)->m:使该指针指向成员m
&(((s*)0)->m):猎取该成员m的地点
(size_t)&(((s*)0)->m):转化这个地点为符合的范例你大概会利诱,如许强迫转换后的布局指针怎样能够用来会见布局体字段?呵呵,实在这个表达式基本没有也不盘算会见m字段。ANSIC尺度同意任何值为0的常量被强迫转换成任何一品种型的指针,而且转换了局是一个NULL指针,因而((s*)0)的了局就是一个范例为s*的NULL指针。假如使用这个NULL指针来会见s的成员固然长短法的,但&(((s*)0)->m)的企图并不是想存取s字段内容,而仅仅是盘算当布局体实例的首址为((s*)0)时m字段的地点。伶俐的编译器基本就不天生会见m的代码,而仅仅是依据s的内存结构和布局体实例首址在编译期盘算这个(常量)地点,如许就完整制止了经由过程NULL指针会见内存的成绩。又由于首址的值为0,以是这个地点的值就是字段相对布局体基址的偏移。这里有个中央必要注重:就是offsetof固然一样合用于union布局,但它不克不及用于盘算位域(bitfield)成员在数据布局中的偏移量。typedefstruct
{
unsignedinta:3;
unsignedintb:13;
unsignedintc:16;
}foo;利用offset(foo,a)盘算a在foo中的偏移量,编译器会报错;
有一些像NSCopying的接口(经@李禹龙提醒应该叫协议)不是特别用到开始不用了解NSObject创建对象的时候用+(id)alloc方法创建后需要init方法初始化 |
|