views:

945

answers:

12

Suppose I have this struct (which incidentally contain bit-fields, but you shouldn't care):

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

and I want to access the i'th member in a convenient way. Let's examine a retrieval solution.
I came up with this function:

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

But I suspect that there is a much simpler solution. Something like array accessing style, maybe.

I tried to do something like that:

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

But expectedly it doesn't work.
Is there a nicer solution?

+8  A: 

Unless you have specific knowledge of the underlying structure of the struct, there is no way to implement such a method in C. There are all sorts of problems that will get in the way including

  • Members of different sizes
  • Packing issues
  • Alignment issues
  • Tricks like bitfields will be problematic

You're best off implementing a method by hand for your struct which has a deep understanding of the internal members of the structure.

JaredPar
I though structs were guaranteed to be contiguously allocated. If that is correct, and you know the number of bits you need to offset by, then it *seems* like it should be entirely possible to simply dereference the struct pointer and jump straight to the particular value.
@devinb, contiguous yes. But the variable size members will get you . If there are members of different sizes, there is no way to calculate the offset of a given member unless you know all of the information about the struct. Hence no way to define a general purpose macro.
JaredPar
I misread your response. I apologize. I agree. It is totally implementable in C, but it must be done by hand, and it requires specific detailed knowledge of the struct.
Structs are *not* guaranteed to be contiguously allocated. They are guaranteed to be allocated in order. There may be padding between members. Otherwise, a struct like {double a; char b; double c; } would have c seriously misaligned.
David Thornley
@devinb no apologies needed.
JaredPar
Nice catch David. Is that consistent across all operating systems? I.E. Would your struct always have the components allocated at the same offset from the struct start location, even with the padding included?
@devinb: The ordering of struct elements is required by the C standard (so it's guaranteed) but padding between adjacent elements is implementation-dependent. This means that the offset of a given element may be different across platforms or between different compilers. If you want the offset of an element you should use the offsetof() macro. Bit-fields complicate things greatly because they are largely implementation-defined and thus completely non-portable.
Michael Carman
+1  A: 

No, there is no simple way to do this easier. Especially for bitfields, that are hard to access indirectly through pointers (you cannot take the address of a bitfield).

You can of course simplify that function to something like this:

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

And it seems obvious how the implementation can be further simplified by using a preprocessor macro that expands to the case-line, but that's just sugar.

unwind
+3  A: 

If every field in your struct is an int, then you should basically be able to say

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

This casts the pointer to your struct to a pointer to an array if integers, then accesses the nth element of that array. Since everything in your struct seems to be an integer, this is perfectly valid. Note that this will fail horribly if you ever have a non-int member.

A more general solution would be to maintain an array of field offsets:

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

This will work in the sense that you'll be able to call getval for any of the int fields of your struct, even if you have other non-int fields in your struct, since the offsets will all be correct. However, if you tried to call getval on one of the non-int fields it would return a completely wrong value.

Of course, you could write a different function for each data type, e.g.

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

and then just call the proper function for whichever datatype you'd want. Incidentally, if you were using C++ you could say something like

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

and then it would work for whatever datatype you'd want.

Eli Courtwright
This is beautiful.
Leif Ericson
This doesn't work. The OP used a bit field of size 1 bit. You will be returning a pointer to a 4 size byte. This code will produce un-aligned pointers and when dereferenced at proper alignment will read 4 bytes (on most platforms) from the struct vs. the 1 bit that was specified.
JaredPar
@JaredPar: First line of this answer is "if every field in your struct is an int". Which it isn't, but then questioner also said "you shouldn't care what the type of the fields is", and we do care. A lot. Because bitfields are weird.
Steve Jessop
Interesting; this is an area of C syntax with which I'm not familiar. When he talked about bitfields I assumed he meant regular int fields against which one would apply bitmasks to extract certain values (e.g. the first four bits are the sequence number, the next 17 bits are the id tag, etc). I'll leave my solution just as a general example of how to do this kind of pointer manipulation, even if it turns out to not work with the weird struct type he's using.
Eli Courtwright
@onebyone, that's mainly what I wanted to call out because there are inherent contradictions in the OP's question. Also it's confusing when Eli says "if every field is an int" because the bit fields are techinically label'd as int but are in fact bit fields.
JaredPar
This would work if instead of automating the init function, you did it by hand, and maintained an 'offset array'. It would have to change whenever the struct changed, but it woudl enable you to be able to access things in the way he describes.
A: 

If the structure really is as simple as described, you might use a union with an array (or a cast to an array) and some bit-access magic (as in How do you set, clear and toggle a single bit in C?).

As Jared says, the general case is hard.

dmckee
Yes - so long as you're confident on how the data is stored, in terms of endianness and padding, etc.
Steve Melnikoff
A: 

Why not build getval() in to the struct?

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() would have knowledge of the internals of Whang, and could deal with whatever it contained.

Number8
"Why not build getval() in to the struct?" Because this is C, not C++?
Steve Jessop
Ack, it's been a long time since using plain C... Sorry for the misdirection.
Number8
+4  A: 

If your struct was anything except bitfields, you could just use array access, if I'm right in remembering that C guarantees that a series of members of a struct all of the same type, has the same layout as an array. If you know which bits in what order your compiler stores bitfields into integer types, then you could use shift/mask ops, but that's then implementation-dependent.

If you want to access bits by variable index, then it's probably best to replace your bitfields with an integer containing flag bits. Access by variable really isn't what bitfields are for: a1 ... an are basically independent members, not an array of bits.

You could do something like this:

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

And some of the repetition could be avoided by the trick where you include the same header multiple times, each time having defined a macro differently. The header expands that macro once for each 1 ... N.

But I'm not convinced it's worth it.

It does deal with JaredPar's point that you're in trouble if your struct mixes different types - here all the members accessed via a particular jump table must of course be of the same type, but they can have any old rubbish in between them. That still leaves the rest of JaredPar's points, though, and this is a lot of code bloat for really no benefit compared with the switch.

Steve Jessop
WOW. What a construction. It surely doesn't seem practical but I'll take some of the ideas you presented. thanks.
Leif Ericson
> if I'm right in remembering that C guarantees that a series of > members of a struct all of the same type, has the same layout as > an arrayThat's news to me. Anyone else heard this?Imagine you define a struct which is 5 bytes long. On a 32 bit machine that will typically leave you with 3 byte gaps. I wonder if an array would also show those gaps?
Blank Xavier
Depends on the members of the struct. A 5-byte struct containing 5 chars can come out with a size of 5 and no gaps in an array or a struct. A 5-byte struct containing an int32 and a char will come out with a size of 8 and gaps in both cases.
Steve Jessop
Here's some code to try on your compiler: http://pastebin.com/m4db82d26. If the array didn't have the padding, then the int member of bloo or blee would be misaligned in the 2nd, 3rd, 4th, 6th, etc, elements of any array.
Steve Jessop
A: 

I think your real solution is to not use bitfields in your struct, but instead define either a set type or a bit array.

plinth
I need a specific number of bits and the problem is that C doesn't guarantee any number of fixed bits for no type. For example, char is not guaranteed to have exactly 8 bits, and so on...
Leif Ericson
@Leif: then include <stdint.h> from C99, and use uint32_t to store 32 flags. If you're stuck on C89, then put in an assert that CHAR_BITS >= 8 and use chars, or else store 6 bits per char if you want to be unreasonably cautious: C's basic charset has more than 64 characters in it, so a char cannot be less than 6 bits. I think POSIX might guarantee CHAR_BIT = 8 anyway, can't remember.
Steve Jessop
@onebyone I'm afraid I can't use C99 libraries. We don't work with C99, and I can't use non-standard libraries as well. As for the assert idea, I'll look into it. I don't quite know what it is yet.Also, there is no upper bound for the number of bits in chars so the lower bound of 8 doesn't do much.
Leif Ericson
A: 

I suggest code generation. If your structures don't contain huge amount of fields you can auto generate routines for each field or for a range of fields and use them like:

val = getfield_aN( myobject, n );

or

val = getfield_foo( myobject );
Nick D
A: 

What about converting from bitfields to a bitmask?

Blank Xavier
A: 

If you want to access your structure using both element index:

int getval(struct Element *ep, int n)

and by name:

ep->a1

then you are stuck with some hard to maintain switch like method that everyone has suggested.

If, however, all you want to do is access by index and never by name, then you can be a bit more creative.

First off, define a field type:

typedef struct _FieldType
{
    int size_in_bits;
} FieldType;

and then create a structure definition:

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

The above defines a structure with five elements of size 1, 1, 1, 4 and 1 bits. The final {0} marks the end of the definition.

Now create an element type:

typedef struct _Element
{
    FieldType *fields;
} Element;

To create an instance of an 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;
}

And then to access an 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;
}

Setting values is similar to getting values, only the final part needs changing.

You can enhance the above by extending the FieldType structure to include information about the type of value stored: char, int, float, etc, and then write accessors for each type which checks the required type against the defined type.

Skizz

Skizz
A: 

If you have

  1. Only bitfields, or all the bitfields first in your struct
  2. less than 32 (or 64) bitfields

then this solution is for you.

#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;
}
gnud
Is this solution portable? It depends on the alignment of the bits inside a unit, doesn't it?
Leif Ericson
Although the idea of the union is kind of cool.
Leif Ericson
A: 
fabdan