函数如何在C中实际返回结构变量?

问题描述 投票:43回答:3

函数返回值对我来说很清楚,只是为了开始:

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 works,但是当应该以function()返回时,items 实际上返回了struct变量并被复制转换为main中的function()变量。

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

编辑:其他答案参考:

  1. https://stackoverflow.com/a/2155742/962545
  2. 命名为return value optimization
c struct
3个回答
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 *)

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


10
投票

您错过了C传递/返回事物的方式中最明显的东西:所有东西都通过值传递或至少:它的行为方式如此。

也就是说:

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

会的,很好。如果该结构很小,则此代码可能会创建一个本地结构变量,并将该结构的copy返回给调用方。但是,在其他情况下,此代码将大致翻译为:

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的指针指向一旦返回结构便释放的内存。在那种情况下,您期望使用此代码的问题是正确的:该内存不再(或不再有效)。仅仅因为指针是一个变量,其value是内存地址,并且该值被返回/分配。


5
投票

一个结构,至少是一个大的结构,将被分配并返回到堆栈上,并由调用方从堆栈中弹出(如果有的话)。编译器将尝试在调用者希望找到它的位置分配它,但是如果不可能的话,它将进行复制。可能但不一定有通过寄存器返回的指向结构的指针。当然,具体内容将取决于体系结构。

© www.soinside.com 2019 - 2024. All rights reserved.