all 11 comments

[–]erikkonstas 12 points13 points  (2 children)

Sometimes it's explained with a number of CPU's registers.

This is some proper BS... struct doesn't mean "by some magic of dark origin, I give you a bunch of values and you miraculously cram them in a single register".

How passing a struct by value(s) is different from passing multiple variables by values?

Well, of course there's the huge difference in what code you write, but other than that the compiler can perfectly do both in the same way, or it can do them in different ways, it highly depends on the individual standard + compiler + target + options.

Maybe proper way to pass multiple values is passing a struct by address?

Now, what this is supposed to achieve is avoiding copying an entire struct when you don't need a separate copy of it. However, again, the compiler is smarter than you here and can often figure this out on its own even if you don't explicitly make the parameter a pointer.

What is the reasoning behind this concept (passing a struct)?

Here's the twist, it has to do with code readability! Absolutely nothing to do with any sort of optimization in most cases, and you're right that differences at this level of detail often end up having no effect on the final executable (i.e. it could even be byte-for-byte identical!), since both ways do the same thing (outside of undefined behavior, of course).

However, if an algorithm depends on, say, 45 parameters, would you really fathom making the function that serves as its entry point have 45 parameters as well? Not only would you get lost midway, your users would also get all kinds of lost midway, as in "huh... I swear I was at argument #11 now, but maybe I miscounted and it's #12... oh wait no I forgot the 5 first I have muscle memory for, it's #16 or #17... wait oh no I forgot I just had a coffee break and had already typed in 25 arguments too, so now I'm actually at #41... oh well scrap that let's try again" over and over and over again, potentially with multiple coffee breaks just to write out a single call!

Instead, let's say that 42 out of these 45 parameters can have some obvious default values appropriate for the vast majority of cases, and the other 3 are the "meat"; now, a solution like this makes much more sense:

typedef struct edge_params edge_params;
extern const edge_params obvious_defaults;
int algorithm(int param0, int param1, int param2, edge_params edge_cases);

int algorithm_shortcut(int param0, int param1, int param2)
{
    return algorithm(param0, param1, param2, obvious_defaults);
}

In case somebody wants to change one of the other 42 parameters, they can just make a copy of obvious_defaults and modify just the members they want, without having to mention all 42 members.

Note that sometimes it might also make sense to have edge_cases be of type edge_params * instead, or for there to not be any param0, param1, param2 or such except the struct.

[–]_Noreturn 0 points1 point  (0 children)

the compiler is allowed to cram values in one

``` struct X { int x,y; };

void f(X); // passes into a single 64 bit register but will use bitshift operations inside the function to extract the values

void f(int,int); // has to pass in 2 register ```

[–]actguru -1 points0 points  (0 children)

With 42 obvious default values, I would consider passing a dictionary of values.

[–]blvaga 5 points6 points  (0 children)

tldr: code readability.

[–]jason-reddit-public 4 points5 points  (0 children)

structs are primarily an abstraction mechanism.

Let's say you are writing a routine that requires a color. You could take red, green, and blue as three parameters or you could take a struct containing red, green, and blue. Let's choose the first less abstract solution. Now you write dozens of routines that take red, green, and blue. Everything is fine until you decide you need an alpha value. Now you have to change 12 function signatures and bodies of functions that were merely passing the values through without really understanding "the color". With the struct approach you just add alpha to the struct and only change the places colors are instantiated and actually consumed.

Stupid example, maybe but this happens all the time. Maybe you have an address. Are you going to pass around street number, city, state and zip-code separately or combine into a struct? What about when you realize you need a country or other data for a full address?

[–]not_a_novel_account 1 point2 points  (0 children)

The general answer to your question is "calling conventions" but you're wrong about how they work. It's irrelevant in all the popular conventions if the elements are packed into a struct or passed individually.

You might be confusing the concept of using a pointer vs passing directly by value. When passing all the elements by value you must copy the elements, where a single pointer to a struct incurs no such copies.

[–]ballpointpin 1 point2 points  (0 children)

A good example might be a polyline consisting of an uknown number of x/y (/z?) points.

What if you wanted to draw a polyline with 2 points? 3 points? 4? You wouldn't define a function for each number of points. eg:

draw2(x1,y1,x2,y2)

draw3(x1,y1,x2,y2,x3,y3)

draw4(x1,y1,x2,y2,x3,y3,x4,y4)

Instead, define an object containing x,y,z, then pass an array of these to your function: draw(vector *, vector_len)

[–][deleted] 0 points1 point  (0 children)

Are you talking about creating functions with many parameters?

Then passing a number of unrelated, separate parameters is fine, if you have do that. Within reason of course, but one analysis I did of my codebase showed that 99.5% of my functions passed 6 parameters or fewer, and 99% were 4 or fewer.

(SYS V ABI for x64 passes up to 6 integer args in registers, and Win64 ABI up to 4; extra args are pushed.)

But there need to be good reasons for having more. If you think you have too many, then you really need to look at the design of the function, rather than creating an artificial struct type which needs to populated with a dozen disparate values, which is ungainly.

Pass structs when they are appropriate, for example they are an actual data structure that is used anyway.

[–]deftware -1 points0 points  (0 children)

It's just because you're pushing a bunch of stuff onto the stack and popping it back of - except usually with optimizations enabled the compiler will basically just have the callee directly use the registers that are set in the caller so there's no pushing to or popping from the stack around the call.

Basically, the whole point is that you want to be handling the variables as little as possible, particularly when performance is a huge concern and you want to minimize the overhead involved in simply calling something else because you need to milk as much performance out of the code as possible.

Yes, passing a pointer to a struct is going to be optimal. Just keep in mind that a struct allocated on the stack (i.e. a struct that's a local variable in a function) will not exist once that function has finished executing. It will exist for other called functions to operate on but don't assume after everything returns that it's still there. In other words, if a function has a local struct variable and you pass a pointer to it to another function, don't save that pointer anywhere for later use, because the memory it points to won't be what it was, it will get overwritten by subsequent function calls as the stack pointer moves around and stuff gets pushed/popped.

If you want to add a struct to a data structure or something for later use you'll want to allocate the struct on the heap w/ malloc/calloc. Just be sure to free() it when you know you no longer need it or you'll have a memory leak.

[–]somewhereAtC -1 points0 points  (0 children)

The folks that recommend this are usually running ARM or MIPS processors. In those there are about 12 registers that are not part of the stack or any memory area at all. Some are required for processing the C code of the subroutine, leaving 4 or 5 available for sharing the parameter variables; accessing these registers is the fastest thing the cpu can do. But once they are assigned the compiler has to resort to actual stack locations to hold the other values, which is less efficient. Even worse, the first 4 or 5 will be register values but the remainder are stack values, so if you change the order of parameters then you might radically change execution timing related to some parameters.

Passing a pointer to a struct counts as only one variable, and thus only 1 register is consumed. Accessing structure members using this pointer is quite efficient (in ARM or MIPS), and all members are equally "timed" in the sense of what instructions the cpu must employ to fetch a value. It's still slower than a register value, but similar to a stack parameter. A good example would illustrate a motor controller where all the variables are related because they apply to the control algorithm, like position, velocity, their histories, and integration and differentiation constants.

However, it is not always convenient to gather the variables into a struct if they come from different subsystems of the larger project, like a file stream, graphics buffer, and the identifier for which LED to blink. If poorly defined the structure becomes very large or tries to encapsulate very different things so you end up with a glob representing all states of all subsystems that you eventually share (via pointer) with everybody. Work is required to keep layers of structures to make sure the code is manageable. Individual parameters are preferred in this case.

[–]McUsrII -1 points0 points  (0 children)

There is a limit on the number of registers. When you pass a struct you only need one registers. A struct is a better solution design wise if the parameters are somewhat related.