views:

534

answers:

7

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  A: 
    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.

Dave Hinton
Be careful, this approach is sensitive to structure packing. I'm sure you can make it work, but I would advise against it.
Tim Sylvester
I tried this approach earlier, but getting the address of the first member did not work either. The address that it kept getting was wrong.
Misha M
Dave, ++tmp will not work for all architectures, hence the use of sizeof. Though you're right, that will not work either, sizeof(foo) needs to be sizeof(int). ThanksFixed in question
Misha M
I'm still not sure it's correct. Why `i * sizeof(int)`?
Nick Meyer
@Misha again, why will ++tmp not work for all architectures? I think it should advance tmp by the size of the thing it points to. I believe that's part of the C standard, if someone can point to the part of the standard that defines this, that would help.
Nick Meyer
Nick, you're right. Just looked it up and ++tmp would do the same thing. Thanks
Misha M
Technically, the offset between mem1 and mem2 doesn't have to be an `int`. There may be padding between members of a struct. In practice, I can't imagine padding `int`s.
David Thornley
+5  A: 

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.

AndreyT
A list or a map would actually work better here. Sometimes, it is not possible to change the architecture of the code, thus one has to make due with what is there.I don't agree with you regarding the language's point of view. C, as the language is defined, does not care.
Misha M
@Misha: The language, as defined, does care whether you have an array or a struct. You can iterate through an array, but not a struct.
David Thornley
+2  A: 

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.

Mark Ransom
Thanks for that idea, completely forgot about using a union here. I am worried about architecture dependencies here, though all of them are 32-bit, so should be ok.My concern in modifying the struct definition, would be the impact on the rest of the code that use it. Having to recompile libraries and other dependencies.
Misha M
As pointed out below, while this will work with many compilers and platforms, it may fail on others. Please do your maintainers a favor and write unit tests for this iteration, so when it breaks on some weird system someday, it will be immediately obvious.
emk
I agree, at the very least something like `assert((void*)`
Mark Ransom
emk, already done :)Mark, thanks for the example.
Misha M
A: 

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.

William Bell
+2  A: 

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,

Andreas Brinck
+7  A: 

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).

Jerry Coffin
+1 I think this is the most portable and safe approach
GogaRieger
Jerry, thanks. I'll investigate offsetof(). This does sound like the better solution then using a union.
Misha M
A: 

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").

John Bode