Доступ к членам структуры, как если бы они были одним массивом?

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

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 выполнить эту работу? Есть ли еще один умный способ автоматизировать задачу добавления другого участника?

Спасибо, Beco

Если все члены гарантированно имеют тип 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, предложенный в его теперь удаленном ответе), имена и код будут такими же четкими, как у вас сейчас.

Если вы все еще не хотите или не можете переключиться на массив, единственный более или менее приемлемый способ сделать то, что вы пытаетесь сделать, – создать «индексный массив» или «map-array» ( увидеть ниже). 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 по своему желанию. Это также далека от элегантности, но, по крайней мере, она имеет определенную степень надежности и достоверности, в отличие от любого уродливого взлома. Все это можно сделать более элегантным, обернув повторяющиеся fragmentы в соответствующие функции / macros.

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

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

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

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

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

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

Два решения, которые приходят мне на ум:

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

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

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

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

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

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]); 

Хорошо, совсем не просто. Такие macros, как «offsetof», могут помочь в этом случае.

Как насчет использования __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) на вашей платформе, который можно было бы выполнить с помощью некоторых макросов компилятора или с помощью .

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

Спасибо,

Джейсон