我应该何时通过值传递或返回结构?

时间:2021-11-12 06:23:10

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

结构可以通过值传递/返回,也可以通过C中的引用(通过指针)传递/返回。

The general consensus seems to be that the former can be applied to small structs without penalty in most cases. See Is there any case for which returning a structure directly is good practice? and Are there any downsides to passing structs by value in C, rather than passing a pointer?

普遍的共识似乎是,在大多数情况下,前者可以适用于没有惩罚的小结构。请参阅是否存在直接返回结构的良好做法?在C中通过值传递结构是否有任何缺点,而不是传递指针?

And that avoiding a dereference can be beneficial from both a speed and clarity perspective. But what counts as small? I think we can all agree that this is a small struct:

从速度和清晰度的角度来看,避免取消引用可能是有益的。但什么算小?我想我们都同意这是一个小结构:

struct Point { int x, y; };

That we can pass by value with relative impunity:

我们可以通过相对有罪不罚的价值来传递:

struct Point sum(struct Point a, struct Point b) {
  return struct Point { .x = a.x + b.x, .y = a.y + b.y };
}

And that Linux's task_struct is a large struct:

Linux的task_struct是一个大型结构:

https://github.com/torvalds/linux/blob/b953c0d234bc72e8489d3bf51a276c5c4ec85345/include/linux/sched.h#L1292-1727

https://github.com/torvalds/linux/blob/b953c0d234bc72e8489d3bf51a276c5c4ec85345/include/linux/sched.h#L1292-1727

That we'd want to avoid putting on the stack at all costs (especially with those 8K kernel mode stacks!). But what's about middling ones? I assume structs smaller than a register are fine. But what about these?

我们希望不惜一切代价避免使用堆栈(特别是那些8K内核模式堆栈!)。但是什么是中等的?我假设小于寄存器的结构是好的。但那些呢?

typedef struct _mx_node_t mx_node_t;
typedef struct _mx_edge_t mx_edge_t;

struct _mx_edge_t {
  char symbol;
  size_t next;
};

struct _mx_node_t {
  size_t id;
  mx_edge_t edge[2];
  int action;
};

What is the best rule of thumb for determining whether a struct is small enough that it's safe to pass it around by value (short of extenuating circumstances such as some deep recursion)?

确定一个结构是否足够小以便通过值传递它是否安全(没有一些深度递归的情有可原的情况),最好的经验法则是什么?

Lastly please don't tell me that I need to profile. I'm asking for a heuristic to use when I'm too lazy/it's not worth it to investigate further.

最后请不要告诉我需要个人资料。当我太懒的时候,我要求使用启发式方法/它不值得进一步调查。

EDIT: I have two followup questions based on the answers so far:

编辑:到目前为止,根据答案我有两个后续问题:

  1. What if the struct is actually smaller than a pointer to it?

    如果结构实际上小于指向它的指针怎么办?

  2. What if a shallow copy is the desired behavior (the called function will perform a shallow copy anyway)?

    如果浅拷贝是期望的行为怎么办(被调用的函数无论如何都会执行浅拷贝)?

EDIT: Not sure why this got marked as a possible duplicate as I actually link the other question in my question. I'm asking for clarification on what constitutes a small struct and am well aware that most of the time structs should be passed by reference.

编辑:不知道为什么这被标记为可能重复,因为我实际上链接了我的问题中的其他问题。我要求澄清什么构成一个小结构,并且我很清楚大多数时间结构应该通过引用传递。

8 个解决方案

#1


12  

On small embedded architectures (8/16-bitters) -- always pass by pointer, as non-trivial structures don't fit into such tiny registers, and those machines are generally register-starved as well.

在小型嵌入式架构(8/16位)上 - 总是通过指针传递,因为非平凡的结构不适合这种微小的寄存器,并且这些机器通常也是寄存器缺乏的。

On PC-like architectures (32 and 64 bit processors) -- passing a structure by value is OK provided sizeof(mystruct_t) <= 2*sizeof(mystruct_t*) and the function does not have many (usually more than 3 machine words' worth of) other arguments. Under these circumstances, a typical optimizing compiler will pass/return the structure in a register or register pair. However, on x86-32, this advice should be taken with a hefty grain of salt, due to the extraordinary register pressure a x86-32 compiler must deal with -- passing a pointer may still be faster due to reduced register spilling and filling.

在类似PC的架构(32位和64位处理器)上 - 按值传递结构是正确的,提供sizeof(mystruct_t)<= 2 * sizeof(mystruct_t *)并且函数没有很多(通常超过3个机器字')值得)其他论点。在这些情况下,典型的优化编译器将在寄存器或寄存器对中传递/返回结构。然而,在x86-32上,由于x86-32编译器必须处理的非常大的寄存压力,这个建议应该带有大量的盐 - 由于减少了寄存器溢出和填充,传递指针可能仍然更快。

Returning a structure by value on PC-likes, on the other hand, follows the same rule, save for the fact that when a structure is returned by pointer, the structure to be filled out should be passed in by pointer as well -- otherwise, the callee and the caller are stuck having to agree on how to manage the memory for that structure.

另一方面,在PC-like上按值返回结构遵循相同的规则,除了当指针返回结构时,要填充的结构也应该通过指针传入 - 否则,被调用者和调用者不得不就如何管理该结构的内存达成一致。

#2


21  

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

我的经验,近40年的实时嵌入,使用C持续20年;是最好的方法是传递指针。

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. 它没有放在堆栈上
  3. it is copied, usually by a hidden call to memcpy()
  4. 它被复制,通常是通过对memcpy()的隐藏调用
  5. it is copied to a section of memory that is now 'reserved' and unavailable to any other part of the program.
  6. 它被复制到现在“保留”的一段内存中,并且对程序的任何其他部分都不可用。

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.

被认为是“小”的细节取决于编译器和底层硬件架构。

#3


6  

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).

如何将结构传递给函数或从函数传递结构取决于应用程序二进制接口(ABI)和目标平台的过程调用标准(PCS,有时包含在ABI中)(CPU / OS,对于某些平台可能有多个)一个版本)。

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).

如果PCS实际上允许在寄存器中传递结构,这不仅取决于它的大小,还取决于它在参数列表中的位置和前面参数的类型。例如,ARM-PCS(AAPCS)将参数打包到前4个寄存器中,直到它们已满并将更多数据传递到堆栈,即使这意味着参数被拆分(所有简化,如果感兴趣:文档可从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.

对于返回的结构,如果它们不通过寄存器传递,则大多数PCS由调用者分配堆栈上的空间,并将指向结构的指针传递给被调用者(隐式变体)。这与调用者中的局部变量相同,并且显式地传递指针 - 对于被调用者。但是,对于隐式变体,结果必须复制到另一个结构,因为无法获得对隐式分配的结构的引用。

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.

某些PCS可能对参数结构执行相同的操作,而其他PCS只使用与标量结构相同的机制。无论如何,你推迟这样的优化,直到你真的知道你需要它们。另请阅读目标平台的PCS。请记住,您的代码可能在不同的平台上执行得更糟。

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.

注意:现代PCS不使用通过全局临时结构传递结构,因为它不是线程安全的。但是,对于某些小型微控制器架构,这可能会有所不同。大多数情况下,如果他们只有一个小堆栈(S08)或限制功能(PIC)。但是对于这些,大多数时候结构都不会在寄存器中传递,强烈建议使用pass-by-pointer。

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.

如果只是为了原始的不变性:传递一个const mystruct * ptr。除非你抛弃了至少在写入结构时会发出警告的const。指针本身也可以是常量:const mystruct * const ptr。

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

所以:没有经验法则;这取决于太多因素。

#4


5  

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. 所有结构的成员都被复制到堆栈中
  3. if returning a struct by value, again, all members are copied from the function's stack memory to a new memory location.
  4. 如果按值返回结构,则所有成员都将从函数的堆栈内存复制到新的内存位置。
  5. 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.
  6. 该操作容易出错 - 如果结构的成员是指针,则常见的错误是假设您可以安全地按值传递参数,因为您正在操作指针 - 这可能导致很难发现错误。
  7. 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.
  8. 如果你的函数修改了输入参数的值,你的输入是结构变量,按值传递,你必须记住总是按值返回一个struct变量(我已经看过很多次了)。这意味着复制结构成员的时间加倍。

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. 调用约定:编译器在调用该函数时自动保存在堆栈上的内容(通常是几个寄存器的内容)。如果您的结构成员可以利用这种机制复制到堆栈上,那么就没有惩罚。
  3. 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.
  4. 结构成员的数据类型:如果你的机器的寄存器是16位,你的结构的成员数据类型是64位,它显然不适合一个寄存器,因此只需要为一个副本执行多个操作。
  5. 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.
  6. 你的机器实际拥有的寄存器数量:假设你的结构只有一个成员,一个字符(8位)。当通过值或通过引用传递参数时(理论上),这应该导致相同的开销。但是还有另外一个危险。如果您的体系结构具有单独的数据和地址寄存器,则通过值传递的参数将占用一个数据寄存器,通过引用传递的参数将占用一个地址寄存器。按值传递参数会对数据寄存器施加压力,这些数据寄存器通常比地址寄存器使用得多。这可能会导致堆栈溢出。

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 :)

底线 - 很难说什么时候按值传递结构是可以的。只是不这样做更安全:)

#5


5  

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.

做IMO的最好的事情是根本不返回结构的结构或指针,而是将指向结果结构的指针传递给函数。

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.
  • 结果struct可以在堆栈上或堆上,由调用者自行决定。
  • 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.
  • 结构甚至可以比需要的更长,或嵌入更大的结构中。

#6


3  

Note: reasons to do so one way or the other overlap.

注意:这样做的原因是这样或那样重叠。

When to pass/return by value:

何时通过值传递/返回:

  1. The object is a fundamental type like int, double, pointer.
  2. 该对象是一个基本类型,如int,double,pointer。
  3. A binary copy of the object must be made - and object is not large.
  4. 必须创建对象的二进制副本 - 并且对象不大。
  5. Speed is important and passing by value is faster.
  6. 速度很重要,价值传递速度更快。
  7. The object is conceptually a smallish numeric

    该对象在概念上是一个小数字

    struct quaternion {
      long double i,j,k;
    }
    struct pixel {
      uint16_t r,g,b;
    }
    struct money {
      intmax_t;
      int exponent;
    }
    

When to use a pointer to the object

何时使用指向对象的指针

  1. Unsure if value or a pointer to value is better - so this is the default choice.
  2. 不确定值或指向值的指针是否更好 - 因此这是默认选择。
  3. The object is large.
  4. 对象很大。
  5. Speed is important and passing by a pointer to the object is faster.
  6. 速度很重要,通过指向对象的指针传递速度更快。
  7. Stack usage is critical. (Strictly this may favor by value in some cases)
  8. 堆栈使用至关重要。 (在某些情况下,这可能会受到价值的影响)
  9. Modifications to the passed object are needed.
  10. 需要修改传递的对象。
  11. Object needs memory management.

    对象需要内存管理。

    struct mystring {
      char *s;
      size_t length;
      size_t size;
    }
    

Notes: Recall that in C, nothing is truly passed by reference. Even passing a pointer is passed by value, as the value of the pointer is copied and passed.

注意:回想一下,在C中,没有任何内容真正通过引用传递。即使传递指针也会按值传递,因为复制并传递指针的值。

I prefer passing numbers, be they int or pixel by value as it is conceptually easier to understand code. Passing numerics by address is conceptual a bit more difficult. With larger numeric objects, it may be faster to pass by address.

我更喜欢传递数字,无论是int还是像素值,因为它在概念上更容易理解代码。通过地址传递数字在概念上有点困难。对于较大的数字对象,通过地址传递可能更快。

Objects having their address passed may use restrict to inform the function the objects do not overlap.

传递了地址的对象可以使用restrict来通知函数对象不重叠。

#7


1  

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.

在典型的PC上,即使对于相当大的结构(许多几十个字节),性能也不应成为问题。因此,其他标准很重要,尤其是语义:你真的想要复制吗?或者在同一个对象上,例如在操纵链表时?指南应该是用最合适的语言结构表达所需的语义,以使代码可读和可维护。

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).

    Memcpy很快,内存局部性(对堆栈有利)可能比数据大小更重要:如果在堆栈上按值传递和返回结构,则复制可能都发生在缓存中。此外,返回值优化应该避免冗余复制要返回的局部变量(20或30年前这些天真的编译器做了)。

  • 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.

底线是肯定的,除非遇到问题,如果更方便或更合适,可以随意传递值。它甚至可能更快。

#8


-2  

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.

以抽象的方式,传递给函数的一组数据值是按值的结构,尽管这是未声明的。您可以将函数声明为结构,在某些情况下需要类型定义。当你这样做时,一切都在堆栈上。这就是问题所在。通过将数据值放在堆栈上,如果在使用或复制其他数据之前使用参数调用函数或子函数,则它很容易过度写入。最好使用指针和类。

#1


12  

On small embedded architectures (8/16-bitters) -- always pass by pointer, as non-trivial structures don't fit into such tiny registers, and those machines are generally register-starved as well.

在小型嵌入式架构(8/16位)上 - 总是通过指针传递,因为非平凡的结构不适合这种微小的寄存器,并且这些机器通常也是寄存器缺乏的。

On PC-like architectures (32 and 64 bit processors) -- passing a structure by value is OK provided sizeof(mystruct_t) <= 2*sizeof(mystruct_t*) and the function does not have many (usually more than 3 machine words' worth of) other arguments. Under these circumstances, a typical optimizing compiler will pass/return the structure in a register or register pair. However, on x86-32, this advice should be taken with a hefty grain of salt, due to the extraordinary register pressure a x86-32 compiler must deal with -- passing a pointer may still be faster due to reduced register spilling and filling.

在类似PC的架构(32位和64位处理器)上 - 按值传递结构是正确的,提供sizeof(mystruct_t)<= 2 * sizeof(mystruct_t *)并且函数没有很多(通常超过3个机器字')值得)其他论点。在这些情况下,典型的优化编译器将在寄存器或寄存器对中传递/返回结构。然而,在x86-32上,由于x86-32编译器必须处理的非常大的寄存压力,这个建议应该带有大量的盐 - 由于减少了寄存器溢出和填充,传递指针可能仍然更快。

Returning a structure by value on PC-likes, on the other hand, follows the same rule, save for the fact that when a structure is returned by pointer, the structure to be filled out should be passed in by pointer as well -- otherwise, the callee and the caller are stuck having to agree on how to manage the memory for that structure.

另一方面,在PC-like上按值返回结构遵循相同的规则,除了当指针返回结构时,要填充的结构也应该通过指针传入 - 否则,被调用者和调用者不得不就如何管理该结构的内存达成一致。

#2


21  

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

我的经验,近40年的实时嵌入,使用C持续20年;是最好的方法是传递指针。

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. 它没有放在堆栈上
  3. it is copied, usually by a hidden call to memcpy()
  4. 它被复制,通常是通过对memcpy()的隐藏调用
  5. it is copied to a section of memory that is now 'reserved' and unavailable to any other part of the program.
  6. 它被复制到现在“保留”的一段内存中,并且对程序的任何其他部分都不可用。

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.

被认为是“小”的细节取决于编译器和底层硬件架构。

#3


6  

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).

如何将结构传递给函数或从函数传递结构取决于应用程序二进制接口(ABI)和目标平台的过程调用标准(PCS,有时包含在ABI中)(CPU / OS,对于某些平台可能有多个)一个版本)。

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).

如果PCS实际上允许在寄存器中传递结构,这不仅取决于它的大小,还取决于它在参数列表中的位置和前面参数的类型。例如,ARM-PCS(AAPCS)将参数打包到前4个寄存器中,直到它们已满并将更多数据传递到堆栈,即使这意味着参数被拆分(所有简化,如果感兴趣:文档可从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.

对于返回的结构,如果它们不通过寄存器传递,则大多数PCS由调用者分配堆栈上的空间,并将指向结构的指针传递给被调用者(隐式变体)。这与调用者中的局部变量相同,并且显式地传递指针 - 对于被调用者。但是,对于隐式变体,结果必须复制到另一个结构,因为无法获得对隐式分配的结构的引用。

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.

某些PCS可能对参数结构执行相同的操作,而其他PCS只使用与标量结构相同的机制。无论如何,你推迟这样的优化,直到你真的知道你需要它们。另请阅读目标平台的PCS。请记住,您的代码可能在不同的平台上执行得更糟。

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.

注意:现代PCS不使用通过全局临时结构传递结构,因为它不是线程安全的。但是,对于某些小型微控制器架构,这可能会有所不同。大多数情况下,如果他们只有一个小堆栈(S08)或限制功能(PIC)。但是对于这些,大多数时候结构都不会在寄存器中传递,强烈建议使用pass-by-pointer。

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.

如果只是为了原始的不变性:传递一个const mystruct * ptr。除非你抛弃了至少在写入结构时会发出警告的const。指针本身也可以是常量:const mystruct * const ptr。

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

所以:没有经验法则;这取决于太多因素。

#4


5  

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. 所有结构的成员都被复制到堆栈中
  3. if returning a struct by value, again, all members are copied from the function's stack memory to a new memory location.
  4. 如果按值返回结构,则所有成员都将从函数的堆栈内存复制到新的内存位置。
  5. 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.
  6. 该操作容易出错 - 如果结构的成员是指针,则常见的错误是假设您可以安全地按值传递参数,因为您正在操作指针 - 这可能导致很难发现错误。
  7. 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.
  8. 如果你的函数修改了输入参数的值,你的输入是结构变量,按值传递,你必须记住总是按值返回一个struct变量(我已经看过很多次了)。这意味着复制结构成员的时间加倍。

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. 调用约定:编译器在调用该函数时自动保存在堆栈上的内容(通常是几个寄存器的内容)。如果您的结构成员可以利用这种机制复制到堆栈上,那么就没有惩罚。
  3. 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.
  4. 结构成员的数据类型:如果你的机器的寄存器是16位,你的结构的成员数据类型是64位,它显然不适合一个寄存器,因此只需要为一个副本执行多个操作。
  5. 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.
  6. 你的机器实际拥有的寄存器数量:假设你的结构只有一个成员,一个字符(8位)。当通过值或通过引用传递参数时(理论上),这应该导致相同的开销。但是还有另外一个危险。如果您的体系结构具有单独的数据和地址寄存器,则通过值传递的参数将占用一个数据寄存器,通过引用传递的参数将占用一个地址寄存器。按值传递参数会对数据寄存器施加压力,这些数据寄存器通常比地址寄存器使用得多。这可能会导致堆栈溢出。

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 :)

底线 - 很难说什么时候按值传递结构是可以的。只是不这样做更安全:)

#5


5  

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.

做IMO的最好的事情是根本不返回结构的结构或指针,而是将指向结果结构的指针传递给函数。

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.
  • 结果struct可以在堆栈上或堆上,由调用者自行决定。
  • 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.
  • 结构甚至可以比需要的更长,或嵌入更大的结构中。

#6


3  

Note: reasons to do so one way or the other overlap.

注意:这样做的原因是这样或那样重叠。

When to pass/return by value:

何时通过值传递/返回:

  1. The object is a fundamental type like int, double, pointer.
  2. 该对象是一个基本类型,如int,double,pointer。
  3. A binary copy of the object must be made - and object is not large.
  4. 必须创建对象的二进制副本 - 并且对象不大。
  5. Speed is important and passing by value is faster.
  6. 速度很重要,价值传递速度更快。
  7. The object is conceptually a smallish numeric

    该对象在概念上是一个小数字

    struct quaternion {
      long double i,j,k;
    }
    struct pixel {
      uint16_t r,g,b;
    }
    struct money {
      intmax_t;
      int exponent;
    }
    

When to use a pointer to the object

何时使用指向对象的指针

  1. Unsure if value or a pointer to value is better - so this is the default choice.
  2. 不确定值或指向值的指针是否更好 - 因此这是默认选择。
  3. The object is large.
  4. 对象很大。
  5. Speed is important and passing by a pointer to the object is faster.
  6. 速度很重要,通过指向对象的指针传递速度更快。
  7. Stack usage is critical. (Strictly this may favor by value in some cases)
  8. 堆栈使用至关重要。 (在某些情况下,这可能会受到价值的影响)
  9. Modifications to the passed object are needed.
  10. 需要修改传递的对象。
  11. Object needs memory management.

    对象需要内存管理。

    struct mystring {
      char *s;
      size_t length;
      size_t size;
    }
    

Notes: Recall that in C, nothing is truly passed by reference. Even passing a pointer is passed by value, as the value of the pointer is copied and passed.

注意:回想一下,在C中,没有任何内容真正通过引用传递。即使传递指针也会按值传递,因为复制并传递指针的值。

I prefer passing numbers, be they int or pixel by value as it is conceptually easier to understand code. Passing numerics by address is conceptual a bit more difficult. With larger numeric objects, it may be faster to pass by address.

我更喜欢传递数字,无论是int还是像素值,因为它在概念上更容易理解代码。通过地址传递数字在概念上有点困难。对于较大的数字对象,通过地址传递可能更快。

Objects having their address passed may use restrict to inform the function the objects do not overlap.

传递了地址的对象可以使用restrict来通知函数对象不重叠。

#7


1  

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.

在典型的PC上,即使对于相当大的结构(许多几十个字节),性能也不应成为问题。因此,其他标准很重要,尤其是语义:你真的想要复制吗?或者在同一个对象上,例如在操纵链表时?指南应该是用最合适的语言结构表达所需的语义,以使代码可读和可维护。

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).

    Memcpy很快,内存局部性(对堆栈有利)可能比数据大小更重要:如果在堆栈上按值传递和返回结构,则复制可能都发生在缓存中。此外,返回值优化应该避免冗余复制要返回的局部变量(20或30年前这些天真的编译器做了)。

  • 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.

底线是肯定的,除非遇到问题,如果更方便或更合适,可以随意传递值。它甚至可能更快。

#8


-2  

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.

以抽象的方式,传递给函数的一组数据值是按值的结构,尽管这是未声明的。您可以将函数声明为结构,在某些情况下需要类型定义。当你这样做时,一切都在堆栈上。这就是问题所在。通过将数据值放在堆栈上,如果在使用或复制其他数据之前使用参数调用函数或子函数,则它很容易过度写入。最好使用指针和类。