函数ACTUALLY如何在C中返回struct变量?

时间:2014-04-09 08:43:27

标签: c struct

对于我来说,函数返回值是如何清楚的,只是为了启动:

int f()
{
  int a = 2;
  return a;
}

现在a获取堆栈中的内存,其生命周期在f()内,以便返回将值复制到特殊寄存器的值,调用者会知道该特殊寄存器被调用者为他设置了价值。 (由于返回值持有者特殊寄存器大小的大小是有限的,这就是为什么我们不能返回大对象因此如果我们想要返回对象函数的高级语言实际上将堆中对象的地址复制到该特殊寄存器)

当我想返回一个不是指针的结构变量时,让我们回到C:

struct inventory
{
    char name[20];
    int number;
};
struct inventory function();

int main()
{
    struct inventory items;
    items=function();
    printf("\nam in main\n");
    printf("\n%s\t",items.name);
    printf(" %d\t",items.number); 
    getch();
    return 0;
}

struct inventory function()
{
    struct inventory items;
    printf(" enter the item name\n ");
    scanf(" %s ",&items.name );
    printf(" enter the number of items\n ");
    scanf("%d",&items.number );
    return items;
}

代码分叉自:https://stackoverflow.com/a/22952975/962545

这是交易,

让我们从声明但未初始化的main items变量开始,然后调用函数,返回初始化的结构变量,该变量将被复制到main中的变量。 现在我有点模糊了解function()如何返回动态创建的结构变量items(技术上不在堆中)所以这个变量的寿命在function()体内,也是大小变量item可能足够大,不适合特殊寄存器,为什么它有效?(我知道我们可以动态分配函数内部的项目并返回地址但我不想替代,我正在寻找解释)

问题: 虽然it worksfunction() 实际上返回了结构变量,并且当它应该与items一起死亡时,如何在主变量中复制function()变量返回。

我肯定错过了重要的事情,详细解释会有所帮助。 :)

修改 其他答案参考:

  1. https://stackoverflow.com/a/2155742/962545
  2. 命名为return value optimization

3 个答案:

答案 0 :(得分:34)

细节因呼召惯例而异。有些ABI没有传递整个结构的调用约定,在这种情况下,编译器可以自由地做任何它认为合理的事情。

示例包括:

  • 将整个结构传递并返回为一系列连续的寄存器(通常与“小”结构一起使用)
  • 将整个结构作为参数块放在堆栈上
  • 分配一个足够大的空参数来保存结构,以填充返回值
  • 将结构的(堆栈)地址作为参数传递(就好像该函数已声明为void function(struct inventory *)

这些实现中的任何一个都可以符合C规范。但是,让我们看一下具体的实现:我的GCC ARM交叉编译器的输出。

编译你给的代码给了我这个:

main:
    stmfd   sp!, {fp, lr}
    add fp, sp, #4
    sub sp, sp, #48
    sub r3, fp, #52
    mov r0, r3
    bl  function(PLT)

目标操作数始终位于左侧。您可以看到程序保留堆栈空间,然后将堆栈空间的地址传递为r0(ARM EABI调用约定中的第一个参数)。 function不带参数,所以这个参数显然是我们编译器添加的一个人为参数。

function看起来像这样:

function:
    stmfd   sp!, {r4, fp, lr}
    add fp, sp, #8
    sub sp, sp, #36
    str r0, [fp, #-40]
    ldr r3, .L6

        ...
    add r2, pc, r2
    mov r0, r2
    mov r1, r3
    bl  scanf(PLT)
    ldr r3, [fp, #-40]
    mov ip, r3
    sub r4, fp, #36
    ldmia   r4!, {r0, r1, r2, r3}
    stmia   ip!, {r0, r1, r2, r3}
    ldmia   r4, {r0, r1}
    stmia   ip, {r0, r1}
    ldr r0, [fp, #-40]
    sub sp, fp, #8
    ldmfd   sp!, {r4, fp, pc}

此代码基本上隐藏[fp, #-40]中的单个参数,然后加载它并开始在其指向的地址存储数据。最后,它再次在r0中返回此指针值。实际上,编译器已将函数签名转换为

struct inventory *function(struct inventory *)

返回的结构由调用者在堆栈上分配,传入,然后返回。

答案 1 :(得分:10)

你错过了C传递/返回事物的方式最明显的事情:一切都是通过价值传递的,或者至少:它表现得那样。

也就是说:

struct foo some_f( void )
{
    struct foo local = {
       .member = 123,
       .bar = 2.0
    };
    //some awsome code
    return local;
}

会工作,很好。如果结构很小,则此代码可能会创建一个本地结构变量,并将该结构的副本返回给调用者。
但是,在其他情况下,此代码将粗略地转换为:

void caller()
{
    struct foo hidden_stack_space;
    struct foo your_var = *(some_f(&hidden_stack_space));
}
//and the some_f function will behave as:
struct foo * some_f(struct foo * local)
{
    //works on local and
    return local;
}

嗯,这不是完全 一直发生的事情,但它或多或少地归结为此。结果将是相同的,但在这种情况下编译器的行为可能会有所不同。

底线是:C按值返回,因此您的代码工作正常。但是,有一些陷阱:

struct foo
{
    int member1;
    char *str;
};
struct foo some_f()
{
    char bar[] = "foobar";
    struct foo local = {
        .member1 = 123,
        .str = &bar[0]
    };
    return local;
}

危险:分配给local.str的指针指向内存,一旦返回结构,释放。在这种情况下,您对此代码所期望的问题是正确的:内存不再存在(或者不再有效) 只是因为指针是一个变量,其是mem地址,并返回/分配该值。

答案 2 :(得分:5)

一个结构,至少是一个大结构,将被分配并返回到堆栈中,并且将由调用者从堆栈中弹出(如果有的话)。编译器将尝试在调用者期望找到它的相同位置分配它,但如果不可能,它将进行复制。有可能,但不一定有指向结构的指针,通过寄存器返回。

当然,细节会因架构而异。