是否有任何缺点由值用C传递结构,而不是传递指针?(Are there any downsides t

2019-08-31 23:11发布

是否有任何缺点由值用C传递结构,而不是传递指针?

如果结构是大的,有明显的复制大量数据的performancd方面,但对于更小的结构,应该主要是与传递几个值的函数。

作为返回值使用时,它也许是更有趣。 仅C具有从功能单一的返回值,但您通常需要几个。 因此,一个简单的办法就是把它们放在一个结构和返回。

是否有支持或反对任何这样的原因?

因为它可能不是很明显大家什么我谈论这里,我举一个简单的例子。

如果你在C语言进行编程,你迟早会开始写看起来像这样的功能:

void examine_data(const char *ptr, size_t len)
{
    ...
}

char *p = ...;
size_t l = ...;
examine_data(p, l);

这不是一个问题。 唯一的问题是,你必须与你的同事同意在该命令的参数应该是这样,你使用相同的约定中的所有功能。

但是,当你想返回相同类型的信息会发生什么? 你通常得到的东西是这样的:

char *get_data(size_t *len);
{
    ...
    *len = ...datalen...;
    return ...data...;
}
size_t len;
char *p = get_data(&len);

这工作得很好,但更成问题。 返回值是一个返回值,但在此实现它不是。 有没有办法从上面告诉该函数GET_DATA不允许看什么LEN点。 并没有什么,使编译器检查的值通过该指针实际返回。 所以下个月,当其他人修改的代码不理解它正确(因为他没有阅读文档的?),它就会不说话就坏了,或者它开始随机崩溃。

所以,我提出的解决方案是简单的结构

struct blob { char *ptr; size_t len; }

这些例子可以写成这样:

void examine_data(const struct blob data)
{
    ... use data.tr and data.len ...
}

struct blob = { .ptr = ..., .len = ... };
examine_data(blob);

struct blob get_data(void);
{
    ...
    return (struct blob){ .ptr = ...data..., .len = ...len... };
}
struct blob data = get_data();

出于某种原因,我认为大多数人会本能地做出examine_data一个指针指向一个struct斑点,但我不明白为什么。 它仍然得到一个指针和一个整数,它只是更清楚,他们一起去。 而在GET_DATA情况下,它是不可能在我之前所描述的方式陷入困境,因为对于长度没有输入值,并且必须有一个返回的长度。

Answer 1:

对于小的结构(例如点,矩形)使由值是完全可以接受的。 但是,除了速度,还有另外一个原因,你应该小心传球/按值返回大的结构:堆栈空间。

很多C语言编程的是嵌入式系统,其中内存是非常宝贵的,和堆栈大小可以在KB或偶数字节来衡量......如果你正在传递或按值返回结构,这些结构的副本将得到安置上堆栈,可能导致这种情况这个网站的名字命名...

如果我看到似乎有过多的堆栈使用的应用程序,按值传递的结构是的东西,我找第一个。



Answer 2:

一个原因不能做到这一点还没有被提及的是,这可能会导致一个问题,即二进制兼容性问题。

根据所使用的编译器,结构可以通过堆叠或取决于编译选项寄存器/实现被传递

请参阅: http://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html

-fpcc-结构回报

-freg-结构回报

如果两个编译器不同意,事情可以炸毁。 不用说,主要的原因不这样做的说明是堆栈消耗和性能方面的原因。



Answer 3:

真正回答这个问题,需要深入挖掘组装地:

(下面的示例使用在x86_64 GCC。欢迎任何人以添加其它架构象MSVC,ARM等)

让我们有我们的示例程序:

// foo.c

typedef struct
{
    double x, y;
} point;

void give_two_doubles(double * x, double * y)
{
    *x = 1.0;
    *y = 2.0;
}

point give_point()
{
    point a = {1.0, 2.0};
    return a;
}

int main()
{
    return 0;
}

全优化编译

gcc -Wall -O3 foo.c -o foo

看总成:

objdump -d foo | vim -

这就是我们得到:

0000000000400480 <give_two_doubles>:
    400480: 48 ba 00 00 00 00 00    mov    $0x3ff0000000000000,%rdx
    400487: 00 f0 3f 
    40048a: 48 b8 00 00 00 00 00    mov    $0x4000000000000000,%rax
    400491: 00 00 40 
    400494: 48 89 17                mov    %rdx,(%rdi)
    400497: 48 89 06                mov    %rax,(%rsi)
    40049a: c3                      retq   
    40049b: 0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

00000000004004a0 <give_point>:
    4004a0: 66 0f 28 05 28 01 00    movapd 0x128(%rip),%xmm0
    4004a7: 00 
    4004a8: 66 0f 29 44 24 e8       movapd %xmm0,-0x18(%rsp)
    4004ae: f2 0f 10 05 12 01 00    movsd  0x112(%rip),%xmm0
    4004b5: 00 
    4004b6: f2 0f 10 4c 24 f0       movsd  -0x10(%rsp),%xmm1
    4004bc: c3                      retq   
    4004bd: 0f 1f 00                nopl   (%rax)

排除nopl垫, give_two_doubles()具有27个字节而give_point()具有29个字节。 在另一方面, give_point()产生一个不是较少指令give_two_doubles()

有趣的是,我们注意到编译器已经能够优化mov到更快的变种SSE2 movapdmovsd 。 此外, give_two_doubles()实际上在和移出存储器,这使得事情缓慢移动的数据。

显然,这在很大程度上可能不适用于嵌入式环境(这是对C比赛场地是大部分时间nowdays)。 我不是一个组件向导,以便任何意见会受到欢迎!



Answer 4:

简单的解决方案将是返回一个错误代码在函数返回值和其他一切作为参数,
此参数可以是当然的结构,但没有看到按值传递这种任何特别的优势,只是派出一个指针。
按值传递的结构是很危险的,你必须非常小心,你这是路过的,记得有C中没有拷贝构造函数,如果结构参数之一是一个指针,指针值将被复制可能是非常混乱,很难保持。

只是为了完成答案(完整的信用罗迪 )堆栈的使用是另一个理由不按值传递结构,相信我调试堆栈溢出是真实的PITA。

重播评论:

通过指针意味着一些实体有这个对象上的所有权和拥有什么和什么时候应该公布一个完整的知识传递结构。 按值传递结构创造结构的内部数据的隐藏引用(指向另一个结构等。)这个是很难维持的(可能的,但为什么呢?)。



Answer 5:

我觉得你的问题已经总结出东西相当不错。

按值传递结构的另一个优点是,内存所有权明确。 有没有想知道如果结构是离堆,谁拥有了释放它的责任。



Answer 6:

我说按值传递(不太大)结构,同时作为参数和返回值,是一个完全合法的技术。 一个人照顾,当然,该结构可以是一个POD类型,或复制语义以及指定。

更新:对不起,我有我的C ++的思想帽。 我记得的时候,它是不是在C从一个函数返回一个结构的法律,但这种自那时以来可能改变。 我仍然会说这是只要你希望使用支持该做法的编译器有效。



Answer 7:

有一点这里的人已经忘记了到目前为止提(或我忽略了它)是结构通常有一个填充!

struct {
  short a;
  char b;
  short c;
  char d;
}

每一个字符为1个字节,每短则2个字节。 有多大结构? 不,这不是6个字节。 至少没有任何比较常用的系统。 在大多数系统上这将是8的问题是,走线不是恒定的,它是依赖于系统的,所以同样的结构将有不同的定位和不同的系统不同的尺寸。

不仅如此填充将进一步吃掉你的筹码,这也增加了不能够预测填充提前,除非你知道你的系统盘,然后看你在你的应用程序有每一个结构和计算规模的不确定性为了它。 指针传递需要的空间可预测量 - 不存在不确定性。 指针的大小是远近闻名的系统,它始终是平等的,不管什么样的结构看起来和指针的大小总是在方式选择它们对齐,并且不需要填充。



Answer 8:

这里的东西没有提到任何一个:

void examine_data(const char *c, size_t l)
{
    c[0] = 'l'; // compiler error
}

void examine_data(const struct blob blob)
{
    blob.ptr[0] = 'l'; // perfectly legal, quite likely to blow up at runtime
}

一个成员const structconst ,但如果成员是一个指针(如char * ),就成了char *const ,而不是const char *我们真正想要的。 当然,我们可以假设const是故意的文档和任何人谁违反这是写不好的代码(他们是),但是这还不够好一些(尤其是那些谁只是了4个小时追查的原因崩溃)。

另一种选择可能是使struct const_blob { const char *c; size_t l } struct const_blob { const char *c; size_t l }和使用,但这是相当混乱-它进入相同的命名的方案问题,我有一个typedef ING指针。 因此,大多数人坚持只是有两个参数(或者,更可能为这种情况下,使用字符串库)。



Answer 9:

在第150页PC组装教程http://www.drpaulcarter.com/pcasm/有关于C如何让一个函数返回一个结构清晰的解释:

C还允许结构类型被用作FUNC-和灰的返回值。 显然,一个结构不能在EAX寄存器返回。 不同的编译器不同的方式处理这种情况。 该编译器使用的一个常见的解决方案是在内部重写功能为一体,需要一个结构指针作为参数。 指针被用来把返回值成称为例程的外部限定的结构。

我用下面的C代码来验证上述声明:

struct person {
    int no;
    int age;
};

struct person create() {
    struct person jingguo = { .no = 1, .age = 2};
    return jingguo;
}

int main(int argc, const char *argv[]) {
    struct person result;
    result = create();
    return 0;
}

使用“GCC -S”,以产生组件,用于此片的C代码:

    .file   "foo.c"
    .text
.globl create
    .type   create, @function
create:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $16, %esp
    movl    8(%ebp), %ecx
    movl    $1, -8(%ebp)
    movl    $2, -4(%ebp)
    movl    -8(%ebp), %eax
    movl    -4(%ebp), %edx
    movl    %eax, (%ecx)
    movl    %edx, 4(%ecx)
    movl    %ecx, %eax
    leave
    ret $4
    .size   create, .-create
.globl main
    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $20, %esp
    leal    -8(%ebp), %eax
    movl    %eax, (%esp)
    call    create
    subl    $4, %esp
    movl    $0, %eax
    leave
    ret
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
    .section    .note.GNU-stack,"",@progbits

调用之前堆栈创建:

        +---------------------------+
ebp     | saved ebp                 |
        +---------------------------+
ebp-4   | age part of struct person | 
        +---------------------------+
ebp-8   | no part of struct person  |
        +---------------------------+        
ebp-12  |                           |
        +---------------------------+
ebp-16  |                           |
        +---------------------------+
ebp-20  | ebp-8 (address)           |
        +---------------------------+

调用创建之后的堆栈:

        +---------------------------+
        | ebp-8 (address)           |
        +---------------------------+
        | return address            |
        +---------------------------+
ebp,esp | saved ebp                 |
        +---------------------------+


Answer 10:

我只是想指出的值传递的结构的一个优点是优化编译器可以更好地优化你的代码。



文章来源: Are there any downsides to passing structs by value in C, rather than passing a pointer?