Как получить доступ к членам структуры в соответствии с целым числом переменной в C? - PullRequest
8 голосов
/ 20 мая 2009

Предположим, у меня есть это struct (которое, кстати, содержит битовые поля, но вам все равно):

struct Element {
    unsigned int a1 : 1;
    unsigned int a2 : 1;
    ...
    unsigned int an : 1;
};

и я хочу получить доступ к члену i'th удобным способом. Давайте рассмотрим поисковое решение.
Я придумал эту функцию:

int getval(struct Element *ep, int n)
{
    int val;
    switch(n) { 
         case 1: val = ep->a1; break;
         case 2: val = ep->a2; break;
         ...
         case n: val = ep->an; break;
    }
    return val;
}

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

Я пытался сделать что-то подобное:

 #define getval(s,n)   s.a##n

Но, как ожидается, это не сработает.
Есть ли лучшее решение?

Ответы [ 12 ]

13 голосов
/ 20 мая 2009

Если вы не обладаете конкретными знаниями о базовой структуре структуры, нет способа реализовать такой метод в C. Существует множество проблем, включая

.
  • Члены разных размеров
  • Проблемы с упаковкой
  • Проблемы с выравниванием
  • Такие хитрости, как битовые поля, будут проблематичными

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

7 голосов
/ 20 мая 2009

Если каждое поле в вашей структуре является int, то вы должны сказать:

int getval(struct Element *ep, int n)
{
    return *(((int*)ep) + n);
}

Это преобразует указатель на вашу структуру в указатель на массив, если он является целым числом, а затем обращается к n-му элементу этого массива. Поскольку все в вашей структуре кажется целым числом, это совершенно правильно. Обратите внимание, что это ужасно провалится, если у вас когда-нибудь будет не пользователь.

Более общим решением было бы сохранить массив смещений полей:

int offsets[3];
void initOffsets()
{
    struct Element e;
    offsets[0] = (int)&e.x - (int)&e;
    offsets[1] = (int)&e.y - (int)&e;
    offsets[2] = (int)&e.z - (int)&e;
}

int getval(struct Element *ep, int n)
{
    return *((int*)((int)ep+offsets[n]));
}

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

Конечно, вы можете написать разные функции для каждого типа данных, например,

double getDoubleVal(struct Element *ep, int n)
{
    return *((double*)((int)ep+offsets[n]));
}

, а затем просто вызовите правильную функцию для любого типа данных, который вы хотите. Кстати, если бы вы использовали C ++, вы могли бы сказать что-то вроде

template<typename T>
T getval(struct Element *ep, int n)
{
    return *((T*)((int)ep+offsets[n]));
}

и тогда он будет работать для любого типа данных, который вы захотите.

6 голосов
/ 20 мая 2009

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

Если вы хотите получить доступ к битам по индексу переменной, то, вероятно, лучше заменить ваши битовые поля целым числом, содержащим биты флага. Доступ по переменным на самом деле не то, для чего нужны битовые поля: a1 ... an в основном независимые члены, а не массив битов.

Вы можете сделать что-то вроде этого:

struct Element {
    unsigned int a1 : 1;
    unsigned int a2 : 1;
    ...
    unsigned int an : 1;
};

typedef unsigned int (*get_fn)(const struct Element*);

#define DEFINE_GETTER(ARG) \
    unsigned int getter_##ARG (const struct Element *ep) { \
        return ep-> a##ARG ; \
    }

DEFINE_GETTER(1);
DEFINE_GETTER(2);
...
DEFINE_GETTER(N);

get_fn jump_table[n] = { getter_1, getter_2, ... getter_n};

int getval(struct Element *ep, int n) {
    return jump_table[n-1](ep);
}

А некоторых повторений можно избежать с помощью хитрости, когда вы включаете один и тот же заголовок несколько раз, каждый раз определяя макрос по-разному. Заголовок раскрывает этот макрос один раз для каждого 1 ... N.

Но я не уверен, что оно того стоит.

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

2 голосов
/ 20 мая 2009

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

Конечно, вы можете упростить эту функцию до следующего вида:

int getval(const struct Element *ep, int n)
{
    switch(n)
    {
      case 1: return ep->a1;
      case 2: return ep->a2;
      /* And so on ... */
    }
    return -1; /* Indicates illegal field index. */
}

И кажется очевидным, как можно еще больше упростить реализацию, используя макрос препроцессора, который расширяется до строки case, но это просто sugar .

0 голосов
/ 29 декабря 2011

Хотя OP указывает, что нам не нужно заботиться о содержимом структуры, так как они являются просто битовыми полями, можно было бы использовать char или int (или любой тип данных требуемого размера) для создания n- бит "массив" в этом случае?

void writebit(char *array, int n)
{
  char mask = (1 << n);
  *array = *array & mask;
}

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

0 голосов
/ 15 января 2010

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

struct  int_pointers
 {
   int  *ptr1;
   int  *ptr2;
   long *ptr3;
   double *ptr4;
   std::string * strDescrPtr;

};

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

struct int_pointers  ptrs;
int  i1 = 154;
int i2 = -97;
long i3 = 100000;
double i4  = (double)i1/i2;
std::string strDescr = "sample-string";
ptrs.ptr1 =  &i1;
ptrs.ptr2 =  &i2;
ptrs.ptr3 = &i3;
ptrs.ptr4 = &i4;
ptrs.strDescrPtr = &strDescr;

тогда, например, для значения типа int вы можете написать:

int GetIntVal (struct int_pointers *ep, int intByteOffset) 
{ 
   int * intValuePtr =  (int *)(*(int*)((int)ep + intByteOffset)); 
   return *intValuePtr; 
}

Позвонив по:

int intResult = GetIntVal(&ptrs,0) //to retrieve the first int value in ptrs structure variable

int intResult = GetIntVal(&ptrs,4) //to retrieve the second int value in ptrs structure variable

и т. Д. Для значений других полей структуры (написание других конкретных функций и использование правильного значения смещения байтов (кратного 4)).

0 голосов
/ 20 мая 2009

Если у вас есть

  1. Только битовые поля или все битовые поля, первые в вашей структуре
  2. менее 32 (или 64) битовых полей

тогда это решение для вас.

#include <stdio.h>
#include <stdint.h>

struct Element {
  unsigned int a1 : 1;
  unsigned int a2 : 1;
  unsigned int a3 : 1;
  unsigned int a4 : 1;
};

#define ELEMENT_COUNT 4 /* the number of bit fields in the struct */

/* returns the bit at position N, or -1 on error (n out of bounds) */
int getval(struct Element* ep, int n) 
{
  if(n > ELEMENT_COUNT || n < 1)
    return -1;

  /* this union makes it possible to access bit fields at the beginning of 
     the struct Element as if they were a number.
   */
  union {
    struct Element el;
    uint32_t bits;
  } comb;

  comb.el = *ep;
  /* check if nth bit is set */
  if(comb.bits & (1<<(n-1))) {
    return 1;
  } else {
    return 0;
  }
}

int main(int argc, char** argv)
{
  int i;
  struct Element el;

  el.a1 = 0;
  el.a2 = 1;
  el.a3 = 1;
  el.a4 = 0;

  for(i = 1; i <= ELEMENT_COUNT; ++i) {
    printf("el.a%d = %d\n", i, getval(&el, i));
  }  

  printf("el.a%d = %d\n", 8, getval(&el, 8));

  return 0;
}
0 голосов
/ 20 мая 2009

Если вы хотите получить доступ к своей структуре, используя оба индекса элемента:

int getval(struct Element *ep, int n)

и по имени:

ep->a1

тогда вы застряли с каким-то трудно поддерживаемым методом переключения, который предлагали все.

Если, однако, все, что вам нужно - это доступ по индексу, а не по имени, тогда вы можете быть немного более креативным.

Прежде всего, определите тип поля:

typedef struct _FieldType
{
    int size_in_bits;
} FieldType;

, а затем создайте определение структуры:

FieldType structure_def [] = { {1}, {1}, {1}, {4}, {1}, {0} };

Выше определяется структура с пятью элементами размером 1, 1, 1, 4 и 1 бит. Финал {0} обозначает конец определения.

Теперь создайте тип элемента:

typedef struct _Element
{
    FieldType *fields;
} Element;

Чтобы создать экземпляр Element:

Element *CreateElement (FieldType *field_defs)
{
  /* calculate number of bits defined by field_defs */
  int size = ?;
  /* allocate memory */
  Element *element = malloc (sizeof (Element) + (size + 7) / 8); /* replace 7 and 8 with bits per char */
  element->fields = field_defs;
  return element;
}

А затем для доступа к элементу:

int GetValue (Element *element, int field)
{
   /* get number of bits in fields 0..(field - 1) */
   int bit_offset = ?;
   /* get char offset */
   int byte_offset = sizeof (Element) + bit_offset / 8;
   /* get pointer to byte containing start of data */
   char *ptr = ((char *) element) + byte_offset;
   /* extract bits of interest */
   int value = ?;
   return value;
}

Установка значений аналогична получению значений, только конечная часть нуждается в изменении.

Вы можете улучшить вышеперечисленное, расширив структуру FieldType, включив в нее информацию о типе хранимого значения: char, int, float и т. Д., А затем напишите методы доступа для каждого типа, которые проверяют требуемый тип на соответствие указанному типу.

0 голосов
/ 20 мая 2009

Я предлагаю генерацию кода. Если ваши структуры не содержат огромное количество полей, вы можете автоматически генерировать процедуры для каждого поля или для диапазона полей и используйте их как:

val = getfield_aN( myobject, n );

или

val = getfield_foo( myobject );
0 голосов
/ 20 мая 2009

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

...