首页 > 解决方案 > 从函数 BY VALUE 返回一个数组,当你返回一个结构时会发生什么?

问题描述

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

我希望能够从被调用函数 BY VALUE 返回一个数组,而不是通过指针。该数组必须在函数内部创建并存储在堆栈中。我想按值返回一个数组,就像您返回一个在被调用函数中声明的 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 个字节!结构是否从函数中逐个复制到寄存器中?或者它是一次从堆栈上的一个内存位置按值复制到另一个内存位置?以及在被调用函数内部创建的原始结构对象会发生什么?这些都是我想知道答案的问题。

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

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

标签: c++carraysstructparameter-passing

解决方案


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

在许多平台上,小到足以放入寄存器的结构将被返回,就好像它们是单个值一样。这将在 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调用函数的堆栈帧中分配,而是直接使用在不可见参数中传递的存储空间,从而避免在return. 这两种优化的组合(如果可能)使返回结构几乎免费。

C++ 标准通过显式允许这些优化来提供这些优化,即使在省略的副本可能在对象的复制构造函数中触发了副作用的情况下也是如此。(显然这种情况在 C 中不存在。)


推荐阅读