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

Ori*_*ski 9 c struct data-structures

假设我有这个struct(偶然包含位字段,但你不应该在意):

struct Element {
    unsigned int a1 : 1;
    unsigned int a2 : 1;
    ...
    unsigned int an : 1;
};
Run Code Online (Sandbox Code Playgroud)

我想以方便的方式访问第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;
}
Run Code Online (Sandbox Code Playgroud)

但我怀疑有一个更简单的解决方案.也许是数组访问风格之类的东西.

我试着这样做:

 #define getval(s,n)   s.a##n
Run Code Online (Sandbox Code Playgroud)

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

Jar*_*Par 13

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

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

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

  • 结构*不保证连续分配.保证按顺序分配.成员之间可能存在填充.否则,像{double a; char b; 双c; 会严重错位. (5认同)

Eli*_*ght 7

如果结构中的每个字段都是一个int,那么你基本上应该可以说

int getval(struct Element *ep, int n)
{
    return *(((int*)ep) + n);
}
Run Code Online (Sandbox Code Playgroud)

如果是整数,则将指向结构的指针转换为指向数组的指针,然后访问该数组的第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]));
}
Run Code Online (Sandbox Code Playgroud)

这将在你能够调用结构getval的任何int字段的意义上工作,即使你的结构中有其他非int字段,因为偏移量都是正确的.但是,如果您尝试调用getval其中一个非int字段,则会返回完全错误的值.

当然,您可以为每种数据类型编写不同的函数,例如

double getDoubleVal(struct Element *ep, int n)
{
    return *((double*)((int)ep+offsets[n]));
}
Run Code Online (Sandbox Code Playgroud)

然后只需为您想要的任何数据类型调用正确的函数.顺便说一句,如果你使用C++,你可以说类似的东西

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

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

  • @JaredPar:这个答案的第一行是"如果你的struct中的每个字段都是int".事实并非如此,但提问者也说"你不应该关心字段的类型是什么",我们也很在意.很多.因为位域很奇怪. (2认同)

Ste*_*sop 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);
}
Run Code Online (Sandbox Code Playgroud)

并且可以通过多次包含相同标头的技巧来避免一些重复,每次都以不同方式定义宏.标题会为每个1 ... N扩展一次该宏.

但我不相信这是值得的.

它确实处理了JaredPar的观点,即如果你的结构混合了不同的类型,你会遇到麻烦 - 这里通过特定跳转表访问的所有成员当然必须属于同一类型,但它们之间可能有任何旧的垃圾.尽管如此,这仍然留下了JaredPar的其余部分,而且与交换机相比,这是一个很大的代码膨胀.