访问struct成员就好像它们是单个数组一样?

时间:2011-04-02 17:00:23

标签: c arrays struct sizeof unions

我有两个结构,其值应该计算一个思考平均值,就像这个简化版本一样:

typedef struct
{
  int v_move, v_read, v_suck, v_flush, v_nop, v_call;
} values;

typedef struct
{
  int qtt_move, qtt_read, qtt_suck, qtd_flush, qtd_nop, qtt_call;
} quantities;

然后我用它们来计算:

average = v_move*qtt_move + v_read*qtt_read + v_suck*qtt_suck + v_flush*qtd_flush + v_nop*qtd_nop + v_call*qtt_call;

现在和他们一起我需要包含另一个变量。现在,例如,我需要包含v_cleanqtt_clean。我无法将结构更改为数组:

typedef struct
{
    int v[6];
} values;
typedef struct
{
    int qtt[6];
} quantities;

这会简化我的工作,但它们是需要变量名称清晰的API的一部分。

所以,我正在寻找一种方法来访问那些结构的成员,可能使用sizeof(),所以我可以将它们视为一个数组,但仍然保持API不可更改。保证所有值均为int,但我无法保证int的大小。

写下这个问题在我脑海中浮现出来...... union可以做这个工作吗?还有另一种聪明的方法可以自动化添加其他成员的任务吗?

谢谢, 贝乔

6 个答案:

答案 0 :(得分:18)

你想做的事情是不可能以任何优雅的方式做。无法将连续的struct成员可靠地作为数组进行访问。目前接受的答案是黑客,而不是解决方案。

正确的解决方案是切换到一个数组,无论它需要多少工作。如果你使用枚举常量进行数组索引(在他现在删除的答案中建议使用@digEmAll),名称和代码将与你现在的内容一样清晰。

如果您仍然不想或不能切换到数组,那么您尝试做的唯一或多或少可接受的方法是创建“索引数组”或“映射 - 数组“(见下文)。 C ++有一个专门的语言功能,可以帮助人们优雅地实现它 - 指向成员的指针。在C中,您被迫使用offsetof

模拟该C ++功能
static const size_t values_offsets[] = { 
  offsetof(values, v_move),
  offsetof(values, v_read),
  offsetof(values, v_suck),
  /* and so on */
};

static const size_t quantities_offsets[] = { 
  offsetof(quantities, qtt_move),
  offsetof(quantities, qtt_read),
  offsetof(quantities, qtt_suck),
  /* and so on */
};

如果现在你得到了

values v;
quantities q;

和索引

int i;

您可以生成指向单个字段的指针

int *pvalue = (int *) ((char *) &v + values_offsets[i]);
int *pquantity = (int *) ((char *) &q + quantities_offsets[i]);

*pvalue += *pquantity;

当然,您现在可以以任何您想要的方式迭代i。这也远非优雅,但至少它具有一定程度的可靠性和有效性,而不是任何丑陋的黑客。通过将重复的部分包装到适当命名的函数/宏中,可以使整个事物看起来更优雅。

答案 1 :(得分:8)

如果所有成员都保证为int类型,则可以使用指向int的指针并递增它:

int *value = &(values.v_move);
int *quantity = &(quantities.qtt_move);
int i;
average = 0;
// although it should work, a good practice many times IMHO is to add a null as the last member in struct and change the condition to quantity[i] != null.
for (i = 0; i < sizeof(quantities) / sizeof(*quantity); i++)
    average += values[i] * quantity[i];

(因为结构中成员的顺序保证是声明的)

答案 2 :(得分:6)

  

写下这个问题出现在我的脑海里......工会可以做这个工作吗?还有另一种聪明的方法可以自动化添加其他成员的任务吗?

是的,union当然可以胜任:

union
{
  values v;    /* As defined by OP */
  int array[6];
} u;

您可以在API中使用指向u.values的指针,并在代码中使用u.array

就我个人而言,我认为所有其他答案都会打破最少惊喜的规则。当我看到一个简单的结构定义时,我假设该结构将使用普通访问方法进行访问。使用union,很明显应用程序将以特殊方式访问它,这会提示我特别注意代码。

答案 3 :(得分:4)

这真的听起来好像这应该是一个数组,因为开始,使用访问器方法或宏使你仍然可以使用漂亮的名称,如移动,读取等。但是,正如你所提到的,这是不可行的,因为API断裂。

我想到的两个解决方案是:

  • 使用编译器特定的指令来确保您的结构被打包(因此,将它转换为数组是安全的)
  • 邪恶的黑魔法。

答案 4 :(得分:3)

这个问题很常见,过去已经以多种方式解决了。它们都不是完全安全或干净的。这取决于您的具体应用。以下是可能的解决方案列表:

1)您可以重新定义结构,使字段成为数组元素,并使用宏将每个特定元素映射为结构字段。 E.g:

struct values { varray[6]; };
#define v_read varray[1]

这种方法的缺点是大多数调试器都不理解宏。另一个问题是理论上编译器可以为原始结构和重新定义的结构选择不同的对齐方式,因此不保证二进制兼容性。

2)依靠编译器的行为并将所有字段视为数组字段(oops,当我写这篇文章时,别人写了同样的内容 - 给他+1)

3)创建一个静态的元素偏移数组(在启动时初始化)并使用它们“映射”元素。它非常棘手,并不是那么快,但其优势在于它独立于结构中场的实际配置。示例(不完整,仅供澄清):

int positions[10];
position[0] = ((char *)(&((values*)NULL)->v_move)-(char *)NULL);
position[1] = ((char *)(&((values*)NULL)->v_read)-(char *)NULL);
//...
values *v = ...;
int vread;
vread = *(int *)(((char *)v)+position[1]);

好吧,一点也不简单。在这种情况下,像“offsetof”这样的宏可能会有所帮助。

答案 5 :(得分:2)

如果使用gcc,请使用__attribute__((packed))怎么样?

所以你可以将结构声明为:

typedef struct
{
    int v_move, v_read, v_suck, v_flush, v_nop, v_call;
} __attribute__((packed)) values;

typedef struct 
{
    int qtt_move, qtt_read, qtt_suck, qtd_flush, qtd_nop, qtt_call;
} __attribute__((packed)) quantities;

根据gcc手册,您的结构将使用尽可能少的内存来存储结构,省略通常可能存在的任何填充。唯一的问题是确定平台上的sizeof(int),可以通过某些编译器宏或使用<stdint.h>来完成。

还有一件事是,当需要访问结构并将其存储回内存时,解压缩和重新打包结构会有性能损失。但至少可以肯定的是,布局是一致的,并且它可以像使用类似于你想要的指针类型的数组一样被访问(即,你不必担心填充指针偏移的填充)。

谢谢,

杰森