簡體   English   中英

訪問struct成員就好像它們是單個數組一樣?

[英]Access struct members as if they are a single array?

我有兩個結構,其值應該計算一個沉重的平均值,就像這個簡化版本:

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做這個工作嗎? 還有另一種聰明的方法可以自動化添加其他成員的任務嗎?

謝謝,Beco

你想要做的是不可能以任何優雅的方式做。 無法將連續的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 這也遠非優雅,但至少它具有一定程度的可靠性和有效性,而不是任何丑陋的黑客。 通過將重復的部分包裝到適當命名的函數/宏中,可以使整個事物看起來更優雅。

如果所有成員都保證為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];

(因為結構中成員的順序保證是聲明的)

在我腦海中寫下這個問題......工會可以做這個工作嗎? 還有另一種聰明的方法可以自動化添加其他成員的任務嗎?

是的, union當然可以做到這一點:

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

您可以在API中使用指向u.values的指針,並在代碼中使用u.array

就個人而言,我認為所有其他答案都打破了最不驚訝的規則。 當我看到一個簡單的結構定義時,我假設該結構將使用普通訪問方法進行訪問。 通過union ,很明顯應用程序將以特殊方式訪問它,這促使我特別注意代碼。

這真的聽起來好像這應該是一個數組,因為開始,使用訪問器方法或宏使您仍然可以使用漂亮的名稱,如移動,讀取等。但是,正如您所提到的,由於API破壞,這是不可行的。

我想到的兩個解決方案是:

  • 使用編譯器特定的指令來確保您的結構被打包(因此,將它轉換為數組是安全的)
  • 邪惡的宏黑魔法。

這個問題很常見,過去已經以多種方式解決了。 它們都不是完全安全或干凈的。 這取決於您的具體應用。 以下是可能的解決方案列表:

1)您可以重新定義結構,使字段成為數組元素,並使用宏將每個特定元素映射為結構字段。 例如:

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”這樣的宏可能會有所幫助。

如果你使用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>

還有一件事是,當需要訪問結構並將其存儲回內存時,解壓縮和重新打包結構會有性能損失。 但至少可以肯定的是,布局是一致的,並且它可以像使用類似於你想要的指針類型的數組一樣被訪問(即,你不必擔心填充指針偏移的填充)。

謝謝,

賈森

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM