While there are some complications involving accessing a structure member versus allocating a “direct” object, a more important difference in your scenario is that a compiler may be able to better optimize uses of a local variable than uses of a member of a structure it is passed.
This is because structures are commonly passed indirectly, by a pointer (that is either a parameter or a pointer from another structure). Consider this code:
struct S { int a; };
void baz(int);
void foo(struct S *s)
{
baz(s->a);
baz(s->a);
}
void bar(struct S *s)
{
int a = s->a;
baz(a);
baz(a);
}
In bar, the compiler knows that baz cannot change a, because a is a local variable whose address is never taken. Therefore, the compiler can load s->a from memory once and pass it to baz twice.
In foo, the compiler cannot know that baz does not change s->a, because baz could have some way of accessing s->a such as through an address stored in an external object. Therefore, it has to load s->a before the first call to baz and again between the first call and the second call.
Testing with Apple Clang 11.0.0 with optimization enabled confirms this; the assembly code generated for the foo routine contains two loads of s->a from memory, while the code for bar contains only one.
Although this occurs frequently for structures since structures are commonly passed by address, this is not inherently a consequence of being a structure member. The same issue occurs in this code:
void baz(int);
void foo(int *p)
{
baz(*p);
baz(*p);
}
void bar(int *p)
{
int a = *p;
baz(a);
baz(a);
}
It can also occur even if no subroutine is called; parameters can “interfere” with other parameters. In the following code, foo must reload z->a but bar does not need to. Even though z is qualified with const, the assignment to x might change it:
struct S { int a; };
void foo(struct S *x, struct S *y, const struct S *z)
{
x->a = z->a;
y->a = z->a;
}
void bar(struct S *x, struct S *y, const struct S *z)
{
int a = z->a;
x->a = a;
y->a = a;
}