В варианте со ссылкой, верней с указателем если в голом C: struct S {...};
void f(struct S *s)
{
s->a = 1, s->b = 2...
}
В варианте с возвратом по значению:
struct S f(void)
{
return (struct S){ 1, 2... };
}
В первом случае память выделяется на стеке вызывающей функции. Во втором теоретически память выделяется и в вызывающей функции и в вызываемой. Потом в вызываемой формируется структура, копируется на вершину стека, происходит возврат из функции, структура копируется в выделенную память... Но это теоретически. Практически в ещё вызываемой функции всё пишется прямо в нужные ячейки стека и вызывающая функция прямо их использует потом. ВСЁ. Практическая часть начинает быть сложной только в очень нетривиальных случаях, когда и по значению возвращать совершенно не нужно.
Кроме того, если функция статическая (а любую функцию, если она не вызывается из других модулей НУЖНО ДЕЛАТЬ СТАТИЧЕСКОЙ, чтоб оптимизатор мог вообще нормально работать), оптимизация может пойти намного дальше и разницы думать как там что через какие регистры передаётся -- не стоит вообще. Компилятор в любом случае сделает лучше тебя, практически всегда. А преждевременная оптимизация может дать только багоопасный нечитаемый код.
Примеры:
Compiler Explorer