2009-05-20 55 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功能。

    相关问题