if I have a struct like
typedef struct {
idx_type type;
union {
char *str;
int num;
} val
} cust_idx;
and I have loops like this
for (i = 0; i < some_get(x); i++) {
some_fun(z, NULL, i);
}
that I want to refactor to use the struct like some_fun(z, idx)
where idx
is one of my cust_idx
structs, would it be best to keep i
as the loop counter and update idx
or change the for header to use idx.val.num
instead of i
?
For the purposes of this, assume idx_type
is an enum for string and number types, and all other fields will have macros, but I'm only going to use the IDX_NUM
macro here as I'm not worried about anything to do with idx.type
.
To sum up my concerns:
#define IDX_NUM(x) (x.val.num)
...
cust_idx j;
j.type = TYPE_num;
for (IDX_NUM(j) = 0; IDX_NUM(j) < some_get(x); IDX_NUM(j)++) {
some_fun(z, j);
}
This does the same as the original, but the using struct field/macro extends and complicates the for loop header in my opinion but it's still fairly understandable.
cust_idx j;
j.type = TYPE_num;
for (i = 0; i < some_get(x); i++) {
IDX_NUM(j) = i;
some_fun(z, j);
}
This results in the least changes from old code logically, but will end in by far the largest amount of code due to the add assignment lines.
cust_idx j;
int *i = &(j.val.num);
j.type = TYPE_num;
for ((*i) = 0; (*i) < some_get(x); (*i)++) {
some_fun(z, j);
}
I'm not sure how good this would be in the long run, or if it's advised against.
As to readability, I would always prefer separate loop counters.
EDIT: The following in italic is not right in this specific case as C structs by default are passed as value copies over the stack, so passing j to some_fun() in the loop is ok. But I'll leave the caveat here, as it applies to many similar situations, where the struct or array is passed by a pointer value. (aka 'passed by reference').
That is especially true in code like you posted, where you call a function with the structure as an argument inside the loop. If I don't know what some_fun() does, I can only hope that the struct's member that I use as a loop counter is not modified. And hope is not a strategy.
So, unless there are very hard reasons for doing otherwise, I'd always place readability first. Remember: If you write code that is at the limits of your own syntactic and semantic capabilities, you will have very little fun debugging such code, as debugging is an order of magnitude harder than writing (buggy) code. ;)
Addition: You could look at the disassemblies of all variants. The compiler might do a lot of optimizations here, especially if it can 'see' some_fun().