从函数返回值VALUE,返回结构时会发生什么?

时间:2018-07-13 05:12:23

标签: c++ c arrays struct parameter-passing

现在,我正在学习C和C ++的来龙去脉。我知道当您在函数内部创建数组时,它会存储在该函数的堆栈框架中。您可以返回数组的基地址,它实际上是指向该数组中第一个元素的指针。返回的指针值将存储到EAX / RAX寄存器中,然后将来自寄存器的值移动到调用函数本地的指针变量中。问题在于,当函数返回时,该函数的堆栈框架会从调用的堆栈中弹出,并且在该函数的堆栈框架内声明的所有数据都会过期。指针现在指向无效的内存位置。

我希望能够从调用函数中按值而不是指针返回数组。数组必须在函数内部创建并存储在堆栈中。我想按值返回一个数组,就像您返回一个在被调用函数内部声明的int一样。

int f() {
   int a = 5;
   return a;  // returned by value
}

int main() {
    int b = f();
    return 0;
}

此处,将int值移动到EAX / RAX寄存器中,因此它是一个副本。被调用函数的堆栈框架从调用堆栈中清除,但是没有问题,因为返回值现​​在就在将其复制到int b之前存储在寄存器中。

我知道在C ++中,我可以在被调用函数内创建一个向量,然后按值返回它。但是我不想使用这种更高层次的抽象来学习一种“ hacky”方式。我待会儿回到向量。

嗯,我意识到可以通过函数的值返回结构对象。因此,我按值返回数组的解决方案非常简单:将其放在结构中,然后按值返回该结构!

struct String {
    char array[20];
};

struct String f() {
    struct String myString;
    strcpy(myString.array, "Hello World");
    return myString;  // Is this returned by value?
}

int main() {
    struct String word = f();
    printf("%s\n", word.array);
}

如果我正确理解代码,请澄清一下。该结构对象是在调用函数的堆栈框架内创建的,“ Hello World”被复制到其中包含的数组中,然后呢?

struct String word是一个左值,f()返回一个rvlaue。将一个结构分配给另一个结构后,它的所有数据成员都会被一个一个地复制。

从调用的函数按值返回结构之后,在将其分配给main()函数内部的结构之前,会发生什么? EAX / RAX寄存器是返回值的目的地。它是64位还是32位,具体取决于您使用的是64位还是32位计算机。您如何精确地将结构对象放入寄存器?我想像该数组不仅可能是20个字节,还可以说是100个字节!是否将结构从函数逐段复制到寄存器中?还是将它从堆栈中的一个存储位置一次复制到另一个值?而且,在调用函数内部创建的原始struct对象会怎样?这些都是我想知道答案的问题。

另外,关于按值从函数返回向量。 C ++中的向量是类,并且类与结构相似。您能否回答这个问题,当您从函数中按值返回向量时会发生什么?当您将类/结构对象作为参数传递给函数 时会发生什么?

我可以想象按值传递如何处理小型数据类型。我什至不知道它如何处理复杂的数据类型和数据结构。

1 个答案:

答案 0 :(得分:0)

精确的机制取决于平台。但是最常见的机制是,调用方在其堆栈上分配空间以返回要返回的结构,并将该空间的地址作为额外的参数传递,通常在所有实际参数之前。

在许多平台上,将返回足够小的结构以适合寄存器的结构,就好像它们是单个值一样。这将在x86-64上适用于由两个32位int组成的结构,因为它们可以在单个64位寄存器中返回。这种方式可以处理的结构大小会因平台而异。

通过复制省略可以改善按值传递较大结构的成本。例如,如果您写

struct MyThingy blob = blobMaker();

编译器可能会传递blobMaker变量blob的地址,而不是分配一个临时变量,然后在函数返回后将临时变量复制到blob。被调用的函数还可以避免复制:

struct MyThingy blobMaker(void) {
  struct MyThingy retval;
  // ...
  retval.member1 = some_calc(42);
  // ...
  retval.member2 = "Hello";
  // ...
  return retval;

在这里,编译器可能选择不在被调用函数的堆栈帧中分配retval,而是直接使用在invisible参数中传递的存储,从而避免在return进行复制。这两个优化的组合(如果可能)使得返回的结构几乎是免费的。

C ++标准通过显式允许它们进行优化,即使在被删除的副本可能在对象的副本构造函数中触发了副作用的情况下,它们也可以提供这些优化。 (显然,这种情况在C语言中不存在。)