10

[Note: This is reposted from https://softwareengineering.stackexchange.com/q/369604/126197, where for some reason this question got immediately downvoted. Twice. Clearly there's more love over here!]

Here's a bit of code paraphrased from a vendor's example.

I've looked for authoritative documentation on passing stack-allocated structures by value, but haven't found the definitive word. In a nutshell: Does C99 guarantee this to be safe?

typedef struct {
    int32_t upper;
    int32_t lower;
} boundaries_t;

static boundaries_t calibrate() {
    boundaries_t boundaries;         // struct allocated on stack

    boundaries.upper = getUpper();
    boundaries.lower = getLower();
    return boundaries;               // return struct by value
}

int main() {
    boundaries_t b;

    b = calibrate();
    // do stuff with b
    ...
}

Note that calibrate() allocates the boundaries struct on the stack and then returns it by value.

If the compiler can guarantee that the stack frame for calibrate() will be intact at the time of the assignment to b, then all is well. Perhaps that's part of the contract in C99's pass-by-value?

(Context: my world is embedded systems where pass-by-value is rarely seen. I do know that returning a pointer from a stack-allocated structure is a recipe for disaster, but this pass-by-value stuff feels alien.)

fearless_fool
  • 33,645
  • 23
  • 135
  • 217
  • 3
    Short answer: Perfectly safe. – DeiDei Apr 18 '18 at 22:20
  • Does this mean the copy into `b` happens before the `calibrate()` frame is unlinked? – fearless_fool Apr 18 '18 at 22:26
  • 3
    @fearless_fool: Not necessarily "into `b`"; a dumb non-optimizing compiler would arrange to have it stored in a temp location on the caller's stack frame for the return, then copy it into `b` for the assignment. But yes, putting it in storage whose lifetime is controlled by the caller has to happen before the callee's stack frame lifetime ends. – R.. GitHub STOP HELPING ICE Apr 18 '18 at 22:27
  • Best way to know for sure on your platform is to inspect the produced assembly. But in the general case you shouldn't be thinking about this as the compiler will do the best possible job. – DeiDei Apr 18 '18 at 22:29
  • Meh! :) I'll stick to passing pointers from caller-allocated memory into the callee -- this new-fangled pass-by-value stuff is weird. And kids, get off my lawn! – fearless_fool Apr 18 '18 at 22:31
  • 1
    Is returning a struct by value something unidiomatic for C? I'm used to C++ and wouldn't think to question whether it was safe to return by value. – Zebrafish Apr 18 '18 at 22:38
  • 1
    @Zebrafish: It's unidiomatic for C, and only idiomatic for C++ because modern C++ puts everything in header files & templates where it gets expanded out in the caller and has "no cost" (except making your program 100x the size it should be and blowing away the icache). – R.. GitHub STOP HELPING ICE Apr 18 '18 at 22:49
  • @R.. That's strange, every recommendation I've heard in C++ is to strictly keep definition in header and implementation in cpp unless it's a template, and only because of necessity. – Zebrafish Apr 18 '18 at 22:57
  • 1
    @fearless_fool Based on the ABI ([SysV ABI on amd64](https://www.uclibc.org/docs/psABI-x86_64.pdf) is this way, for example; I don't know about others), that's exactly what pass-by-value does. Except that in this case the struct is small enough [it will instead get returned by register](https://godbolt.org/g/WVuT54), which you can't do if you pass by pointer instead. – Daniel H Apr 18 '18 at 23:00
  • @R.. That's not how template instantiation works. Instantiations with the same parameters are merged during linking so don't contribute to executable size. For the case where the same code is generated for two distinct template parameters, [some linkers can now combine those functions too](https://stackoverflow.com/q/15168924/27302). I'm sure C++ templates do lead to some amount of executable size bloat, but I'm rather suspicious of the 100x figure (and especially suspicious that it matters in tight loops where icache is most relevant). – Daniel H Apr 18 '18 at 23:14
  • @DanielH: Thanks for the amd64 ABI: Just as you said: _If the type has class MEMORY, then the caller provides space for the return value and passes the address of this storage in %rdi as if it were the first argument to the function. In effect, this address becomes a “hidden” first argument._ That totally explains it -- thank you. – fearless_fool Apr 18 '18 at 23:36
  • 1
    @fearless_fool Yes, for larger structs. Your particular example struct is only one eightbyte and gets the type INTEGER and is returned entirely in `%rax`, though. Which is both entirely safe and faster than passing a hidden pointer. – Daniel H Apr 18 '18 at 23:54
  • This was also correct in C89; nothing changed in this area in C99 – M.M Apr 20 '18 at 02:23
  • @Zebrafish K&R C didn't have passing/returning structs by value , and it seems that a large sector of the community learned from K&R and did not refresh their knowledge when C89 came out (let alone later standards) – M.M Apr 20 '18 at 02:27

3 Answers3

11

Yes, it's perfectly safe. When you return by value it copies the members of the structure into the caller's structure. As long as the structure doesn't contain any pointers to local objects, it's valid.

Returning structures tends to be uncommon, because if they're large it requires lots of copying. But sometimes we put arrays into structures to allow them to be passed and returned by value (arrays normally decay to pointers when used as parameters or return values) like other data types.

addendum by original asker

(I trust @Barmar won't mind...)

As @DanielH pointed out, in the case of SysV ABI for amd64, the compiler will make provisions for returning the struct by value. If it's small, the entire struct can be returned in a register (read: fast). If it's larger, the compiler allocates room in the caller's stack frame and passes a pointer to the callee. The callee then copies the value of the struct into that upon return. From the doc:

If the type has class MEMORY, then the caller provides space for the return value and passes the address of this storage in %rdi as if it were the first argument to the function. In effect, this address becomes a “hidden” first argument.

fearless_fool
  • 33,645
  • 23
  • 135
  • 217
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • 2
    Normally the ABI for returning structures by value requires the caller to pass a hidden argument that's a pointer to where to store the return value. (Fundamentally, there's not really any other reasonable way to return an unbounded amount of data.) As such, it doesn't necessarily involve a lot of copying; a decent compiler can create the struct in-place where the caller expects to receive it, and pass the same storage pointer through multiple levels of calls if needed. – R.. GitHub STOP HELPING ICE Apr 18 '18 at 22:25
  • @R.. `if (x) { return struct1; } else { return struct2; }`. How can that allocate the structure in the caller's data? – Barmar Apr 18 '18 at 22:27
  • 2
    It could arbitrarily (or based on analysis of relative cost of a copy in each code path) decide to build one of the two in-place in the caller's provided storage, and only copy in the other code path. It could hoist operations so that rather than the returns being conditional, the stores into individual members leading up to the returns are conditional. Etc. etc. – R.. GitHub STOP HELPING ICE Apr 18 '18 at 22:33
  • @Barmar Or it could fail to optimize in that case, but in a lot of cases (including the one in the question) that's possible. – Daniel H Apr 18 '18 at 22:46
0
b = calibrate();
// do stuff with b

is well behaved.

boundaries_t contains only integral types as members. Passing it by value and using the object it is assigned to in the function call is perfectly safe.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
0

I dont have a link to a C99 reference, but what caught my eye was the struct assignment.

Assign one struct to another in C

It's basically Barmar's response.

Bwebb
  • 675
  • 4
  • 14