结构可以通过值传递/返回,也可以通过C中的引用(通过指针)传递/返回.
普遍的共识似乎是,在大多数情况下,前者可以适用于没有惩罚的小结构.请参阅是否存在直接返回结构的良好做法?和是否有任何缺点由值用C传递结构,而不是传递指针?
从速度和清晰度的角度来看,避免取消引用可能是有益的.但什么算小?我想我们都同意这是一个小结构:
struct Point { int x, y; };
Run Code Online (Sandbox Code Playgroud)
我们可以通过相对有罪不罚的价值来传递:
struct Point sum(struct Point a, struct Point b) {
return struct Point { .x = a.x + b.x, .y = a.y + b.y };
}
Run Code Online (Sandbox Code Playgroud)
那个Linux task_struct是一个大型结构:
我们希望不惜一切代价避免使用堆栈(特别是那些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;
};
Run Code Online (Sandbox Code Playgroud)
确定结构是否足够小以确定是否可以安全地通过值传递(缺少某些深度递归等情有可原的情况),最好的经验法则是什么?
最后请不要告诉我需要个人资料.当我太懒的时候,我要求使用启发式方法/它不值得进一步调查.
编辑:到目前为止,根据答案我有两个后续问题:
如果结构实际上小于指向它的指针怎么办?
如果浅拷贝是期望的行为怎么办(被调用的函数无论如何都会执行浅拷贝)?
编辑:不知道为什么这被标记为可能重复,因为我实际上链接了我的问题中的其他问题.我要求澄清什么构成一个小结构,并且我很清楚大多数时间结构应该通过引用传递.
use*_*249 24
我的经验,近40年的实时嵌入,使用C持续20年; 是最好的方法是传递指针.
在任何一种情况下,都需要加载结构的地址,然后需要计算感兴趣的字段的偏移量......
传递整个结构时,如果没有通过引用传递,那么
当按值返回结构时,存在类似的考虑因素.
但是,可以完全保存在工作寄存器中的"小"结构在这些寄存器中传递,特别是如果在编译语句中使用了某些优化级别.
被认为是"小"的细节取决于编译器和底层硬件架构.
LTh*_*ode 17
在小型嵌入式架构(8/16位)上 - 总是通过指针传递,因为非平凡的结构不适合这种微小的寄存器,并且这些机器通常也是寄存器缺乏的.
在类似PC的架构(32位和64位处理器)上 - 按值传递结构是可以提供的,sizeof(mystruct_t) <= 2*sizeof(mystruct_t*)并且该函数没有很多(通常超过3个机器字值)其他参数.在这些情况下,典型的优化编译器将在寄存器或寄存器对中传递/返回结构.然而,在x86-32上,由于x86-32编译器必须处理的非常大的寄存压力,这个建议应该带有大量的盐 - 由于减少了寄存器溢出和填充,传递指针可能仍然更快.
另一方面,在PC-like上按值返回结构遵循相同的规则,除了当指针返回结构时,要填充的结构也应该通过指针传入 - 否则,被调用者和调用者不得不就如何管理该结构的内存达成一致.
由于问题的论证传递部分已经回答,我将重点关注回归部分.
做IMO的最好的事情是根本不返回结构或指向结构的指针,而是将指向"结构结构"的指针传递给函数.
void sum(struct Point* result, struct Point* a, struct Point* b);
Run Code Online (Sandbox Code Playgroud)
这具有以下优点:
result结构能活多久在栈或堆,在调用者的自由裁量权.如何将结构传递给函数或从函数传递结构取决于应用程序二进制接口(ABI)和程序调用标准(PCS,有时包含在ABI中),用于您的目标平台(CPU/OS,对于某些平台,可能有多个一个版本).
如果 PCS实际上允许在寄存器中传递结构,这不仅取决于它的大小,还取决于它在参数列表中的位置和前面参数的类型.例如,ARM-PCS(AAPCS)将参数打包到前4个寄存器中,直到它们已满并将更多数据传递到堆栈,即使这意味着参数被拆分(所有简化,如果感兴趣:文档可从ARM免费下载) ).
对于返回的结构,如果它们不通过寄存器传递,则大多数PCS由调用者分配堆栈上的空间,并将指向结构的指针传递给被调用者(隐式变体).这与调用者中的局部变量相同,并且显式地传递指针 - 对于被调用者.但是,对于隐式变体,结果必须复制到另一个结构,因为无法获得对隐式分配的结构的引用.
某些PCS可能对参数结构执行相同的操作,其他PCS只使用与标量相同的机制.无论如何,你推迟这样的优化,直到你真的知道你需要它们.另请阅读目标平台的PCS.请记住,您的代码可能在不同的平台上执行得更糟.
注意:现代PCS不使用通过全局临时结构传递结构,因为它不是线程安全的.但是,对于某些小型微控制器架构,这可能会有所不同.大多数情况下,如果他们只有一个小堆栈(S08)或限制功能(PIC).但是对于这些,大多数时候结构都不会在寄存器中传递,强烈建议使用pass-by-pointer.
如果只是为了原始的不变性:通过a const mystruct *ptr.除非你抛弃它const,否则至少在写入结构时会发出警告.指针本身也可以是常量:const mystruct * const ptr.
所以:没有经验法则; 这取决于太多因素.
实际上最好的经验法则是,通过引用和值将结构作为参数传递给函数,就是避免按值传递它.风险几乎总是超过收益.
为了完整起见,我将指出当通过值传递/返回结构时,会发生一些事情:
现在,在结构的大小方面达到足够小的意义 - 这样它"值得"通过值传递它,这取决于一些事情:
底线 - 很难说什么时候按值传递结构是可以的.只是不这样做更安全:)