How to iterate through a struct as an array in C: Why does this option work the way it does?

Viewed 136

I recently came across a way to iterate through structs as if they were an array (as long as the value type is the same in all members of the struct) and it works as I expect it to and want it to. However, I am having trouble understanding why it works. Without understanding why it works, I do not feel comfortable using it, so I was hoping the community could help me out. Below is some code.

StructName is a generic name for my struct of many float values, and iterate is a generic function name. Please take note of the marked line as that is where I am confused on it's operation.

void iterate(StructName *s){
//some code
float *a;
a = (float*)&*s; //<---This line
//some more code
a++; //gets next struct member
}

This works as expected, I am able to iterate through my struct as an array. If I do the following (remove the (float*) from the marked line):

void iterate(StructName *s){
//some code
float *a;
a = &*s; //<---This line
//some more code
a++; //gets next struct member
}

I get the following warning:

warning: assignment to 'float *' from incompatible pointer type 'StructName *' {aka 'struct <anonymous> *'} [-Wincompatible-pointer-types]
     a = &*s;

But the code still compiles and runs as expected.

And then if I do the following (remove the asterisk from the marked line):

void iterate(StructName *s){
//some code
float *a;
a = (float*)&s; //<---This line
//some more code
a++; //gets next struct member
}

My indexing is largely offset by a seemingly random amount.

2 Answers

Iterating through members of a structure is not something that can be done in C. Attempting to do so invokes undefined behavior. From section 6.5.6 p8 of the C11 working draft:

When an expression that has integer type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integer expression. In other words, if the expression P points to the i -th element of an array object, the expressions (P)+N (equivalently, N+(P) ) and (P)-N (where N has the value n ) point to, respectively, the i+n -th and i−n -th elements of the array object, provided they exist. Moreover, if the expression P points to the last element of an array object, the expression (P)+1 points one past the last element of the array object, and if the expression Q points one past the last element of an array object, the expression (Q)-1 points to the last element of the array object. If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined. If the result points one past the last element of the array object, it shall not be used as the operand of a unary * operator that is evaluated.

(bold emphasis mine)

In this case, taking the address of float member of the array can be treated as an array of size 1.

If you want a data-structure that holds many floats which can be indexed as an array, use an array. If you have chosen a struct because you want nice readable names such as you get with struct members, you can use enums to index your array with nice readable names.

Here's a simple example:

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

enum float_array_enum {FLOAT_X=0, FLOAT_Y, FLOAT_Z, FLOAT_ARR_LENGTH};

int main(void){
   float my_array[FLOAT_ARR_LENGTH] = {0.f};
   for(enum float_array_enum i = FLOAT_X; i < FLOAT_ARR_LENGTH; ++i) {
      my_array[i] = (float) i;
   }
   printf("my_array[FLOAT_X]: %f\n", my_array[FLOAT_X]);
   printf("my_array[FLOAT_Y]: %f\n", my_array[FLOAT_Y]);
   printf("my_array[FLOAT_Z]: %f\n", my_array[FLOAT_Z]);
   return EXIT_SUCCESS;
}

The line a = (float*)&*s; does several things.

*s dereferences the struct. Then &*s gets a reference again, so &*s is identical to s unless s is not a valid pointer, in which case the conversion will fail. The point of &*s is to make sure the pointer points to a real struct.

Now, s is a pointer to the struct, so it basically points to the first element of the struct. We need to cast this to (float*) so that the compiler will know what size the pointer is pointing to, and how we want to treat it - so all that line does is get a pointer to the first struct element.

When incrementing a, the compiler already knows a is a float pointer so it increments a by the size of a float, thus getting the next element, and so on. This is why it will only work for structs with the same type of elements (and even that is not guaranteed, as the C standard allows padding between elements).

Your two examples fail because in the first, you don't cast the pointer to a float*, which creates an assignment of different types, and in the second, you try to cast a pointer to a pointer to the struct to a pointer, which makes no sense.

This is a fun experiment, however as others have mentioned it is not good practice, and you really shouldn't iterate through structs like that on a regular basis.

Related