从C中的函数返回`struct`

Gra*_*Guy 153 c

今天我教了几个朋友如何使用C structs.其中一个问你是否可以struct从一个函数返回一个函数,我回答说:"不!你会返回指向动态malloc编辑struct的指针."

来自主要使用C++的人,我期望无法struct按值返回s.在C++中,您可以operator =为对象重载并完全理解为具有按值返回对象的函数.但是,在C中,你没有那个选项,所以它让我思考编译器实际上在做什么.考虑以下:

struct MyObj{
    double x, y;
};

struct MyObj foo(){
    struct MyObj a;

    a.x = 10;
    a.y = 10;

    return a;
}        

int main () {

    struct MyObj a;

    a = foo();    // This DOES work
    struct b = a; // This does not work

    return 0;
}    
Run Code Online (Sandbox Code Playgroud)

我理解为什么struct b = a;不能工作 - 你不能operator =为你的数据类型重载.它是如何a = foo();编译罚款?这是否意味着什么struct b = a;?也许要问的问题是:return=签名相关的陈述到底是做什么的?

[编辑]:好的,我只是指出struct b = a是一个语法错误 - 这是正确的,我是一个白痴!但这使它变得更加复杂!使用struct MyObj b = a确实有效!我在这里错过了什么?

Car*_*rum 182

您可以从函数返回结构(或使用=运算符)而不会出现任何问题.它是语言中定义明确的部分.唯一的问题struct b = a是你没有提供完整的类型. struct MyObj b = a会工作得很好.您也可以将结构传递函数 - 为了参数传递,返回值和赋值,结构与任何内置类型完全相同.

这是一个简单的演示程序,它完成所有三个 - 将结构作为参数传递,从函数返回结构,并在赋值语句中使用结构:

#include <stdio.h>

struct a {
   int i;
};

struct a f(struct a x)
{
   struct a r = x;
   return r;
}

int main(void)
{
   struct a x = { 12 };
   struct a y = f(x);
   printf("%d\n", y.i);
   return 0;
}
Run Code Online (Sandbox Code Playgroud)

下一个示例几乎完全相同,但使用内置int类型进行演示.这两个程序在参数传递,赋值等的传值方面具有相同的行为:

#include <stdio.h>

int f(int x) 
{
  int r = x;
  return r;
}

int main(void)
{
  int x = 12;
  int y = f(x);
  printf("%d\n", y);
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

  • 这很有意思.我总是觉得你需要这些指针.我错了 :) (13认同)
  • @CarlNorum一个结构需要多大才能获得比malloc + free更多的复制费用? (10认同)
  • 你当然不需要*指针.也就是说,大多数情况下你会想要使用它们 - 隐含的内存副本,这些内存副本是按值进行的,它们可以真正浪费CPU周期,更不用说内存带宽了. (8认同)
  • 只要在编译时不知道为值分配的内存量,就需要指针和为函数体外部的返回值分配内存.它适用于结构体,因此C函数返回它们没有问题. (7认同)
  • @josefx,单一副本?可能很大.问题是,通常情况下,如果你按价值传递结构,你会复制它们*很多*.无论如何,它并不是那么简单.你可能会绕过本地或全球结构,在这种情况下,分配成本几乎是免费的. (6认同)
  • 返回值优化接缝只能讨论C++,但它对C有效吗?如果我在函数中定义一个结构并返回它,那么C编译器将使用RVO来避免复制. (3认同)
  • @Zboson根据[这个答案](http://stackoverflow.com/a/30034272/5386374),C标准隐含地允许RVO和NRVO. (2认同)
  • @DavidPetersonH​​arvey有一个*东西; 如果`sizeof(struct mystruct)> sizeof(struct mystruct*)`,那么按值传递`mystruct`对象本身就比通过地址传递它们要慢,而且你需要一个很好的理由进行权衡,以免你瓶颈你的程序.这可能是大多数用例涉及传递struct指针而不是原始结构的原因.就像Groo所说的那样,原始结构是有意义的,如果它们很小并且你有很多--`sizeof(struct Point)== sizeof(struct Point*)`不能得到保证,但更可能是真的. (2认同)
  • C 常见问题解答在这里有一些参考资料和引文:http://c-faq.com/struct/firstclass.html 这是您正在寻找的吗? (2认同)

Gre*_*ill 31

在进行调用时a = foo();,编译器可能会将结果结构的地址压入堆栈并将其作为"隐藏"指针传递给foo()函数.实际上,它可能会变成:

void foo(MyObj *r) {
    struct MyObj a;
    // ...
    *r = a;
}

foo(&a);
Run Code Online (Sandbox Code Playgroud)

但是,具体实现取决于编译器和/或平台.正如Carl Norum指出的那样,如果结构足够小,它甚至可以完全传回寄存器.

  • 这完全取决于实现.例如,armcc将在常规参数传递(或返回值)寄存器中传递足够小的结构. (10认同)
  • @AndersAbel:最后的`*r = a`会(有效地)将局部变量的副本复制到调用者的变量中.我说"有效",因为编译器可能实现[RVO](https://en.wikipedia.org/wiki/Return_value_optimization)并完全消除局部变量`a`. (4认同)
  • 虽然这不能直接回答问题,但这是为什么许多人会通过google`c return struct`落在这里的原因:他们知道在cdecl中,`eax`是按值返回的,而该结构通常不适合` eax`。这就是我想要的。 (3认同)

Jar*_*Par 13

struct b行无效,因为它是语法错误.如果你将它扩展为包含类型它将工作得很好

struct MyObj b = a;  // Runs fine
Run Code Online (Sandbox Code Playgroud)

C在这里做的事实上是memcpy从源结构到目标.对于赋值和返回struct值(以及C中的所有其他值)都是如此

  • @JaredPar - 编译器通常会*为结构情况调用*memcpy`函数.例如,您可以制作一个快速测试程序,并参见GCC.对于不会发生的内置类型 - 它们不足以触发这种优化. (4认同)
  • 它绝对有可能实现它 - 我正在研究的项目没有定义`memcpy`符号,因此当编译器决定自己吐出一个时,我们经常会遇到"未定义的符号"链接器错误. (3认同)

Ama*_*man 9

是的,我们可以传递结构和返回结构.你是对的,但你实际上没有传递数据类型,这应该像这个结构MyObj b = a.

实际上我也开始知道我什么时候试图找到一个更好的解决方案,在不使用指针或全局变量的情况下返回多个函数值.

现在下面是相同的例子,它计算学生标记与平均值的偏差.

#include<stdio.h>
struct marks{
    int maths;
    int physics;
    int chem;
};

struct marks deviation(struct marks student1 , struct marks student2 );

int main(){

    struct marks student;
    student.maths= 87;
    student.chem = 67;
    student.physics=96;

    struct marks avg;
    avg.maths= 55;
    avg.chem = 45;
    avg.physics=34;
    //struct marks dev;
    struct marks dev= deviation(student, avg );
    printf("%d %d %d" ,dev.maths,dev.chem,dev.physics);

    return 0;
 }

struct marks deviation(struct marks student , struct marks student2 ){
    struct marks dev;

    dev.maths = student.maths-student2.maths;
    dev.chem = student.chem-student2.chem;
    dev.physics = student.physics-student2.physics; 

    return dev;
}
Run Code Online (Sandbox Code Playgroud)


小智 6

传回结构没有问题。它将按值传递

但是,如果结构包含任何具有局部变量地址的成员呢?

struct emp {
    int id;
    char *name;
};

struct emp get() {
    char *name = "John";

    struct emp e1 = {100, name};

    return (e1);
}

int main() {

    struct emp e2 = get();

    printf("%s\n", e2.name);
}
Run Code Online (Sandbox Code Playgroud)

现在,这里e1.name包含函数的本地内存地址get()。一旦get()返回,name 的本地地址就会被释放。因此,在调用者中,如果我们尝试访问该地址,则可能会导致分段错误,因为我们正在尝试释放地址。那很不好..

当作为e1.id会完全有效,它的价值将被复制到e2.id

所以,我们应该总是尽量避免返回函数的本地内存地址。

任何 malloced 都可以在需要时返回

  • 这是错误的,将字符串文字分配给指针会强制字符串成为静态的,并且它适用于整个程序。事实上这个静态字符串是不允许写入的,所以它应该是const (`char const *name`)。你想要的是一个本地数组。 (4认同)
  • @RamonLaPietra 字符串文字是静态的,请参阅 /sf/ask/181296461/ ,即“name”(指针)的值复制到“.name”( struct offset)并指向静态字符数组。 (3认同)

Gio*_*gio 5

据我所记得,C的第一个版本只允许返回一个适合处理器寄存器的值,这意味着您只能返回一个指向结构的指针。相同的限制适用于函数参数。

最新版本允许传递较大的数据对象(如结构)。我认为此功能在80年代或90年代初已经很普遍。

但是,数组仍然只能作为指针传递和返回。

  • 是的,我可以将一个数组放入一个结构体中,但我不能例如写 typedef char arr[100]; arr foo() { ... } 即使已知大小,也无法返回数组。 (2认同)