从 C 中的函数返回一个 `struct`

Posted

技术标签:

【中文标题】从 C 中的函数返回一个 `struct`【英文标题】:Return a `struct` from a function in C 【发布时间】:2012-03-28 00:37:55 【问题描述】:

今天我正在教几个朋友如何使用 C structs。其中一个人问你是否可以从函数中返回struct,我回答说:“不!你应该动态地返回指向malloced structs 的指针。”

来自主要使用 C++ 的人,我期望无法按值返回 structs。在 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 语句与= 符号结合起来究竟有什么作用?

【问题讨论】:

struct b = a; 是语法错误。如果您尝试struct MyObj b = a; 会怎样? @GregHewgill:你完全正确。然而,非常有趣的是,struct MyObj b = a; 似乎确实有效:) 【参考方案1】:

您可以毫无问题地从函数返回结构(或使用= 运算符)。它是语言中定义明确的部分。 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;

【讨论】:

这很有趣。我一直觉得你需要这些指针。我错了:) 你当然不需要指针。也就是说,大多数时候你会想要使用它们 - 隐式内存副本发生的按值抛出结构可能会真正浪费 CPU 周期,更不用说内存带宽了。 @CarlNorum 一个结构需要多大才能获得比 malloc + free 更多的副本成本? @josefx,单份?应该是巨大的。问题是,通常如果您通过值传递结构,您将复制它们很多。无论如何,它并不是那么简单。您可能正在传递本地或全局结构,在这种情况下,它们的分配成本几乎是免费的。 一旦在编译时不知道为某个值分配的内存量,您就需要为函数体外部的返回值分配指针和内存。它用于结构,因此 C 函数返回它们没有问题。【参考方案2】:

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

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


foo(&a);

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

【讨论】:

这完全取决于实现。例如,armcc 将在常规参数传递(或返回值)寄存器中传递足够小的结构。 这不是返回一个指向局部变量的指针吗?返回结构的内存不能是foo 堆栈帧的一部分。它必须是在foo 回归后仍然存在的地方。 @AndersAbel:我认为 Greg 的意思是编译器获取一个指向 main 函数中的变量的指针并将其传递给函数 foo。在函数foo 中,你只需做任务 @AndersAbel:最后的*r = a 将(有效地)将局部变量复制到调用者的变量中。我说“有效”是因为编译器可能会实现RVO 并完全消除局部变量a 虽然这并不能直接回答问题,但这就是为什么很多人会通过谷歌c return struct落到这里的原因:他们知道在cdecl中eax是按值返回的,而且结构一般不适合eax。这就是我一直在寻找的。​​span> 【参考方案3】:

struct b 行不起作用,因为它是语法错误。如果您将其扩展为包含类型,它将正常工作

struct MyObj b = a;  // Runs fine

C 在这里所做的本质上是一个从源结构到目标的memcpystruct 值的赋值和返回都是如此(实际上是 C 中的所有其他值)

【讨论】:

+1,事实上,在这种情况下,许多编译器实际上会发出对memcpy 的字面调用——至少,如果结构相当大的话。 那么,在数据类型的初始化过程中,memcpy 函数起作用了吗?? @bhuwansahni 我不太确定你在这里问什么。你能详细说明一下吗? @JaredPar - 编译器经常逐字地调用 memcpy 函数来处理结构情况。例如,您可以制作一个快速测试程序并查看 GCC 执行此操作。对于不会发生的内置类型 - 它们不够大,无法触发这种优化。 绝对有可能实现它——我正在处理的项目没有定义memcpy 符号,所以当编译器决定时我们经常遇到“未定义符号”链接器错误自己吐出来一个。【参考方案4】:

据我所知,C 的第一个版本只允许返回一个值 可以放入处理器寄存器,这意味着您只能返回一个指向 一个结构。同样的限制也适用于函数参数。

最近的版本允许传递更大的数据对象,如结构。 我认为这个功能在八十年代或九十年代初就已经很普遍了。

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

【讨论】:

如果将数组放在结构中,则可以按值返回数组。不能按值返回的是变长数组。 是的,我可以将数组放入结构中,但我不能,例如写 typedef char arr[100]; arr foo() ... 无法返回数组,即使大小已知。 投反对票的人能否解释投反对票的原因?如果我的答案包含不正确的信息,我会很乐意修复它。【参考方案5】:

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

【讨论】:

【参考方案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);

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

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

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

任何被分配的东西都可以在需要时返回

【讨论】:

这是错误的,将字符串文字分配给指针会强制字符串是静态的并且它存在于整个程序中。实际上这个静态字符串是不允许写入的,所以应该是 const (char const *name)。你想要的是一个本地数组。 这不是返回结构或指针的问题。成员 name 仍然指向一个在 get() 函数之外不可用的局部变量,即使您 malloc e1 并返回其指针【参考方案7】:

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

你只是在你的行中遗漏了部分类型——结构标记——这是行不通的。

【讨论】:

【参考方案8】:
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 一样,名称的内容被复制到分配的结构变量中。

【讨论】:

更简单:struct emp get() return 100, "john"; 【参考方案9】:

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

【讨论】:

以上是关于从 C 中的函数返回一个 `struct`的主要内容,如果未能解决你的问题,请参考以下文章

如何通过 C# 从 C++ dll 调用 struct 中的函数

从给定的 x86 程序集编写 C 函数

不同应用程序(WPF 和 Unity)中的 C# struct 序列化返回不同大小的字节数组

MATLAB中的struct操作

C语言函数中如何返回一个结构体类型

请问 结构体能做函数的参数吗? struct point makepoint ( int x ,int y , char c , struct point sp )