Access struct members as if they are a single array?
Asked Answered
U

7

10

I have two structures, with values that should compute a pondered average, like this simplified version:

typedef struct
{
  int v_move, v_read, v_suck, v_flush, v_nop, v_call;
} values;

typedef struct
{
  int qtt_move, qtt_read, qtt_suck, qtd_flush, qtd_nop, qtt_call;
} quantities;

And then I use them to calculate:

average = v_move*qtt_move + v_read*qtt_read + v_suck*qtt_suck + v_flush*qtd_flush + v_nop*qtd_nop + v_call*qtt_call;

Every now and them I need to include another variable. Now, for instance, I need to include v_clean and qtt_clean. I can't change the structures to arrays:

typedef struct
{
    int v[6];
} values;
typedef struct
{
    int qtt[6];
} quantities;

That would simplify a lot my work, but they are part of an API that need the variable names to be clear.

So, I'm looking for a way to access the members of that structures, maybe using sizeof(), so I can treat them as an array, but still keep the API unchangeable. It is guaranteed that all values are int, but I can't guarantee the size of an int.

Writing the question came to my mind... Can a union do the job? Is there another clever way to automatize the task of adding another member?

Thanks, Beco

Unemployment answered 2/4, 2011 at 17:0 Comment(6)
To better help you, we should know how the api is...Paregmenon
@Paregmenon From the outside the programmer should be allowed to keep the old code running. That code use lines like: v.v_move=1. This can't break.Unemployment
A bit complicated but you can iterate over struct fields... look at this answerParegmenon
@Paregmenon thanks! Very nice work with #define. I think my question is easier because they are all guaranteed to be int. But nonetheless it is a tip one should keep in mind.Unemployment
Why can't I use const int *qt = &(quantities.qtt_move);? When compiling the line: qt[i]=3; it returns an error: assignment of read-only location ‘*(qt + (long unsigned int)((long unsigned int)i * 4ul)). It is not that I'm changing the pointer itself, but the dereference of some index!Unemployment
The answer for the comment above (const pointer) can be found here.Unemployment
M
7

If all members a guaranteed to be of type int you can use a pointer to int and increment it:

int *value = &(values.v_move);
int *quantity = &(quantities.qtt_move);
int i;
average = 0;
// although it should work, a good practice many times IMHO is to add a null as the last member in struct and change the condition to quantity[i] != null.
for (i = 0; i < sizeof(quantities) / sizeof(*quantity); i++)
    average += values[i] * quantity[i];

(Since the order of members in a struct is guaranteed to be as declared)

Metritis answered 2/4, 2011 at 17:6 Comment(16)
is it guaranteed that there's no padding between fields? sounds reasonable in this case, just wondering.Incoercible
@Mat: sorry, what is a padding? I think this solution looks promising.Unemployment
@Incoercible - interesting question. I think that even padding will not change in this case, since the pointer should be incremented respectively.Metritis
@Dr Beco: see the Data Structure Alignement wikipedia page.Incoercible
@Metritis Just one thing: how can I know the last index? Some sizeof, maybe?Unemployment
@Dr Beco - say you have a struct like this one: {char b; int i;}. the sizeof the struct will probably be 8 bytes (assuming 32bit machine) since the order of parameters is as declared, but an int has to be 4 bytes aligned. if you change the order, the size of the struct will probably be 5. try it yourself.Metritis
@Dr beco - if the size of the struct is known to the compiler, than yes, sizeof will do the job, please see a small fix to the answer though.Metritis
@Metritis One doubt: how do I add a null in the structure? I mean, the fields are int, not pointers. Maybe a int v_last and make it v_last=0?Unemployment
@MByD: a struct {int i; char b;} occupies the exact same size as another struct {char b; int i;}. The padding still occurs but at a different offset.Beavers
@Incoercible Thanks, but since all members are guaranteed to be int, I think I'll not have padding problems here.Unemployment
@MByD: what I find in the Standard is 6.7.2.1/14 "There may be unnamed padding at the end of a structure or union". The idea is that each element an array of struct{int i; char b;} is properly alignedBeavers
@Metritis it is not working... Maybe it is just a typo somewhere. Is the line sizeof(quantities) / sizeof(*quantity) the same as sizeof(quantities) / sizeof(int)? About the last field in the structure being NULL, do you have any clarification? Thanks.Unemployment
@Dr. Beco- it should be the same. What is the result of this division? (I'll be able to check when I'm near a computer)Metritis
@Metritis It is working. The problem was just minor typos (plural and singular confusion). It is giving me 16/4 (for 4 members) and 24/4 (for 6 members), that is exactly what it should do. Talking about typos, the average is average += value[i] * quantity[i];. Note the singular value and the *. Thanks! ;)Unemployment
Is it necessary to declare all of them in multiple lines, each having its own int, i.e. int v_move; int v_read; and so on? Because as of now, I have 2 lines for 2 groups: int stuff1, stuff2, stuff3; int stuff4, stuff5, stuff6;. (Was it clear?) TxUnemployment
I always declare each in different line but I don't think it's necessaryMetritis
L
20

What you are trying to do is not possible to do in any elegant way. It is not possible to reliably access consecutive struct members as an array. The currently accepted answer is a hack, not a solution.

The proper solution would be to switch to an array, regardless of how much work it is going to require. If you use enum constants for array indexing (as @digEmAll suggested in his now-deleted answer), the names and the code will be as clear as what you have now.

If you still don't want to or can't switch to an array, the only more-or-less acceptable way to do what you are trying to do is to create an "index-array" or "map-array" (see below). C++ has a dedicated language feature that helps one to implement it elegantly - pointers-to-members. In C you are forced to emulate that C++ feature using offsetof macro

static const size_t values_offsets[] = { 
  offsetof(values, v_move),
  offsetof(values, v_read),
  offsetof(values, v_suck),
  /* and so on */
};

static const size_t quantities_offsets[] = { 
  offsetof(quantities, qtt_move),
  offsetof(quantities, qtt_read),
  offsetof(quantities, qtt_suck),
  /* and so on */
};

And if now you are given

values v;
quantities q;

and index

int i;

you can generate the pointers to individual fields as

int *pvalue = (int *) ((char *) &v + values_offsets[i]);
int *pquantity = (int *) ((char *) &q + quantities_offsets[i]);

*pvalue += *pquantity;

Of course, you can now iterate over i in any way you want. This is also far from being elegant, but at least it bears some degree of reliability and validity, as opposed to any ugly hack. The whole thing can be made to look more elegantly by wrapping the repetitive pieces into appropriately named functions/macros.

Lithometeor answered 2/4, 2011 at 17:38 Comment(7)
Hi @AndreyT This is really a deep answer. Thanks! For a matter of simplicity I'll stick with the ugly hack, but I'll teach my students this proper way. :D I assume you use individual offsets because there is no guarantee that int *value = &(values.v_move); and value[2] is pointing to the second member? Is that so? Even having only integers in the structure? Thanks (BTW: +1!)Unemployment
Do you consider that the __attribute__ ((packed)) suggestion by @Jason prettify the ugly hack shown by @MByD? I mean, does the padding and aligning problem solves? Remember, it is a struct composed with only int, and even if I don't know the size of an int in a given machine, sizeof does. Thanks for your input.Unemployment
you can see through deleted answers ! :O Eheh, I deleted it because OP request was to keep API compatibility... Anyway, I agree this solution is better than the accepted one :)Paregmenon
With 51K he can see through our body like superman x-ray! @digEmAll, I can't, but thanks for trying to help. :)Unemployment
Are you of the view that *(int *)((char *)&v + sizeof(int)) is OK but *((int *)&v + 1) is not?Goethite
Before the publication of C89, the behavior was recognized as useful on platforms where the struct members would get placed at appropriate offsets. Compilers for such platforms essentially unanimously supported it, and judging from the C89 rationale it seems likely the authors would have regarded it as a "popular extension" which quality compilers for such platforms would support whether or not the Standard compelled them to do so. Some compiler writers would rather view such a construct as a "hack" and require programmers to write extra code to work around its absence than support it, but...Mcintosh
...even if a compiler can take the clunky workaround code and turn it into machine code equivalent to what the programmer wanted to write in the first place, it would be more efficient in terms of both human and compiler effort to simply recognize the original useful construct on platforms where it makes sense.Mcintosh
D
8

Writing the question came to my mind... Can a union do the job? Is there another clever way to automatize the task of adding another member?

Yes, a union can certainly do the job:

union
{
  values v;    /* As defined by OP */
  int array[6];
} u;

You can use a pointer to u.values in your API, and work with u.array in your code.

Personally, I think that all the other answers break the rule of least surprise. When I see a plain struct definition, I assume that the structure will be access using normal access methods. With a union, it's clear that the application will access it in special ways, which prompts me to pay extra attention to the code.

Disturbance answered 4/4, 2011 at 8:30 Comment(2)
The more I study this problem, the more I grow found of this solution.Unemployment
It should be pointed out that this solution is implementation-defined as there may be padding in the struct: #47471898Roband
M
7

If all members a guaranteed to be of type int you can use a pointer to int and increment it:

int *value = &(values.v_move);
int *quantity = &(quantities.qtt_move);
int i;
average = 0;
// although it should work, a good practice many times IMHO is to add a null as the last member in struct and change the condition to quantity[i] != null.
for (i = 0; i < sizeof(quantities) / sizeof(*quantity); i++)
    average += values[i] * quantity[i];

(Since the order of members in a struct is guaranteed to be as declared)

Metritis answered 2/4, 2011 at 17:6 Comment(16)
is it guaranteed that there's no padding between fields? sounds reasonable in this case, just wondering.Incoercible
@Mat: sorry, what is a padding? I think this solution looks promising.Unemployment
@Incoercible - interesting question. I think that even padding will not change in this case, since the pointer should be incremented respectively.Metritis
@Dr Beco: see the Data Structure Alignement wikipedia page.Incoercible
@Metritis Just one thing: how can I know the last index? Some sizeof, maybe?Unemployment
@Dr Beco - say you have a struct like this one: {char b; int i;}. the sizeof the struct will probably be 8 bytes (assuming 32bit machine) since the order of parameters is as declared, but an int has to be 4 bytes aligned. if you change the order, the size of the struct will probably be 5. try it yourself.Metritis
@Dr beco - if the size of the struct is known to the compiler, than yes, sizeof will do the job, please see a small fix to the answer though.Metritis
@Metritis One doubt: how do I add a null in the structure? I mean, the fields are int, not pointers. Maybe a int v_last and make it v_last=0?Unemployment
@MByD: a struct {int i; char b;} occupies the exact same size as another struct {char b; int i;}. The padding still occurs but at a different offset.Beavers
@Incoercible Thanks, but since all members are guaranteed to be int, I think I'll not have padding problems here.Unemployment
@MByD: what I find in the Standard is 6.7.2.1/14 "There may be unnamed padding at the end of a structure or union". The idea is that each element an array of struct{int i; char b;} is properly alignedBeavers
@Metritis it is not working... Maybe it is just a typo somewhere. Is the line sizeof(quantities) / sizeof(*quantity) the same as sizeof(quantities) / sizeof(int)? About the last field in the structure being NULL, do you have any clarification? Thanks.Unemployment
@Dr. Beco- it should be the same. What is the result of this division? (I'll be able to check when I'm near a computer)Metritis
@Metritis It is working. The problem was just minor typos (plural and singular confusion). It is giving me 16/4 (for 4 members) and 24/4 (for 6 members), that is exactly what it should do. Talking about typos, the average is average += value[i] * quantity[i];. Note the singular value and the *. Thanks! ;)Unemployment
Is it necessary to declare all of them in multiple lines, each having its own int, i.e. int v_move; int v_read; and so on? Because as of now, I have 2 lines for 2 groups: int stuff1, stuff2, stuff3; int stuff4, stuff5, stuff6;. (Was it clear?) TxUnemployment
I always declare each in different line but I don't think it's necessaryMetritis
S
4

It really sounds as if this should have been an array since the beggining, with accessor methods or macros enabling you to still use pretty names like move, read, etc. However, as you mentioned, this isn't feasible due to API breakage.

The two solutions that come to my mind are:

  • Use a compiler specific directive to ensure that your struct is packed (and thus, that casting it to an array is safe)
  • Evil macro black magic.
Safelight answered 2/4, 2011 at 17:25 Comment(1)
+1 Is there an ascii emoticon for dr. evil's baby finger? :)Selfseeker
A
4

How about using __attribute__((packed)) if you are using gcc?

So you could declare your structures as:

typedef struct
{
    int v_move, v_read, v_suck, v_flush, v_nop, v_call;
} __attribute__((packed)) values;

typedef struct 
{
    int qtt_move, qtt_read, qtt_suck, qtd_flush, qtd_nop, qtt_call;
} __attribute__((packed)) quantities;

According to the gcc manual, your structures will then use the minimum amount of memory possible for storing the structure, omitting any padding that might have normally been there. The only issue would then be to determine the sizeof(int) on your platform which could be done through either some compiler macros or using <stdint.h>.

One more thing is that there will be a performance penalty for unpacking and re-packing the structure when it needs to be accessed and then stored back into memory. But at least you can be assured then that the layout is consistent, and it could be accessed like an array using a cast to a pointer type like you were wanting (i.e., you won't have to worry about padding messing up the pointer offsets).

Thanks,

Jason

Alialia answered 2/4, 2011 at 19:40 Comment(7)
@Jason: Sounds good. I'll look into it in more details. Is this portable? Thanks.Unemployment
+1 Good tip that complements the answer. Just that the correct syntax, because of typedef is typedef struct { int v_move; ...} __attribute__ ((packed)) values;. I'm just not sure if this is portable, because the program runs in more than one OS. ThanksUnemployment
thanks, syntax is fixed now ... as far as portability, I've been able to port this type of code to any platform that gcc supports. But as I mentioned in my post, you may have to use some compiler macros, etc. to make sure you are getting the sizeof(int) correct for that specific platform.Alialia
Do I really need to take that into account? Doesn't sizeof( int ) always tells the truth about a given machine? sizeof at wikipediaUnemployment
It should ... and this isn't really directed at you, i.e., for someone reading this thread later on who may not have the experience you have, I wouldn't want them to assume that sizeof(int) is automatically 4 bytes and start doing "clever" stuff like int a = (int)(*((char*)&structure + (4 * offset_value));Alialia
-1 This is a really bad advice. 1) It does not solve the original problem, as a struct of int:s will have the same memory layout as an array of int:s. 2) It will reduce the alignment of the struct to 1, forcing the generated code to access the elements byte by byte. 3) The elements can't be accessed any other way, e.g. using an int *. 4) It is GCC specific.Disturbance
How can you be assured that a struct of ints will have the same memory layout as an array of ints? There can be padding introduced by the compiler, and it will depend on the platform as well. So I don't see how #1 and #2 are assured per the C99 spec. I also mentioned in the post the caveats in #3 and #4, that is this was gcc-specific and that there would be a performance penalty because of the packing. I don't see why this deserved a down-vote.Alialia
C
3

this problem is common, and has been solved in many ways in the past. None of them is completely safe or clean. It depends on your particuar application. Here's a list of possible solutions:

1) You can redefine your structures so fields become array elements, and use macros to map each particular element as if it was a structure field. E.g:

struct values { varray[6]; };
#define v_read varray[1]

The disadvantage of this approach is that most debuggers don't understand macros. Another problem is that in theory a compiler could choose a different alignment for the original structure and the redefined one, so the binary compatibility is not guaranted.

2) Count on the compiler's behaviour and treat all the fields as it they were array fields (oops, while I was writing this, someone else wrote the same - +1 for him)

3) create a static array of element offsets (initialized at startup) and use them to "map" the elements. It's quite tricky, and not so fast, but has the advantage that it's independent of the actual disposition of the field in the structure. Example (incomplete, just for clarification):

int positions[10];
position[0] = ((char *)(&((values*)NULL)->v_move)-(char *)NULL);
position[1] = ((char *)(&((values*)NULL)->v_read)-(char *)NULL);
//...
values *v = ...;
int vread;
vread = *(int *)(((char *)v)+position[1]);

Ok, not at all simple. Macros like "offsetof" may help in this case.

Carnotite answered 2/4, 2011 at 17:32 Comment(3)
Thanks @Giuseppe - my grandpa's name! ;)Unemployment
It looks like your third solution is related to the solution given by @AndreyT, but in a different syntax.Unemployment
Yes. I have had to be more syntetic because I wanted to report shortly the most common solutions instead of describe in depth one. AndreyT's answer is much more detailed.Carnotite
L
1

This is a really simple approach which means its probably very dangerous, but so far it's not bitten me:

struct jkData {
    float c1,c2,c3,c4,c5,c6,c7,c8,c9,c10,c11,c12,c13,c14,c15,c16,c17,c18,c19,c20;
};

jkData jkD;

// The casting hack... (Not that proud but it works...)
float * jkDarray = (float *) &jkD;

// Set some values up.
jkD.c1 = 101;
jkD.c2 = 254;
..
jkD.c20 = 31;

// Iterate through as an array.
for (uint8_t i=0; i<21; i++)
{
   someDestArray[i] = jkDarray[i];
}
Lubin answered 22/3 at 16:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.