在C中,我如何选择是返回结构还是指向结构的指针?

San*_*war 54 c malloc struct pointers

最近在我的C肌肉上工作,并查看我一直在使用它的许多图书馆,这当然让我很好地了解了什么是好的做法.我没见过的一件事是一个返回结构的函数:

something_t make_something() { ... }
Run Code Online (Sandbox Code Playgroud)

从我所吸收的,这是"正确"的方式:

something_t *make_something() { ... }
void destroy_something(something_t *object) { ... }
Run Code Online (Sandbox Code Playgroud)

代码片段2中的体系结构比片段1更受欢迎.所以现在我问,为什么我会直接返回一个结构,就像在代码片段1中一样?当我在两种选择中做出选择时,我应该考虑哪些差异?

此外,该选项如何比较?

void make_something(something_t *object)
Run Code Online (Sandbox Code Playgroud)

Jon*_*rdy 59

什么时候something_t小(读:复制它就像复制指针一样便宜)并且你希望它默认是堆栈分配的:

something_t make_something(void);

something_t stack_thing = make_something();

something_t *heap_thing = malloc(sizeof *heap_thing);
*heap_thing = make_something();
Run Code Online (Sandbox Code Playgroud)

什么时候something_t很大或者你想要它分配堆:

something_t *make_something(void);

something_t *heap_thing = make_something();
Run Code Online (Sandbox Code Playgroud)

无论大小如何something_t,如果您不关心它的分配位置:

void make_something(something_t *);

something_t stack_thing;
make_something(&stack_thing);

something_t *heap_thing = malloc(sizeof *heap_thing);
make_something(heap_thing);
Run Code Online (Sandbox Code Playgroud)

  • 差不多的话. (10认同)
  • 这个帖子里有很多好的答案.用最少的单词有效地解释了这个. (4认同)
  • 如果你选择了2,你还需要提供`free_something`. (2认同)
  • 样式3在相反的情况下也很有用,在这种情况下,您实际上关心*精确*分配对象的位置,可能是因为它的身份很重要. (2认同)

Yak*_*ont 36

这几乎总是关于ABI的稳定性.库版本之间的二进制稳定性.在不是的情况下,有时候会有动态大小的结构.很少是关于超大struct或性能.


struct在堆上分配并返回它的速度几乎与返回它的值一样快.该struct会必须是巨大的.

实际上,速度并不是技术2背后的原因,而是按指针返回,而不是按值返回.

技术2存在ABI稳定性.如果您有一个struct并且您的下一个版本的库添加了另外20个字段,那么如果它们是预先构造的指针,那么您之前版本的库的使用者是二进制兼容的.超出struct他们所知的额外数据是他们不必了解的.

如果你在堆栈上返回它,调用者正在为它分配内存,他们必须同意你的大小.如果您的库自上次重建后更新,您将要删除堆栈.

技术2还允许您在返回指针之前和之后隐藏额外数据(将数据附加到结构末尾的版本是变体).您可以使用可变大小的数组结束结构,或者在指针前添加一些额外数据,或者两者都添加.

如果你想struct在一个稳定的ABI中分配堆栈,那么几乎所有与struct需要通信的函数都需要传递版本信息.

所以

something_t make_something(unsigned library_version) { ... }
Run Code Online (Sandbox Code Playgroud)

library_version库使用where 来确定something_t它应返回的版本,并更改它操作的堆栈的大小.使用标准C是不可能的,但是

void make_something(something_t* here) { ... }
Run Code Online (Sandbox Code Playgroud)

是.在这种情况下,something_t可能有一个version字段作为其第一个元素(或大小字段),并且您需要在调用之前填充它make_something.

其他库代码something_t然后查询该version字段以确定something_t它们正在使用的版本.


Lun*_*din 13

根据经验,您不应该struct按值传递对象.实际上,只要它们小于或等于CPU在单个指令中可以处理的最大大小,就可以这样做.但在风格上,人们通常会避免它.如果您从未按值传递结构,则稍后可以向结构添加成员,这不会影响性能.

我认为这void make_something(something_t *object)是在C中使用结构的最常用方法.您将分配留给调用者.它很有效但不漂亮.

但是,面向对象的C程序使用,something_t *make_something()因为它们是使用opaque类型的概念构建的,这会强制您使用指针.返回的指针指向动态内存还是其他内容取决于实现.具有opaque类型的OO通常是设计更复杂的C程序的最优雅和最好的方法之一,但遗憾的是,很少有C程序员知道/关心它.

  • -1表示"小于或等于CPU在单个指令中可以处理的最大大小".Malloc需要的不仅仅是一条指令.没有确切的方法可以确定结构在通过引用传递它之前必须有多大(因为这取决于它的使用方式)而不是堆分配它,但实际上它比sizeof(void*)要大得多对于大多数用例.例如,大多数游戏将按值传递4x4矩阵. (5认同)
  • ******回答**不透明类型**,这是ABI稳定性的基石.谢谢你,先生. (2认同)

M.M*_*M.M 9

第一种方法的一些优点:

  • 少写代码.
  • 更多惯用于返回多个值的用例.
  • 适用于没有动态分配的系统.
  • 对于小型或小型物体来说可能更快.
  • 忘记了没有内存泄漏free.

一些缺点:

  • 如果对象很大(例如,兆字节),可能导致堆栈溢出,或者如果编译器没有很好地优化它,则可能会很慢.
  • 可能会让那些在20世纪70年代学会C的人感到惊讶,因为这是不可能的,并且没有及时更新.
  • 不适用于包含指向其自身一部分的指针的对象.

  • "可能会让那些在20世纪70年代学会C的人感到惊讶,因为这是不可能的,而且还没有及时更新." 这不是一件好事吗?:) (7认同)