2009-05-20 19 views
8

Supongamos que tengo este struct (dicho sea de paso, que contienen campos de bits, pero no se debe cuidar):¿Cómo acceder a los miembros de una 'estructura' de acuerdo con un entero variable en C?

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

y quiero acceder a la i'th miembro de una manera conveniente. Examinemos una solución de recuperación.
me ocurrió con esta función:

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; 
} 

pero sospecho que hay una solución mucho más simple. Algo así como el estilo de acceso a la matriz, tal vez.

he tratado de hacer algo por el estilo:

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

Pero como era de esperar no funciona.
¿Existe una mejor solución?

+1

Me temo que deberíamos importarnos si hay campos de bit o no: no se puede definir un puntero a un miembro de campo de bit, y el estilo de acceso de matriz implica el manejo de puntero. – mouviciel

Respuesta

12

A menos que tenga un conocimiento específico de la estructura subyacente de la estructura, no hay manera de poner en práctica un procedimiento de este tipo en C. Hay todo tipo de problemas que van a interponerse en el camino incluyendo

  • miembros de diferentes tamaños
  • cuestiones de embalaje
  • alineación emite
  • Trucos como campos de bits será problemático

Lo mejor es que implemente un método a mano para su estructura que tenga una comprensión profunda de los miembros internos de la estructura.

+0

Creo que se garantiza que las estructuras se asignarán contiguamente. Si eso es correcto, y usted sabe la cantidad de bits que necesita para compensar, entonces * parece * como debería ser posible simplemente desreferenciar el puntero de estructura y saltar directamente al valor particular. – DevinB

+0

@devinb, contiguo sí. Pero los miembros de tamaño variable te atraparán. Si hay miembros de diferentes tamaños, no hay forma de calcular el desplazamiento de un miembro dado a menos que conozca toda la información sobre la estructura. Por lo tanto, no hay forma de definir una macro de propósito general. – JaredPar

+0

Leí mal su respuesta. Me disculpo. Estoy de acuerdo. Es totalmente implementable en C, pero debe hacerse a mano, y requiere un conocimiento detallado específico de la estructura. – DevinB

2

No, no hay una manera simple de hacer esto más fácil. Especialmente para bitfields, que son difíciles de acceder indirectamente a través de punteros (no puede tomar la dirección de un campo de bits).

Se puede simplificar, por supuesto, esa función a algo como esto:

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. */ 
} 

Y parece obvio que la implementación se puede simplificar aún más utilizando una macro preprocesador que se expande a la case -line, pero eso es sólo sugar.

6

Si todos los campos en la estructura es un int, a continuación, que, básicamente, debería ser capaz de decir

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

Esto arroja el puntero a la estructura de un puntero a una matriz si los números enteros, a continuación, accede al enésimo elemento de esa matriz. Como todo en tu estructura parece ser un número entero, esto es perfectamente válido. Tenga en cuenta que esto fallará horriblemente si alguna vez tiene un miembro no int.

Una solución más general sería mantener una serie de desplazamientos de campo:

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

Esto funcionará en el sentido de que usted será capaz de llamar a getval para cualquiera de los campos int de su estructura, incluso si tiene otros campos no-int en su estructura, ya que los desplazamientos serán correctos.Sin embargo, si intentas llamar al getval en uno de los campos no-int, devolverá un valor completamente erróneo.

Por supuesto, puede escribir una función diferente para cada tipo de datos, p. Ej.

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

y luego simplemente llame a la función adecuada para el tipo de datos que desee. Por cierto, si estuviera usando C++ se podría decir algo así como

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

y después de que funcionaría para cualquier tipo de datos que te gustaría.

+0

Esto es hermoso. –

+0

Esto no funciona. El OP usó un campo de bit de tamaño 1 bit. Devolverá un puntero a un byte de 4 tamaños. Este código producirá punteros desalineados y, cuando se desreferencia en la alineación correcta, se leerán 4 bytes (en la mayoría de las plataformas) desde la estructura frente al bit 1 que se especificó. – JaredPar

+2

@JaredPar: la primera línea de esta respuesta es "si cada campo en su estructura es un int". Lo cual no es, pero luego el que pregunta también dijo "no debería importarte qué tipo de campos es", y nos importa. Mucho. Porque los campos de bit son extraños. –

0

Si la estructura es tan simple como se describe, puede usar una unión con una matriz (o una conversión a una matriz) y algo de magia de acceso a los bits (como en How do you set, clear and toggle a single bit in C?).

Como dice Jared, el caso general es difícil.

+0

Sí, siempre que tenga confianza en cómo se almacenan los datos, en términos de endianness y relleno, etc. –

0

¿Por qué no compilar getval() en la estructura?

struct Whang { 
    int a1; 
    int a2; 
    int getIth(int i) { 
     int rval; 
     switch (i) { 
      case 1: rval = a1; break; 
      case 2: rval = a2; break; 
      default : rval = -1; break; 
     } 
     return rval; 
    } 
};  

int _tmain(int argc, _TCHAR* argv[]) 
{ 
     Whang w; 
    w.a1 = 1; 
    w.a2 = 200; 

    int r = w.getIth(1); 

    r = w.getIth(2); 

    return 0; 
} 

getIth() tendría conocimiento de las interioridades de Whang, y podrían hacer frente a lo que contenía.

+1

"¿Por qué no compilar getval() en la estructura?" Porque esto es C, no C++? –

+0

Ack, ha pasado mucho tiempo desde que usé C simple ... Perdón por la dirección equivocada. – Number8

6

Si su estructura era cualquier cosa excepto bitfields, podría usar el acceso a la matriz, si tengo en cuenta que C garantiza que una serie de miembros de una estructura del mismo tipo tiene el mismo diseño que una matriz . Si sabe qué bits en qué orden su compilador almacena bitfields en tipos enteros, entonces podría usar shift/mask ops, pero eso depende de la implementación.

Si desea acceder a los bits por índice de variable, entonces probablemente sea mejor reemplazar sus campos de bits con un número entero que contenga bits indicadores. El acceso por variable realmente no es para lo que son los bitfields: a1 ... an son básicamente miembros independientes, no una matriz de bits.

se podría hacer algo como esto:

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

Y algunas de las repeticiones podrían evitarse mediante el truco, donde se incluye el mismo encabezado varias veces, cada vez habiendo definido una macro diferente. El encabezado expande esa macro una vez por cada 1 ... N.

Pero no estoy seguro de que valga la pena.

Tiene que ver con el argumento de JaredPar de que tienes problemas si tu estructura mezcla diferentes tipos: aquí todos los miembros accedidos a través de una tabla de salto deben ser del mismo tipo, pero pueden tener cualquier basura vieja en entre ellos. Sin embargo, eso deja el resto de los puntos de JaredPar, y esto es una gran cantidad de código inflado realmente sin beneficio en comparación con el cambio.

+0

WOW. Qué construcción. Seguramente no parece práctico pero tomaré algunas de las ideas que presentaste. Gracias. –

+0

> si no me equivoco al recordar que C garantiza que una serie de > miembros de una estructura del mismo tipo, tiene el mismo diseño que > una matriz Eso es nuevo para mí. ¿Alguien más escuchó esto? Imagine que define una estructura que tiene 5 bytes de longitud. En una máquina de 32 bits que normalmente te dejará con espacios de 3 bytes. Me pregunto si una matriz también mostrará esas lagunas. –

+0

Depende de los miembros de la estructura. Una estructura de 5 bytes que contiene 5 caracteres puede salir con un tamaño de 5 y sin espacios en una matriz o una estructura. Una estructura de 5 bytes que contiene un int32 y un char saldrá con un tamaño de 8 y huecos en ambos casos. –

0

Creo que su solución real es no usar bitfields en su estructura, sino definir un tipo de conjunto o una matriz de bits.

+0

Necesito un número específico de bits y el problema es que C no garantiza ningún número de bits fijos para ningún tipo. Por ejemplo, no se garantiza que char contenga exactamente 8 bits, y así sucesivamente ... –

+0

@Leif: luego incluya de C99, y use uint32_t para almacenar 32 indicadores. Si está atascado en C89, coloque una afirmación de CHAR_BITS> = 8 y use caracteres, o bien almacene 6 bits por carácter si quiere ser irrazonablemente prudente: el juego de caracteres básico de C tiene más de 64 caracteres, por lo que char no puede ser menos de 6 bits. Creo que POSIX podría garantizar CHAR_BIT = 8 de todos modos, no recuerdo. –

+0

@onebyone Me temo que no puedo usar las bibliotecas C99. No trabajamos con C99, y tampoco puedo usar bibliotecas no estándar. En cuanto a la idea de afirmar, lo investigaré. Todavía no sé muy bien qué es. Además, no hay límite superior para el número de bits en caracteres, por lo que el límite inferior de 8 no hace mucho. –

0

Sugiero generación de código.Si sus estructuras no contienen gran cantidad de campos que puede generar rutinas de auto para cada campo o para una serie de campos y el uso de ellos como:

val = getfield_aN(myobject, n); 

o

val = getfield_foo(myobject); 
0

Si desea acceder a su estructura utilizando tanto el índice de elemento:

int getval(struct Element *ep, int n) 

y por su nombre:

ep->a1 

y luego está atascado con algún interruptor difícil de mantener como método que todos han sugerido.

Si, sin embargo, todo lo que desea hacer es acceder por índice y nunca por nombre, entonces puede ser un poco más creativo.

En primer lugar, definir un tipo de campo:

typedef struct _FieldType 
{ 
    int size_in_bits; 
} FieldType; 

y luego crear una definición de estructura:

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

El anterior define una estructura con cinco elementos de tamaño 1, 1, 1, 4 y 1 bit. El final {0} marca el final de la definición.

Ahora crea un tipo de elemento:

typedef struct _Element 
{ 
    FieldType *fields; 
} Element; 

Para crear una instancia de un 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; 
} 

Y a continuación, para acceder a un elemento:

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; 
} 

Valores de ajuste es similar para obtener valores, solo la parte final debe cambiar.

Puede ampliar lo anterior ampliando la estructura FieldType para incluir información sobre el tipo de valor almacenado: char, int, float, etc., y luego escribir accesos para cada tipo que verifique el tipo requerido con el tipo definido.

0

Si tiene

  1. Solamente los campos de bits, o todos los campos de bits por primera vez en su estructura
  2. menos de 32 (o 64) bitfields

continuación, esta solución es para usted.

#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

¿Esta solución es portátil? Depende de la alineación de los bits dentro de una unidad, ¿no es así? –

+0

Aunque la idea de la unión es genial. –

0

basado en la solución eli-Courtwright pero sin utilizar variedad de offsets de campo ...... si tiene una estructura que contiene campo de puntero de este tipo, tal vez usted podría escribir:

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

}; 

entonces usted sabe que cada puntero tiene un 4 bytes de desplazamiento de un puntero a la estructura, para que pueda escribir:

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; 

entonces, por ejemplo, por un valor int puede escribir:

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

Llamarlo por:

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 

y así sucesivamente para los otros valores de los campos de estructura (escribir otras funciones específicas y usar el valor de corrección de bytes correcto (múltiplo de 4)).

0

Aunque el OP especifica que no deberíamos preocuparnos por los contenidos de la estructura, ya que son solo bitfields, sería posible usar un char o int (o el tipo de datos que tenga el tamaño requerido) para crear un n -bit "matriz" en este caso?

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

con los tipos de caracteres reemplazados por un tipo más grande si se necesitaba una "matriz" más larga. No estoy seguro de si esta es una solución definitiva en otras estructuras, pero debería funcionar aquí, con una función readbit similar.

Cuestiones relacionadas