Доступ к членам структуры, как если бы они были одним массивом?
У меня есть две структуры со значениями, которые должны вычислять взвешенное среднее, как эта упрощенная версия:
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 ответов
Если все члены гарантированно будут иметь тип 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];
(Поскольку порядок членов в структуре гарантированно будет таким, как объявлено)
То, что вы пытаетесь сделать, невозможно сделать каким-либо изящным способом. Невозможно надежно обращаться к последовательным элементам структуры в виде массива. В настоящее время принятый ответ - это взлом, а не решение.
Правильным решением было бы переключиться на массив, независимо от того, сколько работы он потребует. Если вы используете enum-константы для индексации массива (как предложил @digEmAll в своем теперь удаленном ответе), имена и код будут такими же понятными, как и ваши.
Если вы все еще не хотите или не можете переключиться на массив, единственный более или менее приемлемый способ сделать то, что вы пытаетесь сделать, - это создать "индекс-массив" или "карту-массив" (увидеть ниже). C++ имеет специальную языковую функцию, которая помогает элегантно ее реализовать - указатели на члены. В C вы вынуждены эмулировать эту функцию C++, используя offsetof
макрос
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
любым способом, который вы хотите. Это также далеко не элегантно, но, по крайней мере, оно несет некоторую степень надежности и достоверности, в отличие от любого уродливого взлома. Все это можно сделать, чтобы выглядеть более элегантно, обернув повторяющиеся фрагменты в соответственно названные функции / макросы.
Написание вопроса пришло мне в голову... Может ли профсоюз сделать работу? Есть ли еще один умный способ автоматизировать задачу добавления другого участника?
Да union
Конечно, можно сделать работу:
union
{
values v; /* As defined by OP */
int array[6];
} u;
Вы можете использовать указатель на u.values
в вашем API, и работать с u.array
в вашем коде.
Лично я считаю, что все остальные ответы нарушают правило наименьшего удивления. Когда я вижу простое определение структуры, я предполагаю, что структура будет доступна с использованием обычных методов доступа. С union
Ясно, что приложение будет обращаться к нему особым образом, что побуждает меня уделять дополнительное внимание к коду.
Это действительно звучит так, как будто это должен быть массив с самого начала, с методами доступа или макросами, позволяющими вам по-прежнему использовать красивые имена, такие как перемещение, чтение и т. Д. Однако, как вы упомянули, это невозможно из-за поломки API.
Мне приходят на ум два решения:
- Используйте директиву, специфичную для компилятора, чтобы убедиться, что ваша структура упакована (и, следовательно, приведение ее к массиву безопасно)
- Злой макро черная магия.
Как насчет использования __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>
,
Еще одна вещь заключается в том, что при распаковке и повторной упаковке структуры будет наблюдаться снижение производительности, когда к ней необходимо будет получить доступ, а затем сохранить ее обратно в память. Но, по крайней мере, вы можете быть уверены в том, что компоновка является согласованной, и к ней можно обращаться как к массиву, используя приведение к типу указателя, как вы и хотели (то есть вам не придется беспокоиться о заполнении, которое испортит смещение указателя).
Спасибо,
Джейсон
Эта проблема является общей и была решена многими способами в прошлом. Ни один из них не является полностью безопасным или чистым. Это зависит от вашего конкретного применения. Вот список возможных решений:
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" могут помочь в этом случае.