2009-05-20 56 views
8

假設我有這個struct(這亦包含位字段,但你不應該關心):如何根據C中的變量整數訪問`struct'的成員?

struct Element { 
    unsigned int a1 : 1; 
    unsigned int a2 : 1; 
    ... 
    unsigned int an : 1; 
}; 

,我想訪問第i個成員的便捷方式。我們來看一下檢索解決方案。
我想出了這個功能:

int getval(struct Element *ep, int n) 
{ 
    int val; 
    switch(n) { 
     case 1: val = ep->a1; break; 
     case 2: val = ep->a2; break; 
     ... 
     case n: val = ep->an; break; 
    } 
    return val; 
} 

但我懷疑,有一個更簡單的解決方案。也許像數組訪問風格。

我試圖做這樣的事情:

#define getval(s,n) s.a##n 

但是果然這是行不通的。
有更好的解決方案嗎?

+1

恐怕我們應該關心是否有位字段:你不能定義一個指向位字段成員的指針,而數組訪問方式意味着指針處理。 – mouviciel 2009-05-20 13:45:39

回答

12

除非你有結構的底層結構的特定知識,沒有辦法用C來實現這樣的方法有各種各樣的問題,將得到的方式,包括

  • 成員不同尺寸
  • 包裝問題
  • 對齊問題
  • 技巧像位域,將有問題的

對於結構的內部成員有深​​入瞭解的結構,您最好手動實施一種方法。

+0

我雖然保證結構是連續分配的。如果這是正確的,並且您知道需要偏移的位數,那麼它*看起來像是應該完全可以簡單地解引用結構指針並直接跳轉到特定值。 – DevinB 2009-05-20 13:29:25

+0

@devinb,連續的是。但不同規模的會員將會得到你。如果有不同大小的成員,除非您知道有關該結構的所有信息,否則無法計算給定成員的偏移量。因此沒有辦法來定義一個通用宏。 – JaredPar 2009-05-20 13:31:42

+0

我誤解了你的回覆。我道歉。我同意。它完全可以在C中實現,但它必須手工完成,並且需要對結構有詳細的瞭解。 – DevinB 2009-05-20 13:32:52

2

不,沒有簡單的方法來做到這一點更容易。特別是對於位域而言,很難通過指針間接訪問(不能獲取位域的地址)。

您當然可以簡化功能是這樣的:

int getval(const struct Element *ep, int n) 
{ 
    switch(n) 
    { 
     case 1: return ep->a1; 
     case 2: return ep->a2; 
     /* And so on ... */ 
    } 
    return -1; /* Indicates illegal field index. */ 
} 

而且這似乎是顯而易見的是如何實現可以通過使用預處理器宏擴展到case直插進一步簡化,但是這只是sugar

6

如果你的結構每一個字段是一個int,那麼你應該基本上可以說

int getval(struct Element *ep, int n) 
{ 
    return *(((int*)ep) + n); 
} 

這就引起指針你的結構的指針數組,如果整數,然後訪問第n個元素該陣列。由於你的結構中的所有東西都是整數,所以這是完全有效的。請注意,如果您有非int成員,這將會失敗。

一個更普遍的解決辦法是保持字段偏移數組:

int offsets[3]; 
void initOffsets() 
{ 
    struct Element e; 
    offsets[0] = (int)&e.x - (int)&e; 
    offsets[1] = (int)&e.y - (int)&e; 
    offsets[2] = (int)&e.z - (int)&e; 
} 

int getval(struct Element *ep, int n) 
{ 
    return *((int*)((int)ep+offsets[n])); 
} 

這將在這個意義上,你就可以調用getval您的任何結構的int字段的工作,即使你的結構中有其他非int字段,因爲偏移量都是正確的。但是,如果您嘗試在其中一個非int字段上調用getval,它將返回完全錯誤的值。

當然,您可以爲每種數據類型編寫不同的函數,例如,

double getDoubleVal(struct Element *ep, int n) 
{ 
    return *((double*)((int)ep+offsets[n])); 
} 

然後只要調用適當的函數爲你想要的任何數據類型。順便說一句,如果你使用的是C++,你可以說類似於

template<typename T> 
T getval(struct Element *ep, int n) 
{ 
    return *((T*)((int)ep+offsets[n])); 
} 

然後它可以用於任何你想要的數據類型。

0

爲什麼不建立getval()到結構?

struct Whang { 
    int a1; 
    int a2; 
    int getIth(int i) { 
     int rval; 
     switch (i) { 
      case 1: rval = a1; break; 
      case 2: rval = a2; break; 
      default : rval = -1; break; 
     } 
     return rval; 
    } 
};  

int _tmain(int argc, _TCHAR* argv[]) 
{ 
     Whang w; 
    w.a1 = 1; 
    w.a2 = 200; 

    int r = w.getIth(1); 

    r = w.getIth(2); 

    return 0; 
} 

getIth()將有Whang內部工作機制的瞭解,並能應付不管它包含。

6

如果你的結構體除bitfields之外的任何東西,那麼你可以使用數組訪問權限,如果我正確地記住C保證結構體的所有相同類型的一系列成員具有​​與數組相同的佈局。如果您知道編譯器將位域按照什麼順序存儲到整數類型中,那麼可以使用shift/mask操作符,但這是與實現相關的。

如果你想通過變量索引來訪問位,那麼最好用包含標誌位的整數替換你的位域。訪問變量真的不是什麼位域:a1 ... an基本上是獨立的成員,而不是一組位。

你可以做這樣的事情:

struct Element { 
    unsigned int a1 : 1; 
    unsigned int a2 : 1; 
    ... 
    unsigned int an : 1; 
}; 

typedef unsigned int (*get_fn)(const struct Element*); 

#define DEFINE_GETTER(ARG) \ 
    unsigned int getter_##ARG (const struct Element *ep) { \ 
     return ep-> a##ARG ; \ 
    } 

DEFINE_GETTER(1); 
DEFINE_GETTER(2); 
... 
DEFINE_GETTER(N); 

get_fn jump_table[n] = { getter_1, getter_2, ... getter_n}; 

int getval(struct Element *ep, int n) { 
    return jump_table[n-1](ep); 
} 

以及一些重複的可以用,你有相同的頭多次的伎倆來避免,每次有確定的宏不同。標題擴展了每個1 ... N的宏一次。

但我不相信這是值得的。

它確實處理了JaredPar的觀點,即如果你的結構混合了不同的類型,那麼你遇到了麻煩 - 在這裏,通過特定跳轉表訪問的所有成員當然必須是相同類型的,但它們可以有任何舊垃圾它們之間。儘管如此,JaredPar的其餘部分仍然存在,並且與交換機相比,這是很多代碼膨脹的代價。

0

我認爲你真正的解決方案是不使用你的結構中的位域,而是定義一個集合類型或一個位數組。

0

我建議代碼生成。如果您的結構不包含字段的鉅額可以自動生成程序爲每個字段或多個領域 ,並利用它們喜歡:

val = getfield_aN(myobject, n); 

val = getfield_foo(myobject); 
0

如果你想

int getval(struct Element *ep, int n) 

和名稱:

0123使用這兩種元素索引來訪問你的結構

然後你堅持一些難以維護的開關像每個人都建議的方法。

但是,如果您想要做的只是按索引訪問而不按名稱訪問,那麼您可以更有創意。

首先,定義一個字段類型:

typedef struct _FieldType 
{ 
    int size_in_bits; 
} FieldType; 

然後創建一個結構的定義:

FieldType structure_def [] = { {1}, {1}, {1}, {4}, {1}, {0} }; 

上面定義了尺寸1,1,1,4的五行的結構和1位。最後的{0}標誌着定義的結束。

現在創建一個元素類型:

typedef struct _Element 
{ 
    FieldType *fields; 
} Element; 

要創建Element的一個實例:

Element *CreateElement (FieldType *field_defs) 
{ 
    /* calculate number of bits defined by field_defs */ 
    int size = ?; 
    /* allocate memory */ 
    Element *element = malloc (sizeof (Element) + (size + 7)/8); /* replace 7 and 8 with bits per char */ 
    element->fields = field_defs; 
    return element; 
} 

然後訪問一個元素:

int GetValue (Element *element, int field) 
{ 
    /* get number of bits in fields 0..(field - 1) */ 
    int bit_offset = ?; 
    /* get char offset */ 
    int byte_offset = sizeof (Element) + bit_offset/8; 
    /* get pointer to byte containing start of data */ 
    char *ptr = ((char *) element) + byte_offset; 
    /* extract bits of interest */ 
    int value = ?; 
    return value; 
} 

設定值是相似爲了獲得價值,只有最後一部分需要改變。

您可以通過擴展FieldType結構來增強上述結構以包含有關所存儲值的類型的信息:char,int,float等等,然後爲每種類型編寫訪問器,用於根據定義的類型檢查所需的類型。

0

如果您在結構

  • 小於32(或64)具有

    1. 只有位域,或所有的位域第一個位字段

    那麼這個解決方案是給你的。

    #include <stdio.h> 
    #include <stdint.h> 
    
    struct Element { 
        unsigned int a1 : 1; 
        unsigned int a2 : 1; 
        unsigned int a3 : 1; 
        unsigned int a4 : 1; 
    }; 
    
    #define ELEMENT_COUNT 4 /* the number of bit fields in the struct */ 
    
    /* returns the bit at position N, or -1 on error (n out of bounds) */ 
    int getval(struct Element* ep, int n) 
    { 
        if(n > ELEMENT_COUNT || n < 1) 
        return -1; 
    
        /* this union makes it possible to access bit fields at the beginning of 
        the struct Element as if they were a number. 
        */ 
        union { 
        struct Element el; 
        uint32_t bits; 
        } comb; 
    
        comb.el = *ep; 
        /* check if nth bit is set */ 
        if(comb.bits & (1<<(n-1))) { 
        return 1; 
        } else { 
        return 0; 
        } 
    } 
    
    int main(int argc, char** argv) 
    { 
        int i; 
        struct Element el; 
    
        el.a1 = 0; 
        el.a2 = 1; 
        el.a3 = 1; 
        el.a4 = 0; 
    
        for(i = 1; i <= ELEMENT_COUNT; ++i) { 
        printf("el.a%d = %d\n", i, getval(&el, i)); 
        } 
    
        printf("el.a%d = %d\n", 8, getval(&el, 8)); 
    
        return 0; 
    } 
    
  • 0

    基於埃利-courtwright解決方案,但在不使用字段偏移 的陣列...... 如果有包含這樣的指針域的結構,也許你可以寫:

    struct int_pointers 
    { 
        int *ptr1; 
        int *ptr2; 
        long *ptr3; 
        double *ptr4; 
        std::string * strDescrPtr; 
    
    }; 
    

    然後,你知道,每個指針有4個字節的指針結構失調,所以你可以寫:

    struct int_pointers ptrs; 
    int i1 = 154; 
    int i2 = -97; 
    long i3 = 100000; 
    double i4 = (double)i1/i2; 
    std::string strDescr = "sample-string"; 
    ptrs.ptr1 = &i1; 
    ptrs.ptr2 = &i2; 
    ptrs.ptr3 = &i3; 
    ptrs.ptr4 = &i4; 
    ptrs.strDescrPtr = &strDescr; 
    

    然後,例如,對於一個int值,你可以這樣寫:

    int GetIntVal (struct int_pointers *ep, int intByteOffset) 
    { 
        int * intValuePtr = (int *)(*(int*)((int)ep + intByteOffset)); 
        return *intValuePtr; 
    } 
    

    通過調用它:

    int intResult = GetIntVal(&ptrs,0) //to retrieve the first int value in ptrs structure variable 
    
    int intResult = GetIntVal(&ptrs,4) //to retrieve the second int value in ptrs structure variable 
    

    等等其他結構字段值(寫其他特定函數並使用正確的字節偏移值(4的倍數))。

    0

    儘管OP指定我們不應該關心結構的內容,因爲它們只是位域,可以使用char或int(或任何具有所需大小的數據類型)來創建n位「數組」在這種情況下?

    void writebit(char *array, int n) 
    { 
        char mask = (1 << n); 
        *array = *array & mask; 
    } 
    

    如果需要更長的「數組」,則將char類型替換爲較大的類型。不確定這是否是其他結構中的權威解決方案,但它應該在這裏工作,具有類似的readbit功能。

    相關問題