offsetof(s,m)解析


https://www.cnblogs.com/jingzhishen/p/3696293.html

sizeof()用法匯總


sizeof()功能:計算數據空間的字節數
1.與strlen()比較
      strlen()計算字符數組的字符數,以"\0"為結束判斷,不計算為'\0'的數組元素。
      而sizeof計算數據(包括數組、變量、類型、結構體等)所占內存空間,用字節數表示。
2.指針與靜態數組的sizeof操作
      指針均可看為變量類型的一種。所有指針變量的sizeof 操作結果均為4。
注意:int *p; sizeof(p)=4;
                  但sizeof(*p)相當於sizeof(int);      
      對於靜態數組,sizeof可直接計算數組大小;
      例:int a[10];char b[]="hello";
              sizeof(a)等於4*10=40;
              sizeof(b)等於6;
 注意:數組做型參時,數組名稱當作指針使用!!
               void  fun(char p[])
               {sizeof(p)等於4}

 

 

    char str[20]="0123456789";
    int a=strlen(str); //a=10;
    int b=sizeof(str); //而b=20;    
    char ss[] = "0123456789";    
    sizeof(ss) 結果 11 ===》ss是數組,計算到\0位置,因此是10+1   
    sizeof(*ss) 結果 1 ===》*ss是第一個字符
    char ss[100] = "0123456789";
    sizeof(ss) 結果是100 ===》ss表示在內存中的大小 100×1
    strlen(ss) 結果是10 ===》strlen是個函數內部實現是用一個循環計算到\0為止之前
    int ss[100] = "0123456789";
    sizeof(ss) 結果 400 ===》ss表示在內存中的大小 100×4
    strlen(ss) 錯誤 ===》strlen的參數只能是char* 且必須是以'\0'結尾的
    char q[]="abc";
    char p[]="a\n";
    sizeof(q),sizeof(p),strlen(q),strlen(p);     結果是 4 3 3 2
    
     一些朋友剛開始時把sizeof當作了求數組元素的個數,如今你應該知道這是不對的,那么應該怎么求數組元素的個數呢Easy,通常有下面兩種寫法:
     1.int c1 = sizeof( a1 ) / sizeof( char );         // 總長度/單個元素的長度
     2.int c2 = sizeof( a1 ) / sizeof( a1[0] );        // 總長度/第一個元素的長度    
 
     Student stus[4]={
        {"humingtao",24,99},
        {"zhaoweisong",23,66},
        {"chenfengchang",21,88},
        {"zhuwenpeng",20,77}
        
     數組長度  count = sizeof(stus)/sizeof(stus[0]
 
     
     double* (*a)[3][6];
     cout<<sizeof(a)<<endl; // 4 a為指針
     cout<<sizeof(*a)<<endl; // 72 *a為一個有3*6個指針元素的數組
     cout<<sizeof(**a)<<endl; // 24 **a為數組一維的6個指針
     cout<<sizeof(***a)<<endl; // 4 ***a為一維的第一個指針
     cout<<sizeof(****a)<<endl; // 8 ****a為一個double變量

 

offsetof(s,m)解析

 
使用實例:
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中的偏移量,編譯器會報錯。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM