从C中的函数返回`struct`


171

今天,我在教学上几个朋友如何用C struct秒。其中一个询问您是否可以struct从函数中返回a ,我回答道:“不!您将返回指向动态malloced 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;
}    

我知道为什么struct b = a;不起作用-您不能operator =为您的数据类型超载。a = foo();编译效果如何?它不是什么意思struct b = a;吗?可能要问的问题是:return=签名共同声明的语句到底做了什么?

[edit]:好的,我只是指出这struct b = a是一个语法错误-没错,我是个白痴!但这使事情变得更加复杂!使用struct MyObj b = a确实有效!我在这里想念什么?


23
struct b = a;是语法错误。如果您尝试struct MyObj b = a;怎么办?
Greg Hewgill'3

2
@GregHewgill:你绝对正确。有趣的是,struct MyObj b = a;它似乎确实起作用了:)
mmirzadeh,2012年

Answers:


199

您可以从函数返回结构(或使用=运算符),而不会出现任何问题。这是语言中定义明确的部分。唯一的问题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;
}

下一个示例几乎完全相同,但是使用内置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;
}

14
那很有趣。我总是觉得您需要这些的指示。我错了:)
mmirzadeh,2012年

8
您当然不需要指针。就是说,大多数时候您都想使用它们-按值围绕结构浮动的隐式内存副本实际上是对CPU周期的浪费,更不用说内存带宽了。
卡尔·诺鲁姆

10
@CarlNorum要获得一个副本要比malloc + free多得多的结构必须达到多大?
josefx 2012年

7
@josefx,一个副本?可能很大。关键是,通常如果您按值传递结构,则会大量复制它们。无论如何,它并不是真的那么简单。您可能会绕过本地或全局结构,在这种情况下,其分配成本几乎是免费的。
卡尔·诺鲁姆

7
一旦在编译时不知道为值分配的内存量,就需要在函数体外部为返回的值分配指针和内存。它用于结构,因此C函数可以毫无问题地返回它们。
reinierpost 2015年

33

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

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

foo(&a);

但是,此操作的确切实现取决于编译器和/或平台。正如Carl Norum所指出的,如果结构足够小,它甚至可能会完全传回寄存器中。


11
这完全取决于实现。例如,armcc将在常规参数传递(或返回值)寄存器中传递足够小的结构。
卡尔·诺鲁姆

那不是会返回指向局部变量的指针吗?返回结构的内存不能成为foo堆栈框架的一部分。它必须在一个能够超越回归的地方生存foo
安德斯·亚伯

@AndersAbel:我认为Greg的意思是编译器将指针指向函数中的变量,并将其传递给函数foo。在函数内部foo,您只需执行任务
mmirzadeh 2012年

4
@AndersAbel:*r = a最后,(有效)将本地变量复制到调用者的变量。我说“有效”是因为编译器可以实现RVOa完全消除局部变量。
Greg Hewgill'3

3
尽管这不能直接回答问题,但这是许多人会通过google落在这里的原因c return struct:他们知道cdecl eax是按值返回的,并且结构通常不适合内部eax。这就是我想要的。
Ciro Santilli郝海东冠状病六四事件法轮功

14

struct b行不起作用,因为它是语法错误。如果将其扩展为包括该类型,它将很好用

struct MyObj b = a;  // Runs fine

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


+1,实际上,memcpy在这种情况下,至少在结构相当大的情况下,许多编译器实际上会发出字面量调用。
卡尔·诺鲁姆

因此,在数据类型初始化期间,memcpy函数可以正常工作?
bhuwansahni'3

1
@bhuwansahni我不太确定你在这里问什么。您能详细说明一下吗?
JaredPar'3

4
@JaredPar-编译器通常确实在结构情况下调用memcpy函数。例如,您可以制作一个快速测试程序并查看GCC。对于不会发生的内置类型-它们的大小不足以触发这种优化。
卡尔·诺鲁姆

3
绝对有可能实现-我正在处理的项目没有memcpy定义符号,因此当编译器决定自己吐出一个代码时,我们经常会遇到“未定义符号”链接器错误。
卡尔·诺鲁姆

9

是的,我们也可以传递结构和返回结构。您是对的,但实际上您没有传递应该像这样的数据类型struct 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;
}

5

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

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

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


如果将数组放入结构中,则可以按值返回数组。您无法按值返回的是可变长度数组。
2012年

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

下票者可以解释下票的原因吗?如果我的答案包含不正确的信息,我将很乐意解决。
乔治

4

可以在C中分配结构。a = b; 有效语法。

您只是在行中忽略了类型的一部分-struct标记-这行不通。


4

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

但是,如果该结构包含任何具有局部变量地址的成员,该怎么办

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);
}

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

其中,e1.id将完全有效,因为其值将被复制到e2.id

因此,我们应始终尝试避免返回函数的本地内存地址。

任何分配的内容都可以在需要时返回


2
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);
}

与较新版本的编译器一起使用时效果很好。就像id一样,名称的内容会复制到分配的结构变量中。


1
更简单:struct emp get(){return {100,“ john”}; }
克里斯·里德

1

将struct var e2地址作为arg推送到被调用者堆栈,并在那里分配值。实际上,get()在eax reg中返回e2的地址。这类似于按引用调用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.