Iterating over same type struct members in C

2019-01-19 06:40发布

问题:

Is it possible to iterate of a C struct, where all members are of same type, using a pointer. Here's some sample code that does not compile:

#include <stdio.h>
#include <stdlib.h>

typedef struct
{
    int mem1 ;
    int mem2 ;
    int mem3 ;
    int mem4 ;
} foo ;

void my_func( foo* data )
{
    int i ;
    int* tmp = data ; // This line is the problem

    for( i = 0; i < 4; ++i )
    {
        ++tmp ;
        printf( "%d\n", *tmp ) ;
    }
}

int main()
{
    foo my_foo ;
    //
    my_foo.mem1 = 0 ;
    my_foo.mem2 = 1 ;
    my_foo.mem3 = 2 ;
    my_foo.mem4 = 3 ;
    //
    my_func( &my_foo ) ;
    return 0 ;
}

The members of foo should be aligned in memory to be one after another, assuming your compiler/kernel does not try to provide stack protection for buffer overflow.

So my question is:

How would I iterate over members of a C struct that are of the same type.

回答1:

The easiest way would be to create a union, one part which contains each member individually and one part which contains an array. I'm not sure if platform-dependent padding might interfere with the alignment.



回答2:

Most of the attempts using a union with an array are prone to failure. They stand a decent chance of working as long as you only use int's, but for other, especially smaller, types, they're likely to fail fairly frequently because the compiler can (and especially with smaller types often will) add padding between members of a struct, but is not allowed to do so with elements of an array).

C does, however, have an offsetof() macro that you can use. It yields the offset of an item in a struct, so you can create an array of offsets, then (with a bit of care in casting) you can add that offset to the address of the struct to get the address of the member. The care in casting is because the offset is in bytes, so you need to cast the address of the struct to char *, then add the offset, then cast the result to the type of the member (int in your case).



回答3:

From the language point of view: you can't. data members of the struct are not... er.. "iteratible" in C, regardless of whether they are of the same type or of different types.

Use an array instead of a bunch of independent members.



回答4:

You can also use an unnamed union/struct:

struct foo {
    union {
        struct {
            int mem1;
            int mem2;
            int mem3;
            int mem4;
        };
        int elements[4];
    };
};

foo thefoo;
for (int i = 0; i < 4; ++i) {
    thefoo.elements[i] = i;
}

This might not work on some compilers, int this case you'll have to explicitily name the union and struct inside foo,



回答5:

    int* tmp = &data->mem1 ;  // explicitly get the address of the first int member

As you say, you have to be careful of alignment and other memory layout issues. You should be fine doing this with ints though.

But I would question how readable this would make your code.

Incidently,

    tmp += ( i * sizeof(foo) ) ;

I do not think that does what you think it does, but I'm not entirely sure what you want it to do. Use ++tmp; if you want to step to the next int member of the same foo object.



回答6:

Here's another approach; I've never had reason to do this, but it has the advantage of not mucking up the struct definition. Create a an array of pointers to the members you're interested in, and then iterate over that array:

typedef struct { int mem1; int mem2; int mem3, int mem4; } foo;
...
foo theStruct;
int *structMembers[4] = { &theStruct.mem1, &theStruct.mem2, 
                          &theStruct.mem3, &theStruct.mem4};
...
for (i = 0; i < 4; i++)
{
  printf("%d\n", *structMembers[i]);
}

This way you don't have to worry about alignment issues biting you, and you can arbitrarily order how you want to iterate over the members (e.g., you could order it so the walk is "mem4, mem3, mem2, mem1").



回答7:

You should be able to cast the foo pointer to an int pointer.

A cleaner solution would be to declare foo as follows.

typedef struct
{
    int fooints[ 4 ] ;
} foo ;

Then iterate over the int array.

for ( int i = 0 ; i < 4 ; i++ )
{
    printf( "%d\n" , *( foo->fooints + i ) ) ;
}

You could go on to create member functions to access and/or manipulate the int array.