今天我在教几个朋友如何使用C结构体。其中一个问你是否可以从函数返回结构体,我回答说:“不能!你会返回指向动态错误结构的指针。”

来自一个主要做c++的人,我期望不能按值返回结构。在c++中,你可以重载对象的操作符=,使用函数按值返回对象是完全有意义的。然而,在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语句到底有什么作用?


当前回答

结构体b行不起作用,因为这是一个语法错误。如果你将它扩展到包含该类型,它将工作得很好

struct MyObj b = a;  // Runs fine

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

其他回答

#include <stdio.h>

struct emp {
    int id;
    char *name; /* This must point to valid memory, or replace with an array  that holes the data, like this char name[128] */
};

struct emp bad() {
    static char name[] = {'J', 'o', 'h', 'n', '\0'}; /* static enforces this array to be stored globally and not in the local stack which would not be valid after the function returns */
    struct emp e1 = {404, name};
    return (e1);
}

int main() {
    struct emp e2 = bad();
    printf("%s\n", e2.name);
}

您可以从函数返回结构(或使用=操作符)而不会出现任何问题。这是语言中定义良好的一部分。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;
}
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一样,name的内容被复制到赋值的结构变量中。

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

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

foo(&a);

然而,它的确切实现依赖于编译器和平台。正如Carl Norum所指出的,如果结构足够小,它甚至可以在寄存器中完全被传递回去。

结构变量e2地址作为参数被推到被调用栈,值在那里被赋值。实际上,get()返回e2在eax reg中的地址。这类似于引用调用。