Доступ к членам структуры, как если бы они были одним массивом? - PullRequest
5 голосов
/ 02 апреля 2011

У меня есть две структуры со значениями, которые должны вычислять взвешенное среднее, как эта упрощенная версия:

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_clean и qtt_clean. Я не могу изменить структуры на массивы:

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

Это сильно упростило бы мою работу, но они являются частью API, который требует ясности имен переменных.

Итак, я ищу способ доступа к членам этих структур, возможно, используя sizeof(), чтобы я мог обращаться с ними как с массивом, но при этом сохранять API неизменным. Гарантируется, что все значения int, но я не могу гарантировать размер int.

Написание вопроса пришло мне в голову ... Может ли union сделать работу? Есть ли еще один умный способ автоматизировать задачу добавления другого члена?

Спасибо, Бек

Ответы [ 6 ]

18 голосов
/ 02 апреля 2011

То, что вы пытаетесь сделать, невозможно сделать каким-либо изящным способом. Невозможно надежно обращаться к последовательным элементам структуры в виде массива. В настоящее время принятый ответ - это взлом, а не решение.

Правильным решением было бы переключиться на массив, независимо от того, сколько работы ему потребуется. Если вы используете enum-константы для индексации массива (как предложил @digEmAll в своем теперь удаленном ответе), имена и код будут такими же понятными, как и ваши.

Если вы все еще не хотите или не можете переключиться на массив, единственный более или менее приемлемый способ сделать то, что вы пытаетесь сделать, - это создать «индекс-массив» или «map-». массив "(см. ниже). C ++ имеет специальную языковую функцию, которая помогает элегантно ее реализовать - указатели на члены. В C вы вынуждены эмулировать эту функцию C ++, используя offsetof macro

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 любым удобным для вас способом. Это также далеко не элегантно, но, по крайней мере, несет некоторую степень надежности и обоснованности, в отличие от любого уродливого взлома. Все это можно сделать, чтобы выглядеть более элегантно, обернув повторяющиеся фрагменты в соответственно названные функции / макросы.

8 голосов
/ 02 апреля 2011

Если все члены гарантированно имеют тип 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];

(Поскольку порядок членов в структуре гарантированно будет таким, как объявлено)

6 голосов
/ 04 апреля 2011

Написание вопроса пришло мне в голову ... Может ли профсоюз сделать работу? Есть ли еще один умный способ автоматизировать задачу добавления другого члена?

Да, union, безусловно, может выполнить работу:

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

Вы можете использовать указатель на u.values в своем API и работать с u.array в своем коде.

Лично я думаю, что все остальные ответы нарушают правило наименьший сюрприз . Когда я вижу простое определение структуры, я предполагаю, что структура будет доступна с использованием обычных методов доступа. С union ясно, что приложение будет обращаться к нему особым образом, что побуждает меня уделять больше внимания коду.

4 голосов
/ 02 апреля 2011

Это действительно звучит так, как будто это должен быть массив с самого начала, с методами доступа или макросами, позволяющими вам по-прежнему использовать красивые имена, такие как перемещение, чтение и т. Д. Однако, как вы упомянули, это невозможно из-заAPI breakage.

Мне приходят на ум два решения:

  • Используйте директиву, специфичную для компилятора, чтобы убедиться, что ваша структура упакована (и, следовательно, приведена к массиву).это безопасно)
  • Злой макро черная магия.
3 голосов
/ 02 апреля 2011

эта проблема является распространенной и была решена многими способами в прошлом. Ни один из них не является полностью безопасным или чистым. Это зависит от вашего конкретного применения. Вот список возможных решений:

1) Вы можете переопределить свои структуры, чтобы поля стали элементами массива, и использовать макросы для отображения каждого конкретного элемента, как если бы это было структурное поле. Например:

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

Недостатком этого подхода является то, что большинство отладчиков не понимают макросы. Другая проблема заключается в том, что теоретически компилятор может выбрать другое выравнивание для исходной структуры и переопределенной, поэтому двоичная совместимость не гарантируется.

2) Рассчитывайте поведение компилятора и рассматривайте все поля как поля массива (упс, пока я писал это, кто-то другой написал то же самое - +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».

2 голосов
/ 02 апреля 2011

Как насчет использования __attribute__((packed)), если вы используете gcc?

Так что вы можете объявить свои структуры как:

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>.

Еще одна вещь - это снижение производительности при распаковке ипереупаковка структуры, когда она должна быть доступна, а затем сохранена обратно в память.Но, по крайней мере, вы можете быть уверены, что компоновка является согласованной, и к ней можно обращаться как к массиву, используя приведение к типу указателя, как вы и хотели (то есть вам не придется беспокоиться о заполнении, которое испортит смещение указателя)).

Спасибо,

Джейсон

...