When should I pass or return a struct by value?

前端 未结 8 557
暗喜
暗喜 2020-12-02 17:10

A struct can be either passed/returned by value or passed/returned by reference (via a pointer) in C.

The general consensus seems to be that the former can be applie

相关标签:
8条回答
  • 2020-12-02 17:13

    On a typical PC, performance should not be an issue even for fairly large structures (many dozens of bytes). Consequently other criteria are important, especially semantics: Do you indeed want to work on a copy? Or on the same object, e.g. when manipulating linked lists? The guideline should be to express the desired semantics with the most appropriate language construct in order to make the code readable and maintainable.

    That said, if there is any performance impact it may not be as clear as one would think.

    • Memcpy is fast, and memory locality (which is good for the stack) may be more important than data size: The copying may all happen in the cache, if you pass and return a struct by value on the stack. Also, return value optimization should avoid redundant copying of local variables to be returned (which naive compilers did 20 or 30 years ago).

    • Passing pointers around introduces aliases to memory locations which then cannot be cached as efficiently any longer. Modern languages are often more value-oriented because all data is isolated from side effects which improves the compiler's ability to optimize.

    The bottom line is yes, unless you run into problems feel free to pass by value if it is more convenient or appropriate. It may even be faster.

    0 讨论(0)
  • 2020-12-02 17:18

    My experience, nearly 40 years of real-time embedded, last 20 using C; is that the best way is to pass a pointer.

    In either case the address of the struct needs to be loaded, then the offset for the field of interest needs to be calculated...

    When passing the whole struct, if it is not passed by reference, then

    1. it is not placed on the stack
    2. it is copied, usually by a hidden call to memcpy()
    3. it is copied to a section of memory that is now 'reserved' and unavailable to any other part of the program.

    Similar considerations exist for when a struct is returned by value.

    However, "small" structs, that can be completely held in a working register to two are passed in those registers especially if certain levels of optimization are used in the compile statement.

    The details of what is considered 'small' depend on the compiler and the underlying hardware architecture.

    0 讨论(0)
  • 2020-12-02 17:25

    Since the argument-passing part of the question is already answered, I'll focus on the returning part.

    The best thing to do IMO is to not return structs or pointers to structs at all, but to pass a pointer to the 'result struct' to the function.

    void sum(struct Point* result, struct Point* a, struct Point* b);
    

    This has the following advantages:

    • The result struct can live either on the stack or on the heap, at the caller's discretion.
    • There are no ownership problems, as it is clear that the caller is responsible for allocating and freeing the result struct.
    • The structure could even be longer than what is needed, or be embedded in a larger struct.
    0 讨论(0)
  • 2020-12-02 17:26

    How a struct is passed to or from a function depends on the application binary interface (ABI) and the procedure call standard (PCS, sometimes included in the ABI) for your target platform (CPU/OS, for some platforms there may be more than one version).

    If the PCS actually allows to pass a struct in registers, this not only depends on its size, but also on its position in the argument list and the types of preceeding arguments. ARM-PCS (AAPCS) for instance packs arguments into the first 4 registers until they are full and passes further data onto the stack, even if that means an argument is split (all simplified, if interested: the documents are free for download from ARM).

    For structs returned, if they are not passed through registers, most PCS allocate the space on the stack by the caller and pass a pointer to the struct to the callee (implicit variant). This is identical to a local variable in the caller and passing the pointer explicitly - for the callee. However, for the implicit variant, the result has to be copied to another struct, as there is no way to get a reference to the implicitly allocated struct.

    Some PCS might do the same for argument structs, others just use the same mechanisms as for scalars. In any way, you defer such optimizations until you really know you need them. Also read the PCS of your target platform. Remember, that your code might perform even worse on a different platform.

    Note: passing a struct through a global temp is not used by modern PCS, as it is not thread-safe. For some small microcontroller architectures, this might be different, however. Mostly if they only have a small stack (S08) or restricted features (PIC). But for these most times structs are not passed in registers, either, and pass-by-pointer is strongly recommended.

    If it is just for immutability of the original: pass a const mystruct *ptr. Unless you cast away the const that will give a warning at least when writing to the struct. The pointer itself can also be constant: const mystruct * const ptr.

    So: No rule of thumb; it depends on too many factors.

    0 讨论(0)
  • 2020-12-02 17:28

    in an abstract way a set of data values passed to a function is a structure by value, albeit undeclared as such. you can declare a function as a structure, in some cases requiring a type definition. when you do this everything is on the stack. and that is the problem. by putting your data values on the stack it becomes vulnerable to over writing if a function or sub is called with parameters before you utilize or copy the data elsewhere. it is best to use pointers and classes.

    0 讨论(0)
  • 2020-12-02 17:31

    Really the best rule of thumb, when it comes to passing a struct as argument to a function by reference vs by value, is to avoid passing it by value. The risks almost always outweigh the benefits.

    For the sake of completeness I'll point out that when passing/returning a struct by value a few things happen:

    1. all the structure's members are copied on the stack
    2. if returning a struct by value, again, all members are copied from the function's stack memory to a new memory location.
    3. the operation is error prone - if the structure's members are pointers a common error is to assume you are safe to pass the parameter by value, since you are operating on pointers - this can cause very difficult to spot bugs.
    4. if your function modifies the value of the input parameters and your inputs are struct variables, passed by value, you have to remember to ALWAYS return a struct variable by value (I've seen this one quite a few times). Which means double the time copying the structure members.

    Now getting to what small enough means in terms of size of the struct - so that it's 'worth' passing it by value, that would depend on a few things:

    1. the calling convention: what does the compiler automatically save on the stack when calling that function(usually it's the content of a few registers). If your structure members can be copied on the stack taking advantage of this mechanism than there is no penalty.
    2. the structure member's data type: if the registers of your machine are 16 bits and your structure's members data type is 64 bit, it obviously won't fit in one registers so multiple operations will have to be performed just for one copy.
    3. the number of registers your machine actually has: assuming you have a structure with only one member, a char (8bit). That should cause the same overhead when passing the parameter by value or by reference (in theory). But there is potentially one other danger. If your architecture has separate data and address registers, the parameter passed by value will take up one data register and the parameter passed by reference will take up one address register. Passing the parameter by value puts pressure on the data registers which are usually used more than the address registers. And this may cause spills on the stack.

    Bottom line - it's very difficult to say when it's ok to pass a struct by value. It's safer to just not do it :)

    0 讨论(0)
提交回复
热议问题