Are there any downsides to passing structs by value in C, rather than passing a pointer?

Viewed 95671

Are there any downsides to passing structs by value in C, rather than passing a pointer?

If the struct is large, there is obviously the performance aspect of copying lots of data, but for a smaller struct, it should basically be the same as passing several values to a function.

It is maybe even more interesting when used as return values. C only has single return values from functions, but you often need several. So a simple solution is to put them in a struct and return that.

Are there any reasons for or against this?

Since it might not be obvious to everyone what I'm talking about here, I'll give a simple example.

If you're programming in C, you'll sooner or later start writing functions that look like this:

void examine_data(const char *ptr, size_t len)
{
    ...
}

char *p = ...;
size_t l = ...;
examine_data(p, l);

This isn't a problem. The only issue is that you have to agree with your coworker in which the order the parameters should be so you use the same convention in all functions.

But what happens when you want to return the same kind of information? You typically get something like this:

char *get_data(size_t *len);
{
    ...
    *len = ...datalen...;
    return ...data...;
}
size_t len;
char *p = get_data(&len);

This works fine, but is much more problematic. A return value is a return value, except that in this implementation it isn't. There is no way to tell from the above that the function get_data isn't allowed to look at what len points to. And there is nothing that makes the compiler check that a value is actually returned through that pointer. So next month, when someone else modifies the code without understanding it properly (because he didn't read the documentation?) it gets broken without anyone noticing, or it starts crashing randomly.

So, the solution I propose is the simple struct

struct blob { char *ptr; size_t len; }

The examples can be rewritten like this:

void examine_data(const struct blob data)
{
    ... use data.tr and data.len ...
}

struct blob = { .ptr = ..., .len = ... };
examine_data(blob);

struct blob get_data(void);
{
    ...
    return (struct blob){ .ptr = ...data..., .len = ...len... };
}
struct blob data = get_data();

For some reason, I think that most people would instinctively make examine_data take a pointer to a struct blob, but I don't see why. It still gets a pointer and an integer, it's just much clearer that they go together. And in the get_data case it is impossible to mess up in the way I described before, since there is no input value for the length, and there must be a returned length.

11 Answers

For small structs (eg point, rect) passing by value is perfectly acceptable. But, apart from speed, there is one other reason why you should be careful passing/returning large structs by value: Stack space.

A lot of C programming is for embedded systems, where memory is at a premium, and stack sizes may be measured in KB or even Bytes... If you're passing or returning structs by value, copies of those structs will get placed on the stack, potentially causing the situation that this site is named after...

If I see an application that seems to have excessive stack usage, structs passed by value is one of the things I look for first.

One reason not to do this which has not been mentioned is that this can cause an issue where binary compatibility matters.

Depending on the compiler used, structures can be passed via the stack or registers depending on compiler options/implementation

See: http://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html

-fpcc-struct-return

-freg-struct-return

If two compilers disagree, things can blow up. Needless to say the main reasons not to do this are illustrated are stack consumption and performance reasons.

One thing people here have forgotten to mention so far (or I overlooked it) is that structs usually have a padding!

struct {
  short a;
  char b;
  short c;
  char d;
}

Every char is 1 byte, every short is 2 bytes. How large is the struct? Nope, it's not 6 bytes. At least not on any more commonly used systems. On most systems it will be 8. The problem is, the alignment is not constant, it's system dependent, so the same struct will have different alignment and different sizes on different systems.

Not only that padding will further eat up your stack, it also adds the uncertainty of not being able to predict the padding in advance, unless you know how your system pads and then look at every single struct you have in your app and calculate the size for it. Passing a pointer takes a predictable amount of space -- there is no uncertainty. The size of a pointer is known for the system, it is always equal, regardless of what the struct looks like and pointer sizes are always chosen in a way that they are aligned and need no padding.

Simple solution will be return an error code as a return value and everything else as a parameter in the function,
This parameter can be a struct of course but don't see any particular advantage passing this by value, just sent a pointer.
Passing structure by value is dangerous, you need to be very careful what are you passing are, remember there is no copy constructor in C, if one of structure parameters is a pointer the pointer value will be copied it might be very confusing and hard to maintain.

Just to complete the answer (full credit to Roddy ) the stack usage is another reason not pass structure by value, believe me debugging stack overflow is real PITA.

Replay to comment:

Passing struct by pointer meaning that some entity has an ownership on this object and have a full knowledge of what and when should be released. Passing struct by value create a hidden references to the internal data of struct (pointers to another structures etc .. ) at this is hard to maintain (possible but why ?) .

I'd say passing (not-too-large) structs by value, both as parameters and as return values, is a perfectly legitimate technique. One has to take care, of course, that the struct is either a POD type, or the copy semantics are well-specified.

Update: Sorry, I had my C++ thinking cap on. I recall a time when it was not legal in C to return a struct from a function, but this has probably changed since then. I would still say it's valid as long as all the compilers you expect to use support the practice.

I think that your question has summed things up pretty well.

One other advantage of passing structs by value is that memory ownership is explicit. There is no wondering about if the struct is from the heap, and who has the responsibility for freeing it.

Taking into account all of the things people have said...

  1. Returning a struct was not always allowed in C. Now it is.
  2. Returning a struct can be done in three ways... a. Returning each member in a register (probably optimal, but unlikely to be the actual...) b. Returning the struct in the stack (slower than registers, but still better than a cold access of heap ram... yay caching!) c. Returning the struct in a pointer to the heap (It only hurts you when you read or write to it? A Good compiler will pass the pointers it read just once and tried to access, did instruction reordering and accesses it much earlier than needed so it was ready when you were? to make life better? (shiver))
  3. Different compiler settings can cause different problems when the code interfaces because of this. (Different size registers, different amounts of padding, different optimizations turned on)
  4. const-ness or volatile-ness doesn't permeate through a struct, and can result in some miserably un-efficient or possibly lead to broken code (E.G. a const struct foo does not result in foo->bar being const.)

Some simple measures I will take after reading this...

  1. Make your functions accept parameters rather than structs. It allows fine grained control over const-ness and volatile-ness etc, it also ensures that all the variables passed are relevant to the function using them. If the parameters are all the same kind, use some other method to enforce ordering. (Make type defs to make your function calls more strongly typed, which an OS does routinely.)
  2. Instead of allowing the final base function to return a pointer to a structure made in the heap, provide a pointer to a struct to put the results into. that struct still might be in the heap, but it is possible that the struct is actually in the stack - and will get better runtime performance. It also means that you do not need to rely on compilers providing you a struct return type.
  3. By passing the parameters as pieces and being clear about the const-ness, volatile-ness, or the restrict-ness, you better convey your intentions to the complier and that will allow it to make better optimizations.

I am not sure where 'too big' and 'too small' is at, but I guess the answer is between 2 and register count + 1 members. If I made a struct that holds 1 member that is an int, then clearly we should not pass the struct. (Not only is it inefficient, it also makes intention VERY murky... I suppose it has a use somewhere, but not common)

If I make a struct that holds two items, it might have value in clarity, as well as compliers might optimize it into two variables that travel as pairs. (risc-v specifies that a struct with two members returns both members in registers, assuming they are ints or smaller...)

If I make a structure that holds as many ints and double as there are in the registers for in the processor, it is TECHNICALLY a possible optimization. The instance I surpass the register amounts though, it probably would have been worth it to keep the result struct in a pointer, and pass in only the parameters that were relevant. (That, and probably make the struct smaller and the function do less, because we have a LOT of registers on systems nowadays, even in the embedded world...)

Related