如何根据C中的变量整数访问`struct'的成员?

时间:2009-05-20 13:20:40

标签: c data-structures struct

假设我有struct(偶然包含位字段,但您不应该关心):

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

我希望以方便的方式访问 i'th 成员。让我们检查一下检索解决方案 我想出了这个功能:

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

但是预计它不起作用 有更好的解决方案吗?

12 个答案:

答案 0 :(得分:13)

除非你对结构的底层结构有特定的了解,否则无法在C中实现这样的方法。会遇到各种各样的问题,包括

  • 不同规模的会员
  • 包装问题
  • 对齐问题
  • 像位域这样的技巧会有问题

你最好亲自为你的结构实现一个方法,它对结构的内部成员有深​​刻的理解。

答案 1 :(得分:7)

如果结构中的每个字段都是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]));
}

这将起到这样的意义,即您可以为结构的任何int字段调用getval,即使结构中有其他非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]));
}

然后它适用于您想要的任何数据类型。

答案 2 :(得分:6)

如果你的结构是除了位域之外的任何东西,你可以只使用数组访问,如果我正确地记住C保证结构的一系列成员都是相同类型,具有与数组相同的布局。如果您知道编译器将位域按顺序存储到整数类型中的哪些位,那么您可以使用shift / mask ops,但那是依赖于实现的。

如果要通过变量索引访问位,那么最好用包含标志位的整数替换位域。变量访问确实不是bitfields的用途: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的其余部分,与开关相比,这是一个很大的代码膨胀。

答案 3 :(得分: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

答案 4 :(得分:0)

如果结构真的像描述的那样简单,你可以使用带数组的联合(或对数组的强制转换)和一些位访问魔法(如How do you set, clear and toggle a single bit in C?中所示)。

正如Jared所说,一般情况是 hard

答案 5 :(得分: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 :(得分:0)

我认为你的真正解决方案是不在结构中使用位域,而是定义一个集合类型或一个位数组。

答案 7 :(得分:0)

我建议代码生成。如果您的结构不包含大量字段,则可以为每个字段或一系列字段自动生成例程 并使用它们:

val = getfield_aN( myobject, n );

val = getfield_foo( myobject );

答案 8 :(得分:0)

如果要使用元素索引访问您的结构:

int getval(struct Element *ep, int n)

并按名称:

ep->a1

然后你会遇到一些难以维护的开关,就像每个人都建议的方法一样。

但是,如果您想要做的只是按索引访问而不是按名称访问,那么您可以更有创意。

首先,定义一个字段类型:

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等,然后为每种类型写入访问器,根​​据定义检查所需类型类型。

答案 9 :(得分:0)

如果你有

  1. 只有位字段或结构中的所有位域
  2. 小于32(或64)个位域
  3. 然后这个解决方案适合你。

    #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;
    }
    

答案 10 :(得分:0)

基于eli-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的倍数))。

答案 11 :(得分:0)

尽管OP指定我们不应该关心结构的内容,因为它们只是位域,所以可以使用char或int(或任何具有所需大小的数据类型)来创建n-在这种情况下位“数组”?

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

如果需要更长的“数组”,则将char类型替换为更大的类型。不确定这是否是其他结构中的确定解决方案,但它应该在这里工作,具有类似的readbit函数。