views:

187

answers:

6

Is it possible to get access to an individual member of a struct or class without knowing the names of its member variables? I would like to do an "offsetof(struct, tyname)" without having the struct name or member variable name hard coded amoungst other things. thanks.

+4  A: 

Sure. If you have a struct and you know the offset and the type of the member variable, you can access it using pointers.

struct my_struct {
    int member1;
    char member2;
    short member3;
    char member4;
}

...

struct my_struct obj;
short member3 = *((short*)((char*)&obj + 5));

That'll get the value of member3, which is 5 bytes on from the start of obj on an x86 computer. However, you want to be careful. First of all, if the struct changes, your data will be garbage. We're casting all over the place, so you get no type safety, and the compiler won't warn you if something's awry. You'll also need to make sure the compiler's not packing the struct to align variables to word boundaries, or the offset will change.

This isn't a pleasant thing to do, and I'd avoid it if I were you, but yes, it can be done.

Samir Talwar
It is probably 6 bytes offset - because `short` is usually aligned on a multiple of 2 bytes. There are a couple of gaps in your structure, and it probably `sizeof(struct my_struct) == 12`.
Jonathan Leffler
Yeah, I just wrote a program to find out and it looks like you're right. This is the danger: it's *usually* aligned on a multiple of 2 bytes, but not always. Cheers for making it crystal clear.
Samir Talwar
You should cast to `char*`, not `void*`, since `sizeof(char)==1`. The C standard does not define `sizeof(void*)`, so doing pointer arithmetic with `void*` is undefined behavior. As an extension, gcc lets you do so with `sizeof(void*)==1`, but it issues a warning when it does so.
Adam Rosenfield
@Adam: Thanks. Updated.
Samir Talwar
@Adam: You mean `sizeof(void)`? `sizeof(void*)` is defined, and when doing arithmetic on `T*` it's `sizeof(T)` that is considered.
GMan
Err yes, I mean `sizeof(void)`, thanks.
Adam Rosenfield
Note that this entire thing is completely implementation defined because the compiler is allowed to insert padding between the members of a struct.
Billy ONeal
+1 for warning about alignment issues
George Edison
A: 

Somewhere in your code you need to reference the data member in the struct. However you can create a variable that is a pointer to a struct data member and from then on you no longer need to reference it by name.

struct foo
{
    int member1;
    int member2;
};

typedef int (foo::*intMemberOfFoo);

intMemberOfFoo getMember()
{
    if (rand() > RAND_MAX / 2) return &foo::member1;
    else return &foo::member2;
}

foo f;
void do_somthing()
{
    intMemberOfFoo m = getMember();
    f.*m = 0;
}
Stephen Nutt
+1  A: 

C and C++ are compiled languages without built-in "reflection" features. This means that regardless of what you do and how you do it, one way or another the path will always start from an explicit hard-coded value, be that a member name or an compile-time offset value. That means that if you want to select a struct member based on some run-time key, you have no other choice but to manually create a mapping of some kind that would map the key value to something that identifies a concrete struct member.

In C++ in order to identify a struct member at run-time you can use such feature as pointers-to-members. In C your only choice is to use an offset value.

Another issue is, of course, specifying the type of the members, if your members can have different types. But you provided no details about that, so I can't say whether you need to deal with it or not.

AndreyT
+1  A: 

We had a similar problem some years ago: A huge struct of configuration information that we wanted to reflect on. So we wrote a Perl script to find the struct, parse its members, and output a C++ file that looked like:

struct ConfField
{    const char* name;
     int type;
     size_t offset;
};

ConfField confFields[] = {
    { "version", eUInt32, 0 },
    { "seqID", eUInt32, 4 },
    { "timestamp", eUInt64, 8 },
    // ... lots more ...
    { 0, 0, 0 }
};

And we'd feed the script with the output from gcc -E.

Nowadays, I understand that gccxml can output an XML file representing any C++ source that gcc can compile, since it actually uses the g++ front end to do the parsing. So I'd recommend pairing it with an XML-parsing script (I'd use Python with the lxml library) to find out everything you ever wanted to know about your C++ source.

Mike DeSimone
A: 

The technical answer is 'yes' because C++ is Turing-complete and you can do almost anything if you try hard enough. The more practical answer is probably 'no' since there is no safe and easy way of doing exactly what you want.

I agree with GMan. What exactly are you trying to do that makes you think you need this technique?

SigmaXiPi
A: 

Well you will have to set up some stuff first, but it can be done. Expanding on Samir's response

struct my_struct {
    int member1;
    char member2;
    short member3;
    char member4;
}

you can create a table of offsets:

my_struct tmp;
int my_struct_offsets[4]={
  0,
  (char*)&(tmp.member2)-(char*)&(tmp.member1),
  (char*)&(tmp.member3)-(char*)&(tmp.member1),
  (char*)&(tmp.member4)-(char*)&(tmp.member1)
}

this will take into account different alignments on different systems

miked